Merge "Return Bool From isNullCipherAndIntegrityPreferenceEnabled"
diff --git a/apct-tests/perftests/settingsprovider/AndroidManifest.xml b/apct-tests/perftests/settingsprovider/AndroidManifest.xml
index 140c280..9509c83 100644
--- a/apct-tests/perftests/settingsprovider/AndroidManifest.xml
+++ b/apct-tests/perftests/settingsprovider/AndroidManifest.xml
@@ -15,7 +15,7 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="com.android.perftests.multiuser">
+        package="com.android.perftests.settingsprovider">
 
     <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
     <uses-permission android:name="android.permission.READ_DEVICE_CONFIG" />
@@ -26,6 +26,6 @@
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-            android:targetPackage="com.android.perftests.multiuser"/>
+            android:targetPackage="com.android.perftests.settingsprovider"/>
 
 </manifest>
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
index 30986dd..c90445e 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
@@ -761,8 +761,8 @@
             if (js != null) {
                 mWorkCountTracker.incrementRunningJobCount(jsc.getRunningJobWorkType());
                 assignment.workType = jsc.getRunningJobWorkType();
-                if (js.startedAsExpeditedJob && js.lastEvaluatedBias == JobInfo.BIAS_TOP_APP) {
-                    info.numRunningTopEj++;
+                if (js.startedWithImmediacyPrivilege) {
+                    info.numRunningImmediacyPrivileged++;
                 }
             }
 
@@ -829,11 +829,9 @@
                 continue;
             }
 
-            final boolean isTopEj = nextPending.shouldTreatAsExpeditedJob()
-                    && nextPending.lastEvaluatedBias == JobInfo.BIAS_TOP_APP;
+            final boolean hasImmediacyPrivilege = hasImmediacyPrivilegeLocked(nextPending);
             if (DEBUG && isSimilarJobRunningLocked(nextPending)) {
-                Slog.w(TAG, "Already running similar " + (isTopEj ? "TOP-EJ" : "job")
-                        + " to: " + nextPending);
+                Slog.w(TAG, "Already running similar job to: " + nextPending);
             }
 
             // Factoring minChangedWaitingTimeMs into the min waiting time effectively limits
@@ -876,23 +874,25 @@
                     final JobStatus runningJob = assignment.context.getRunningJobLocked();
                     // Maybe stop the job if it has had its day in the sun. Only allow replacing
                     // for one of the following conditions:
-                    // 1. We're putting in the current TOP app's EJ
+                    // 1. We're putting in a job that has the privilege of running immediately
                     // 2. There aren't too many jobs running AND the current job started when the
                     //    app was in the background
                     // 3. There aren't too many jobs running AND the current job started when the
                     //    app was on TOP, but the app has since left TOP
                     // 4. There aren't too many jobs running AND the current job started when the
-                    //    app was on TOP, the app is still TOP, but there are too many TOP+EJs
+                    //    app was on TOP, the app is still TOP, but there are too many
+                    //    immediacy-privileged jobs
                     //    running (because we don't want them to starve out other apps and the
                     //    current job has already run for the minimum guaranteed time).
                     // 5. This new job could be waiting for too long for a slot to open up
-                    boolean canReplace = isTopEj; // Case 1
+                    boolean canReplace = hasImmediacyPrivilege; // Case 1
                     if (!canReplace && !isInOverage) {
                         final int currentJobBias = mService.evaluateJobBiasLocked(runningJob);
                         canReplace = runningJob.lastEvaluatedBias < JobInfo.BIAS_TOP_APP // Case 2
                                 || currentJobBias < JobInfo.BIAS_TOP_APP // Case 3
                                 // Case 4
-                                || info.numRunningTopEj > .5 * mWorkTypeConfig.getMaxTotal();
+                                || info.numRunningImmediacyPrivileged
+                                        > (mWorkTypeConfig.getMaxTotal() / 2);
                     }
                     if (!canReplace && mMaxWaitTimeBypassEnabled) { // Case 5
                         if (nextPending.shouldTreatAsExpeditedJob()) {
@@ -919,7 +919,7 @@
                     }
                 }
             }
-            if (selectedContext == null && (!isInOverage || isTopEj)) {
+            if (selectedContext == null && (!isInOverage || hasImmediacyPrivilege)) {
                 int lowestBiasSeen = Integer.MAX_VALUE;
                 long newMinPreferredUidOnlyWaitingTimeMs = Long.MAX_VALUE;
                 for (int p = preferredUidOnly.size() - 1; p >= 0; --p) {
@@ -962,12 +962,13 @@
                     info.minPreferredUidOnlyWaitingTimeMs = newMinPreferredUidOnlyWaitingTimeMs;
                 }
             }
-            // Make sure to run EJs for the TOP app immediately.
-            if (isTopEj) {
+            // Make sure to run jobs with special privilege immediately.
+            if (hasImmediacyPrivilege) {
                 if (selectedContext != null
                         && selectedContext.context.getRunningJobLocked() != null) {
-                    // We're "replacing" a currently running job, but we want TOP EJs to start
-                    // immediately, so we'll start the EJ on a fresh available context and
+                    // We're "replacing" a currently running job, but we want immediacy-privileged
+                    // jobs to start immediately, so we'll start the privileged jobs on a fresh
+                    // available context and
                     // stop this currently running job to replace in two steps.
                     changed.add(selectedContext);
                     projectedRunningCount--;
@@ -1029,6 +1030,7 @@
                     projectedRunningCount--;
                 }
                 if (selectedContext.newJob != null) {
+                    selectedContext.newJob.startedWithImmediacyPrivilege = hasImmediacyPrivilege;
                     projectedRunningCount++;
                     minChangedWaitingTimeMs = Math.min(minChangedWaitingTimeMs,
                             mService.getMinJobExecutionGuaranteeMs(selectedContext.newJob));
@@ -1103,6 +1105,18 @@
         mActivePkgStats.forEach(mPackageStatsStagingCountClearer);
     }
 
+    @VisibleForTesting
+    @GuardedBy("mLock")
+    boolean hasImmediacyPrivilegeLocked(@NonNull JobStatus job) {
+        // EJs & user-initiated jobs for the TOP app should run immediately.
+        // However, even for user-initiated jobs, if the app has not recently been in TOP or BAL
+        // state, we don't give the immediacy privilege so that we can try and maintain
+        // reasonably concurrency behavior.
+        return job.lastEvaluatedBias == JobInfo.BIAS_TOP_APP
+                // TODO(): include BAL state for user-initiated jobs
+                && (job.shouldTreatAsExpeditedJob() || job.shouldTreatAsUserInitiated());
+    }
+
     @GuardedBy("mLock")
     void onUidBiasChangedLocked(int prevBias, int newBias) {
         if (prevBias != JobInfo.BIAS_TOP_APP && newBias != JobInfo.BIAS_TOP_APP) {
@@ -1361,7 +1375,7 @@
         mActiveServices.remove(worker);
         if (mIdleContexts.size() < MAX_RETAINED_OBJECTS) {
             // Don't need to save all new contexts, but keep some extra around in case we need
-            // extras for another TOP+EJ overage.
+            // extras for another immediacy privileged overage.
             mIdleContexts.add(worker);
         } else {
             mNumDroppedContexts++;
@@ -1403,7 +1417,8 @@
             }
             if (respectConcurrencyLimit) {
                 worker.clearPreferredUid();
-                // We're over the limit (because the TOP app scheduled a lot of EJs), but we should
+                // We're over the limit (because there were a lot of immediacy-privileged jobs
+                // scheduled), but we should
                 // be able to stop the other jobs soon so don't start running anything new until we
                 // get back below the limit.
                 noteConcurrency();
@@ -1627,17 +1642,17 @@
                 }
             } else if (mWorkCountTracker.getPendingJobCount(WORK_TYPE_EJ) > 0) {
                 return "blocking " + workTypeToString(WORK_TYPE_EJ) + " queue";
-            } else if (js.startedAsExpeditedJob && js.lastEvaluatedBias == JobInfo.BIAS_TOP_APP) {
-                // Try not to let TOP + EJ starve out other apps.
-                int topEjCount = 0;
+            } else if (js.startedWithImmediacyPrivilege) {
+                // Try not to let jobs with immediacy privilege starve out other apps.
+                int immediacyPrivilegeCount = 0;
                 for (int r = mRunningJobs.size() - 1; r >= 0; --r) {
                     JobStatus j = mRunningJobs.valueAt(r);
-                    if (j.startedAsExpeditedJob && j.lastEvaluatedBias == JobInfo.BIAS_TOP_APP) {
-                        topEjCount++;
+                    if (j.startedWithImmediacyPrivilege) {
+                        immediacyPrivilegeCount++;
                     }
                 }
-                if (topEjCount > .5 * mWorkTypeConfig.getMaxTotal()) {
-                    return "prevent top EJ dominance";
+                if (immediacyPrivilegeCount > mWorkTypeConfig.getMaxTotal() / 2) {
+                    return "prevent immediacy privilege dominance";
                 }
             }
             // No other pending EJs. Return null so we don't let regular jobs preempt an EJ.
@@ -2566,11 +2581,11 @@
     @VisibleForTesting
     static final class AssignmentInfo {
         public long minPreferredUidOnlyWaitingTimeMs;
-        public int numRunningTopEj;
+        public int numRunningImmediacyPrivileged;
 
         void clear() {
             minPreferredUidOnlyWaitingTimeMs = 0;
-            numRunningTopEj = 0;
+            numRunningImmediacyPrivileged = 0;
         }
     }
 
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
index 419127e..2e67a81 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
@@ -388,6 +388,8 @@
      */
     public boolean startedAsExpeditedJob = false;
 
+    public boolean startedWithImmediacyPrivilege = false;
+
     // If non-null, this is work that has been enqueued for the job.
     public ArrayList<JobWorkItem> pendingWork;
 
diff --git a/core/api/current.txt b/core/api/current.txt
index ccf6578..1695d12 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -4292,6 +4292,7 @@
     method @Deprecated public final void removeDialog(int);
     method public void reportFullyDrawn();
     method public android.view.DragAndDropPermissions requestDragAndDropPermissions(android.view.DragEvent);
+    method public void requestFullscreenMode(@NonNull int, @Nullable android.os.OutcomeReceiver<java.lang.Void,java.lang.Throwable>);
     method public final void requestPermissions(@NonNull String[], int);
     method public final void requestShowKeyboardShortcuts();
     method @Deprecated public boolean requestVisibleBehind(boolean);
@@ -4378,6 +4379,8 @@
     field public static final int DEFAULT_KEYS_SEARCH_LOCAL = 3; // 0x3
     field public static final int DEFAULT_KEYS_SHORTCUT = 2; // 0x2
     field protected static final int[] FOCUSED_STATE_SET;
+    field public static final int FULLSCREEN_MODE_REQUEST_ENTER = 1; // 0x1
+    field public static final int FULLSCREEN_MODE_REQUEST_EXIT = 0; // 0x0
     field public static final int RESULT_CANCELED = 0; // 0x0
     field public static final int RESULT_FIRST_USER = 1; // 0x1
     field public static final int RESULT_OK = -1; // 0xffffffff
@@ -10589,6 +10592,7 @@
     field public static final String EXTRA_MIME_TYPES = "android.intent.extra.MIME_TYPES";
     field public static final String EXTRA_NOT_UNKNOWN_SOURCE = "android.intent.extra.NOT_UNKNOWN_SOURCE";
     field public static final String EXTRA_ORIGINATING_URI = "android.intent.extra.ORIGINATING_URI";
+    field public static final String EXTRA_PACKAGES = "android.intent.extra.PACKAGES";
     field public static final String EXTRA_PACKAGE_NAME = "android.intent.extra.PACKAGE_NAME";
     field public static final String EXTRA_PERMISSION_GROUP_NAME = "android.intent.extra.PERMISSION_GROUP_NAME";
     field public static final String EXTRA_PHONE_NUMBER = "android.intent.extra.PHONE_NUMBER";
@@ -11718,9 +11722,12 @@
     method public void unregisterSessionCallback(@NonNull android.content.pm.PackageInstaller.SessionCallback);
     method public void updateSessionAppIcon(int, @Nullable android.graphics.Bitmap);
     method public void updateSessionAppLabel(int, @Nullable CharSequence);
+    method public void waitForInstallConstraints(@NonNull java.util.List<java.lang.String>, @NonNull android.content.pm.PackageInstaller.InstallConstraints, @NonNull android.content.IntentSender, long);
     field public static final String ACTION_SESSION_COMMITTED = "android.content.pm.action.SESSION_COMMITTED";
     field public static final String ACTION_SESSION_DETAILS = "android.content.pm.action.SESSION_DETAILS";
     field public static final String ACTION_SESSION_UPDATED = "android.content.pm.action.SESSION_UPDATED";
+    field public static final String EXTRA_INSTALL_CONSTRAINTS = "android.content.pm.extra.INSTALL_CONSTRAINTS";
+    field public static final String EXTRA_INSTALL_CONSTRAINTS_RESULT = "android.content.pm.extra.INSTALL_CONSTRAINTS_RESULT";
     field public static final String EXTRA_OTHER_PACKAGE_NAME = "android.content.pm.extra.OTHER_PACKAGE_NAME";
     field public static final String EXTRA_PACKAGE_NAME = "android.content.pm.extra.PACKAGE_NAME";
     field public static final String EXTRA_PRE_APPROVAL = "android.content.pm.extra.PRE_APPROVAL";
@@ -17936,6 +17943,7 @@
     method public int capture(@NonNull android.hardware.camera2.CaptureRequest, @NonNull java.util.concurrent.Executor, @NonNull android.hardware.camera2.CameraExtensionSession.ExtensionCaptureCallback) throws android.hardware.camera2.CameraAccessException;
     method public void close() throws android.hardware.camera2.CameraAccessException;
     method @NonNull public android.hardware.camera2.CameraDevice getDevice();
+    method @Nullable public android.util.Pair<java.lang.Long,java.lang.Long> getRealtimeStillCaptureLatency() throws android.hardware.camera2.CameraAccessException;
     method public int setRepeatingRequest(@NonNull android.hardware.camera2.CaptureRequest, @NonNull java.util.concurrent.Executor, @NonNull android.hardware.camera2.CameraExtensionSession.ExtensionCaptureCallback) throws android.hardware.camera2.CameraAccessException;
     method public void stopRepeating() throws android.hardware.camera2.CameraAccessException;
   }
@@ -20518,6 +20526,7 @@
     field public static final int ENCODING_DOLBY_MAT = 19; // 0x13
     field public static final int ENCODING_DOLBY_TRUEHD = 14; // 0xe
     field public static final int ENCODING_DRA = 28; // 0x1c
+    field public static final int ENCODING_DSD = 31; // 0x1f
     field public static final int ENCODING_DTS = 7; // 0x7
     field public static final int ENCODING_DTS_HD = 8; // 0x8
     field public static final int ENCODING_DTS_HD_MA = 29; // 0x1d
@@ -26785,6 +26794,7 @@
     method public boolean onKeyUp(int, @NonNull android.view.KeyEvent);
     method public void onMediaViewSizeChanged(@Px int, @Px int);
     method public void onRecordingStarted(@NonNull String);
+    method public void onRecordingStopped(@NonNull String);
     method public abstract void onRelease();
     method public void onResetInteractiveApp();
     method public abstract boolean onSetSurface(@Nullable android.view.Surface);
@@ -26811,6 +26821,7 @@
     method @CallSuper public void requestCurrentTvInputId();
     method @CallSuper public void requestSigning(@NonNull String, @NonNull String, @NonNull String, @NonNull byte[]);
     method @CallSuper public void requestStartRecording(@Nullable android.net.Uri);
+    method @CallSuper public void requestStopRecording(@NonNull String);
     method @CallSuper public void requestStreamVolume();
     method @CallSuper public void requestTrackInfoList();
     method @CallSuper public void sendPlaybackCommandRequest(@NonNull String, @Nullable android.os.Bundle);
@@ -26843,6 +26854,7 @@
     method @Nullable public android.media.tv.interactive.TvInteractiveAppView.OnUnhandledInputEventListener getOnUnhandledInputEventListener();
     method public void notifyError(@NonNull String, @NonNull android.os.Bundle);
     method public void notifyRecordingStarted(@NonNull String);
+    method public void notifyRecordingStopped(@NonNull String);
     method public void onAttachedToWindow();
     method public void onDetachedFromWindow();
     method public void onLayout(boolean, int, int, int, int);
@@ -26885,6 +26897,7 @@
     method public void onRequestCurrentTvInputId(@NonNull String);
     method public void onRequestSigning(@NonNull String, @NonNull String, @NonNull String, @NonNull String, @NonNull byte[]);
     method public void onRequestStartRecording(@NonNull String, @Nullable android.net.Uri);
+    method public void onRequestStopRecording(@NonNull String, @NonNull String);
     method public void onRequestStreamVolume(@NonNull String);
     method public void onRequestTrackInfoList(@NonNull String);
     method public void onSetVideoBounds(@NonNull String, @NonNull android.graphics.Rect);
@@ -27851,6 +27864,7 @@
   public final class VcnConfig implements android.os.Parcelable {
     method public int describeContents();
     method @NonNull public java.util.Set<android.net.vcn.VcnGatewayConnectionConfig> getGatewayConnectionConfigs();
+    method @NonNull public java.util.Set<java.lang.Integer> getRestrictedUnderlyingNetworkTransports();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.net.vcn.VcnConfig> CREATOR;
   }
@@ -27859,6 +27873,7 @@
     ctor public VcnConfig.Builder(@NonNull android.content.Context);
     method @NonNull public android.net.vcn.VcnConfig.Builder addGatewayConnectionConfig(@NonNull android.net.vcn.VcnGatewayConnectionConfig);
     method @NonNull public android.net.vcn.VcnConfig build();
+    method @NonNull public android.net.vcn.VcnConfig.Builder setRestrictedUnderlyingNetworkTransports(@NonNull java.util.Set<java.lang.Integer>);
   }
 
   public final class VcnGatewayConnectionConfig {
@@ -36267,7 +36282,7 @@
     field public static final String PARENTAL_CONTROL_LAST_UPDATE = "parental_control_last_update";
     field public static final String PARENTAL_CONTROL_REDIRECT_URL = "parental_control_redirect_url";
     field public static final String RTT_CALLING_MODE = "rtt_calling_mode";
-    field public static final String SECURE_FRP_MODE = "secure_frp_mode";
+    field @Deprecated public static final String SECURE_FRP_MODE = "secure_frp_mode";
     field public static final String SELECTED_INPUT_METHOD_SUBTYPE = "selected_input_method_subtype";
     field public static final String SETTINGS_CLASSNAME = "settings_classname";
     field public static final String SKIP_FIRST_USE_HINTS = "skip_first_use_hints";
@@ -42024,6 +42039,8 @@
     method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.READ_PHONE_STATE, "carrier privileges"}) public android.os.PersistableBundle getConfigForSubId(int, @NonNull java.lang.String...);
     method public static boolean isConfigForIdentifiedCarrier(android.os.PersistableBundle);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void notifyConfigChangedForSubId(int);
+    method public void registerCarrierConfigChangeListener(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.CarrierConfigManager.CarrierConfigChangeListener);
+    method public void unregisterCarrierConfigChangeListener(@NonNull android.telephony.CarrierConfigManager.CarrierConfigChangeListener);
     field public static final String ACTION_CARRIER_CONFIG_CHANGED = "android.telephony.action.CARRIER_CONFIG_CHANGED";
     field public static final int CARRIER_NR_AVAILABILITY_NSA = 1; // 0x1
     field public static final int CARRIER_NR_AVAILABILITY_SA = 2; // 0x2
@@ -42353,6 +42370,10 @@
     field public static final String KEY_PREFIX = "bsf.";
   }
 
+  public static interface CarrierConfigManager.CarrierConfigChangeListener {
+    method public void onCarrierConfigChanged(int, int, int, int);
+  }
+
   public static final class CarrierConfigManager.Gps {
     field public static final String KEY_PERSIST_LPP_MODE_BOOL = "gps.persist_lpp_mode_bool";
     field public static final String KEY_PREFIX = "gps.";
@@ -52765,12 +52786,14 @@
     method public void addAudioDescriptionRequestedChangeListener(@NonNull java.util.concurrent.Executor, @NonNull android.view.accessibility.AccessibilityManager.AudioDescriptionRequestedChangeListener);
     method public boolean addTouchExplorationStateChangeListener(@NonNull android.view.accessibility.AccessibilityManager.TouchExplorationStateChangeListener);
     method public void addTouchExplorationStateChangeListener(@NonNull android.view.accessibility.AccessibilityManager.TouchExplorationStateChangeListener, @Nullable android.os.Handler);
+    method public void addUiContrastChangeListener(@NonNull java.util.concurrent.Executor, @NonNull android.view.accessibility.AccessibilityManager.UiContrastChangeListener);
     method @ColorInt public int getAccessibilityFocusColor();
     method public int getAccessibilityFocusStrokeWidth();
     method @Deprecated public java.util.List<android.content.pm.ServiceInfo> getAccessibilityServiceList();
     method public java.util.List<android.accessibilityservice.AccessibilityServiceInfo> getEnabledAccessibilityServiceList(int);
     method public java.util.List<android.accessibilityservice.AccessibilityServiceInfo> getInstalledAccessibilityServiceList();
     method public int getRecommendedTimeoutMillis(int, int);
+    method @FloatRange(from=-1.0F, to=1.0f) public float getUiContrast();
     method public void interrupt();
     method public static boolean isAccessibilityButtonSupported();
     method public boolean isAudioDescriptionRequested();
@@ -52782,6 +52805,7 @@
     method public boolean removeAccessibilityStateChangeListener(@NonNull android.view.accessibility.AccessibilityManager.AccessibilityStateChangeListener);
     method public boolean removeAudioDescriptionRequestedChangeListener(@NonNull android.view.accessibility.AccessibilityManager.AudioDescriptionRequestedChangeListener);
     method public boolean removeTouchExplorationStateChangeListener(@NonNull android.view.accessibility.AccessibilityManager.TouchExplorationStateChangeListener);
+    method public void removeUiContrastChangeListener(@NonNull android.view.accessibility.AccessibilityManager.UiContrastChangeListener);
     method public void sendAccessibilityEvent(android.view.accessibility.AccessibilityEvent);
     field public static final int FLAG_CONTENT_CONTROLS = 4; // 0x4
     field public static final int FLAG_CONTENT_ICONS = 1; // 0x1
@@ -52804,6 +52828,10 @@
     method public void onTouchExplorationStateChanged(boolean);
   }
 
+  public static interface AccessibilityManager.UiContrastChangeListener {
+    method public void onUiContrastChanged(@FloatRange(from=-1.0F, to=1.0f) float);
+  }
+
   public class AccessibilityNodeInfo implements android.os.Parcelable {
     ctor public AccessibilityNodeInfo();
     ctor public AccessibilityNodeInfo(@NonNull android.view.View);
@@ -54544,6 +54572,8 @@
     method @NonNull public android.text.SegmentFinder getGraphemeSegmentFinder();
     method @NonNull public android.text.SegmentFinder getLineSegmentFinder();
     method @NonNull public android.graphics.Matrix getMatrix();
+    method public int getOffsetForPosition(float, float);
+    method @Nullable public int[] getRangeForRect(@NonNull android.graphics.RectF, @NonNull android.text.SegmentFinder, @NonNull android.text.Layout.TextInclusionStrategy);
     method public int getStart();
     method @NonNull public android.text.SegmentFinder getWordSegmentFinder();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index 98c78fe..314fd03 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -99,6 +99,10 @@
 
   public class ApplicationInfo extends android.content.pm.PackageItemInfo implements android.os.Parcelable {
     method @NonNull public java.util.List<android.content.pm.SharedLibraryInfo> getSharedLibraryInfos();
+    field public static final int HIDDEN_API_ENFORCEMENT_DEFAULT = -1; // 0xffffffff
+    field public static final int HIDDEN_API_ENFORCEMENT_DISABLED = 0; // 0x0
+    field public static final int HIDDEN_API_ENFORCEMENT_ENABLED = 2; // 0x2
+    field public static final int HIDDEN_API_ENFORCEMENT_JUST_WARN = 1; // 0x1
   }
 
   public abstract class PackageManager {
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index b3cbba2..df8a47d 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -352,6 +352,7 @@
     field public static final String USE_COLORIZED_NOTIFICATIONS = "android.permission.USE_COLORIZED_NOTIFICATIONS";
     field public static final String USE_RESERVED_DISK = "android.permission.USE_RESERVED_DISK";
     field public static final String UWB_PRIVILEGED = "android.permission.UWB_PRIVILEGED";
+    field public static final String WAKEUP_SURFACE_FLINGER = "android.permission.WAKEUP_SURFACE_FLINGER";
     field public static final String WHITELIST_AUTO_REVOKE_PERMISSIONS = "android.permission.WHITELIST_AUTO_REVOKE_PERMISSIONS";
     field public static final String WHITELIST_RESTRICTED_PERMISSIONS = "android.permission.WHITELIST_RESTRICTED_PERMISSIONS";
     field public static final String WIFI_ACCESS_COEX_UNSAFE_CHANNELS = "android.permission.WIFI_ACCESS_COEX_UNSAFE_CHANNELS";
@@ -3319,7 +3320,6 @@
     field public static final String EXTRA_INSTANT_APP_TOKEN = "android.intent.extra.INSTANT_APP_TOKEN";
     field public static final String EXTRA_LONG_VERSION_CODE = "android.intent.extra.LONG_VERSION_CODE";
     field public static final String EXTRA_ORIGINATING_UID = "android.intent.extra.ORIGINATING_UID";
-    field public static final String EXTRA_PACKAGES = "android.intent.extra.PACKAGES";
     field public static final String EXTRA_PERMISSION_NAME = "android.intent.extra.PERMISSION_NAME";
     field public static final String EXTRA_REASON = "android.intent.extra.REASON";
     field public static final String EXTRA_REMOTE_CALLBACK = "android.intent.extra.REMOTE_CALLBACK";
@@ -5275,11 +5275,12 @@
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.hardware.radio.ProgramSelector> CREATOR;
     field public static final int IDENTIFIER_TYPE_AMFM_FREQUENCY = 1; // 0x1
+    field public static final int IDENTIFIER_TYPE_DAB_DMB_SID_EXT = 14; // 0xe
     field public static final int IDENTIFIER_TYPE_DAB_ENSEMBLE = 6; // 0x6
     field public static final int IDENTIFIER_TYPE_DAB_FREQUENCY = 8; // 0x8
     field public static final int IDENTIFIER_TYPE_DAB_SCID = 7; // 0x7
-    field public static final int IDENTIFIER_TYPE_DAB_SIDECC = 5; // 0x5
-    field public static final int IDENTIFIER_TYPE_DAB_SID_EXT = 5; // 0x5
+    field @Deprecated public static final int IDENTIFIER_TYPE_DAB_SIDECC = 5; // 0x5
+    field @Deprecated public static final int IDENTIFIER_TYPE_DAB_SID_EXT = 5; // 0x5
     field public static final int IDENTIFIER_TYPE_DRMO_FREQUENCY = 10; // 0xa
     field @Deprecated public static final int IDENTIFIER_TYPE_DRMO_MODULATION = 11; // 0xb
     field public static final int IDENTIFIER_TYPE_DRMO_SERVICE_ID = 9; // 0x9
@@ -5316,7 +5317,7 @@
     field @NonNull public static final android.os.Parcelable.Creator<android.hardware.radio.ProgramSelector.Identifier> CREATOR;
   }
 
-  @IntDef(prefix={"IDENTIFIER_TYPE_"}, value={android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_INVALID, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_RDS_PI, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_HD_STATION_ID_EXT, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_HD_SUBCHANNEL, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_HD_STATION_NAME, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DAB_SID_EXT, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DAB_SIDECC, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DAB_ENSEMBLE, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DAB_SCID, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DAB_FREQUENCY, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DRMO_SERVICE_ID, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DRMO_FREQUENCY, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DRMO_MODULATION, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_SXM_SERVICE_ID, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_SXM_CHANNEL}) @IntRange(from=android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_VENDOR_START, to=android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_VENDOR_END) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface ProgramSelector.IdentifierType {
+  @IntDef(prefix={"IDENTIFIER_TYPE_"}, value={android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_INVALID, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_RDS_PI, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_HD_STATION_ID_EXT, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_HD_SUBCHANNEL, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_HD_STATION_NAME, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DAB_SID_EXT, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DAB_SIDECC, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DAB_ENSEMBLE, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DAB_SCID, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DAB_FREQUENCY, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DRMO_SERVICE_ID, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DRMO_FREQUENCY, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DRMO_MODULATION, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_SXM_SERVICE_ID, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_SXM_CHANNEL, android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_DAB_DMB_SID_EXT}) @IntRange(from=android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_VENDOR_START, to=android.hardware.radio.ProgramSelector.IDENTIFIER_TYPE_VENDOR_END) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface ProgramSelector.IdentifierType {
   }
 
   @Deprecated @IntDef(prefix={"PROGRAM_TYPE_"}, value={android.hardware.radio.ProgramSelector.PROGRAM_TYPE_INVALID, android.hardware.radio.ProgramSelector.PROGRAM_TYPE_AM, android.hardware.radio.ProgramSelector.PROGRAM_TYPE_FM, android.hardware.radio.ProgramSelector.PROGRAM_TYPE_AM_HD, android.hardware.radio.ProgramSelector.PROGRAM_TYPE_FM_HD, android.hardware.radio.ProgramSelector.PROGRAM_TYPE_DAB, android.hardware.radio.ProgramSelector.PROGRAM_TYPE_DRMO, android.hardware.radio.ProgramSelector.PROGRAM_TYPE_SXM}) @IntRange(from=android.hardware.radio.ProgramSelector.PROGRAM_TYPE_VENDOR_START, to=android.hardware.radio.ProgramSelector.PROGRAM_TYPE_VENDOR_END) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface ProgramSelector.ProgramType {
@@ -5531,30 +5532,31 @@
 
   public abstract class RadioTuner {
     ctor public RadioTuner();
-    method public abstract int cancel();
-    method public abstract void cancelAnnouncement();
-    method public abstract void close();
-    method @Deprecated public abstract int getConfiguration(android.hardware.radio.RadioManager.BandConfig[]);
-    method @Nullable public android.hardware.radio.ProgramList getDynamicProgramList(@Nullable android.hardware.radio.ProgramList.Filter);
-    method public abstract boolean getMute();
-    method @NonNull public java.util.Map<java.lang.String,java.lang.String> getParameters(@NonNull java.util.List<java.lang.String>);
-    method @Deprecated public abstract int getProgramInformation(android.hardware.radio.RadioManager.ProgramInfo[]);
-    method @Deprecated @NonNull public abstract java.util.List<android.hardware.radio.RadioManager.ProgramInfo> getProgramList(@Nullable java.util.Map<java.lang.String,java.lang.String>);
-    method public abstract boolean hasControl();
-    method @Deprecated public abstract boolean isAnalogForced();
-    method @Deprecated public abstract boolean isAntennaConnected();
-    method public boolean isConfigFlagSet(int);
-    method public boolean isConfigFlagSupported(int);
-    method public abstract int scan(int, boolean);
-    method @Deprecated public abstract void setAnalogForced(boolean);
-    method public void setConfigFlag(int, boolean);
-    method @Deprecated public abstract int setConfiguration(android.hardware.radio.RadioManager.BandConfig);
-    method public abstract int setMute(boolean);
-    method @NonNull public java.util.Map<java.lang.String,java.lang.String> setParameters(@NonNull java.util.Map<java.lang.String,java.lang.String>);
-    method public abstract boolean startBackgroundScan();
-    method public abstract int step(int, boolean);
-    method @Deprecated public abstract int tune(int, int);
-    method public abstract void tune(@NonNull android.hardware.radio.ProgramSelector);
+    method @RequiresPermission(android.Manifest.permission.ACCESS_BROADCAST_RADIO) public abstract int cancel();
+    method @Deprecated @RequiresPermission(android.Manifest.permission.ACCESS_BROADCAST_RADIO) public abstract void cancelAnnouncement();
+    method @RequiresPermission(android.Manifest.permission.ACCESS_BROADCAST_RADIO) public abstract void close();
+    method @Deprecated @RequiresPermission(android.Manifest.permission.ACCESS_BROADCAST_RADIO) public abstract int getConfiguration(android.hardware.radio.RadioManager.BandConfig[]);
+    method @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_BROADCAST_RADIO) public android.hardware.radio.ProgramList getDynamicProgramList(@Nullable android.hardware.radio.ProgramList.Filter);
+    method @RequiresPermission(android.Manifest.permission.ACCESS_BROADCAST_RADIO) public abstract boolean getMute();
+    method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_BROADCAST_RADIO) public java.util.Map<java.lang.String,java.lang.String> getParameters(@NonNull java.util.List<java.lang.String>);
+    method @Deprecated @RequiresPermission(android.Manifest.permission.ACCESS_BROADCAST_RADIO) public abstract int getProgramInformation(android.hardware.radio.RadioManager.ProgramInfo[]);
+    method @Deprecated @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_BROADCAST_RADIO) public abstract java.util.List<android.hardware.radio.RadioManager.ProgramInfo> getProgramList(@Nullable java.util.Map<java.lang.String,java.lang.String>);
+    method @RequiresPermission(android.Manifest.permission.ACCESS_BROADCAST_RADIO) public abstract boolean hasControl();
+    method @Deprecated @RequiresPermission(android.Manifest.permission.ACCESS_BROADCAST_RADIO) public abstract boolean isAnalogForced();
+    method @Deprecated @RequiresPermission(android.Manifest.permission.ACCESS_BROADCAST_RADIO) public abstract boolean isAntennaConnected();
+    method @RequiresPermission(android.Manifest.permission.ACCESS_BROADCAST_RADIO) public boolean isConfigFlagSet(int);
+    method @RequiresPermission(android.Manifest.permission.ACCESS_BROADCAST_RADIO) public boolean isConfigFlagSupported(int);
+    method @Deprecated @RequiresPermission(android.Manifest.permission.ACCESS_BROADCAST_RADIO) public abstract int scan(int, boolean);
+    method @RequiresPermission(android.Manifest.permission.ACCESS_BROADCAST_RADIO) public int seek(int, boolean);
+    method @Deprecated @RequiresPermission(android.Manifest.permission.ACCESS_BROADCAST_RADIO) public abstract void setAnalogForced(boolean);
+    method @RequiresPermission(android.Manifest.permission.ACCESS_BROADCAST_RADIO) public void setConfigFlag(int, boolean);
+    method @Deprecated @RequiresPermission(android.Manifest.permission.ACCESS_BROADCAST_RADIO) public abstract int setConfiguration(android.hardware.radio.RadioManager.BandConfig);
+    method @RequiresPermission(android.Manifest.permission.ACCESS_BROADCAST_RADIO) public abstract int setMute(boolean);
+    method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_BROADCAST_RADIO) public java.util.Map<java.lang.String,java.lang.String> setParameters(@NonNull java.util.Map<java.lang.String,java.lang.String>);
+    method @Deprecated @RequiresPermission(android.Manifest.permission.ACCESS_BROADCAST_RADIO) public abstract boolean startBackgroundScan();
+    method @RequiresPermission(android.Manifest.permission.ACCESS_BROADCAST_RADIO) public abstract int step(int, boolean);
+    method @Deprecated @RequiresPermission(android.Manifest.permission.ACCESS_BROADCAST_RADIO) public abstract int tune(int, int);
+    method @RequiresPermission(android.Manifest.permission.ACCESS_BROADCAST_RADIO) public abstract void tune(@NonNull android.hardware.radio.ProgramSelector);
     field public static final int DIRECTION_DOWN = 1; // 0x1
     field public static final int DIRECTION_UP = 0; // 0x0
     field @Deprecated public static final int ERROR_BACKGROUND_SCAN_FAILED = 6; // 0x6
@@ -5564,6 +5566,14 @@
     field @Deprecated public static final int ERROR_HARDWARE_FAILURE = 0; // 0x0
     field @Deprecated public static final int ERROR_SCAN_TIMEOUT = 3; // 0x3
     field @Deprecated public static final int ERROR_SERVER_DIED = 1; // 0x1
+    field public static final int TUNER_RESULT_CANCELED = 6; // 0x6
+    field public static final int TUNER_RESULT_INTERNAL_ERROR = 1; // 0x1
+    field public static final int TUNER_RESULT_INVALID_ARGUMENTS = 2; // 0x2
+    field public static final int TUNER_RESULT_INVALID_STATE = 3; // 0x3
+    field public static final int TUNER_RESULT_NOT_SUPPORTED = 4; // 0x4
+    field public static final int TUNER_RESULT_OK = 0; // 0x0
+    field public static final int TUNER_RESULT_TIMEOUT = 5; // 0x5
+    field public static final int TUNER_RESULT_UNKNOWN_ERROR = 7; // 0x7
   }
 
   public abstract static class RadioTuner.Callback {
@@ -5571,6 +5581,7 @@
     method public void onAntennaState(boolean);
     method public void onBackgroundScanAvailabilityChange(boolean);
     method public void onBackgroundScanComplete();
+    method public void onConfigFlagUpdated(int, boolean);
     method @Deprecated public void onConfigurationChanged(android.hardware.radio.RadioManager.BandConfig);
     method public void onControlChanged(boolean);
     method public void onEmergencyAnnouncement(boolean);
@@ -10883,6 +10894,7 @@
     field public static final String INSTALL_CARRIER_APP_NOTIFICATION_SLEEP_MILLIS = "install_carrier_app_notification_sleep_millis";
     field public static final String OTA_DISABLE_AUTOMATIC_UPDATE = "ota_disable_automatic_update";
     field public static final String REQUIRE_PASSWORD_TO_DECRYPT = "require_password_to_decrypt";
+    field public static final String SECURE_FRP_MODE = "secure_frp_mode";
     field public static final String TETHER_OFFLOAD_DISABLED = "tether_offload_disabled";
     field public static final String TETHER_SUPPORTED = "tether_supported";
     field public static final String THEATER_MODE_ON = "theater_mode_on";
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 30ff052..a16d4ba 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -83,6 +83,7 @@
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Looper;
+import android.os.OutcomeReceiver;
 import android.os.Parcelable;
 import android.os.PersistableBundle;
 import android.os.Process;
@@ -987,6 +988,17 @@
     /** @hide */
     boolean mIsInPictureInPictureMode;
 
+    /** @hide */
+    @IntDef(prefix = { "FULLSCREEN_REQUEST_" }, value = {
+            FULLSCREEN_MODE_REQUEST_EXIT,
+            FULLSCREEN_MODE_REQUEST_ENTER
+    })
+    public @interface FullscreenModeRequest {}
+
+    public static final int FULLSCREEN_MODE_REQUEST_EXIT = 0;
+
+    public static final int FULLSCREEN_MODE_REQUEST_ENTER = 1;
+
     private boolean mShouldDockBigOverlays;
 
     private UiTranslationController mUiTranslationController;
@@ -3001,6 +3013,36 @@
     }
 
     /**
+     * Request to put the a freeform activity into fullscreen. This will only be allowed if the
+     * activity is on a freeform display, such as a desktop device. The requester has to be the
+     * top-most activity and the request should be a response to a user input. When getting
+     * fullscreen and receiving corresponding {@link #onConfigurationChanged(Configuration)} and
+     * {@link #onMultiWindowModeChanged(boolean, Configuration)}, the activity should relayout
+     * itself and the system bars' visibilities can be controlled as usual fullscreen apps.
+     *
+     * Calling it again with the exit request can restore the activity to the previous status.
+     * This will only happen when it got into fullscreen through this API.
+     *
+     * If an app wants to be in fullscreen always, it should claim as not being resizable
+     * by setting
+     * <a href="https://developer.android.com/guide/topics/large-screens/multi-window-support#resizeableActivity">
+     * {@code android:resizableActivity="false"}</a> instead of calling this API.
+     *
+     * @param request Can be {@link #FULLSCREEN_MODE_REQUEST_ENTER} or
+     *                {@link #FULLSCREEN_MODE_REQUEST_EXIT} to indicate this request is to get
+     *                fullscreen or get restored.
+     * @param approvalCallback Optional callback, use {@code null} when not necessary. When the
+     *                         request is approved or rejected, the callback will be triggered. This
+     *                         will happen before any configuration change. The callback will be
+     *                         dispatched on the main thread.
+     */
+    public void requestFullscreenMode(@NonNull @FullscreenModeRequest int request,
+            @Nullable OutcomeReceiver<Void, Throwable> approvalCallback) {
+        FullscreenRequestHandler.requestFullscreenMode(
+                request, approvalCallback, mCurrentConfig, getActivityToken());
+    }
+
+    /**
      * Specifies a preference to dock big overlays like the expanded picture-in-picture on TV
      * (see {@link PictureInPictureParams.Builder#setExpandedAspectRatio}). Docking puts the
      * big overlay side-by-side next to this activity, so that both windows are fully visible to
diff --git a/core/java/android/app/ActivityClient.java b/core/java/android/app/ActivityClient.java
index 324b8e7..ce99119 100644
--- a/core/java/android/app/ActivityClient.java
+++ b/core/java/android/app/ActivityClient.java
@@ -24,6 +24,7 @@
 import android.content.res.Resources;
 import android.os.Bundle;
 import android.os.IBinder;
+import android.os.IRemoteCallback;
 import android.os.PersistableBundle;
 import android.os.RemoteException;
 import android.util.Singleton;
@@ -372,6 +373,14 @@
         }
     }
 
+    void requestMultiwindowFullscreen(IBinder token, int request, IRemoteCallback callback) {
+        try {
+            getActivityClientController().requestMultiwindowFullscreen(token, request, callback);
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+    }
+
     void startLockTaskModeByToken(IBinder token) {
         try {
             getActivityClientController().startLockTaskModeByToken(token);
diff --git a/core/java/android/app/ActivityTaskManager.java b/core/java/android/app/ActivityTaskManager.java
index 7cfca97..be8f48d 100644
--- a/core/java/android/app/ActivityTaskManager.java
+++ b/core/java/android/app/ActivityTaskManager.java
@@ -62,6 +62,12 @@
     public static final int INVALID_TASK_ID = -1;
 
     /**
+     * Invalid windowing mode.
+     * @hide
+     */
+    public static final int INVALID_WINDOWING_MODE = -1;
+
+    /**
      * Input parameter to {@link IActivityTaskManager#resizeTask} which indicates
      * that the resize doesn't need to preserve the window, and can be skipped if bounds
      * is unchanged. This mode is used by window manager in most cases.
diff --git a/core/java/android/app/FullscreenRequestHandler.java b/core/java/android/app/FullscreenRequestHandler.java
new file mode 100644
index 0000000..52f461d
--- /dev/null
+++ b/core/java/android/app/FullscreenRequestHandler.java
@@ -0,0 +1,123 @@
+/*
+ * 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 android.app;
+
+import static android.app.Activity.FULLSCREEN_MODE_REQUEST_ENTER;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.res.Configuration;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.IRemoteCallback;
+import android.os.OutcomeReceiver;
+
+/**
+ * @hide
+ */
+public class FullscreenRequestHandler {
+    @IntDef(prefix = { "RESULT_" }, value = {
+            RESULT_APPROVED,
+            RESULT_FAILED_NOT_IN_FREEFORM,
+            RESULT_FAILED_NOT_IN_FULLSCREEN_WITH_HISTORY,
+            RESULT_FAILED_NOT_DEFAULT_FREEFORM,
+            RESULT_FAILED_NOT_TOP_FOCUSED
+    })
+    public @interface RequestResult {}
+
+    public static final int RESULT_APPROVED = 0;
+    public static final int RESULT_FAILED_NOT_IN_FREEFORM = 1;
+    public static final int RESULT_FAILED_NOT_IN_FULLSCREEN_WITH_HISTORY = 2;
+    public static final int RESULT_FAILED_NOT_DEFAULT_FREEFORM = 3;
+    public static final int RESULT_FAILED_NOT_TOP_FOCUSED = 4;
+
+    public static final String REMOTE_CALLBACK_RESULT_KEY = "result";
+
+    static void requestFullscreenMode(@NonNull @Activity.FullscreenModeRequest int request,
+            @Nullable OutcomeReceiver<Void, Throwable> approvalCallback, Configuration config,
+            IBinder token) {
+        int earlyCheck = earlyCheckRequestMatchesWindowingMode(
+                request, config.windowConfiguration.getWindowingMode());
+        if (earlyCheck != RESULT_APPROVED) {
+            if (approvalCallback != null) {
+                notifyFullscreenRequestResult(approvalCallback, earlyCheck);
+            }
+            return;
+        }
+        try {
+            if (approvalCallback != null) {
+                ActivityClient.getInstance().requestMultiwindowFullscreen(token, request,
+                        new IRemoteCallback.Stub() {
+                            @Override
+                            public void sendResult(Bundle res) {
+                                notifyFullscreenRequestResult(
+                                        approvalCallback, res.getInt(REMOTE_CALLBACK_RESULT_KEY));
+                            }
+                        });
+            } else {
+                ActivityClient.getInstance().requestMultiwindowFullscreen(token, request, null);
+            }
+        } catch (Throwable e) {
+            if (approvalCallback != null) {
+                approvalCallback.onError(e);
+            }
+        }
+    }
+
+    private static void notifyFullscreenRequestResult(
+            OutcomeReceiver<Void, Throwable> callback, int result) {
+        Throwable e = null;
+        switch (result) {
+            case RESULT_FAILED_NOT_IN_FREEFORM:
+                e = new IllegalStateException("The window is not a freeform window, the request "
+                        + "to get into fullscreen cannot be approved.");
+                break;
+            case RESULT_FAILED_NOT_IN_FULLSCREEN_WITH_HISTORY:
+                e = new IllegalStateException("The window is not in fullscreen by calling the "
+                        + "requestFullscreenMode API before, such that cannot be restored.");
+                break;
+            case RESULT_FAILED_NOT_DEFAULT_FREEFORM:
+                e = new IllegalStateException("The window is not launched in freeform by default.");
+                break;
+            case RESULT_FAILED_NOT_TOP_FOCUSED:
+                e = new IllegalStateException("The window is not the top focused window.");
+                break;
+            default:
+                callback.onResult(null);
+                break;
+        }
+        if (e != null) {
+            callback.onError(e);
+        }
+    }
+
+    private static int earlyCheckRequestMatchesWindowingMode(int request, int windowingMode) {
+        if (request == FULLSCREEN_MODE_REQUEST_ENTER) {
+            if (windowingMode != WINDOWING_MODE_FREEFORM) {
+                return RESULT_FAILED_NOT_IN_FREEFORM;
+            }
+        } else {
+            if (windowingMode != WINDOWING_MODE_FULLSCREEN) {
+                return RESULT_FAILED_NOT_IN_FULLSCREEN_WITH_HISTORY;
+            }
+        }
+        return RESULT_APPROVED;
+    }
+}
diff --git a/core/java/android/app/IActivityClientController.aidl b/core/java/android/app/IActivityClientController.aidl
index 8b655b9..286b84c 100644
--- a/core/java/android/app/IActivityClientController.aidl
+++ b/core/java/android/app/IActivityClientController.aidl
@@ -24,6 +24,7 @@
 import android.content.Intent;
 import android.content.res.Configuration;
 import android.os.Bundle;
+import android.os.IRemoteCallback;
 import android.os.PersistableBundle;
 import android.view.RemoteAnimationDefinition;
 import android.window.SizeConfigurationBuckets;
@@ -102,6 +103,8 @@
     void setPictureInPictureParams(in IBinder token, in PictureInPictureParams params);
     oneway void setShouldDockBigOverlays(in IBinder token, in boolean shouldDockBigOverlays);
     void toggleFreeformWindowingMode(in IBinder token);
+    oneway void requestMultiwindowFullscreen(in IBinder token, in int request,
+            in IRemoteCallback callback);
 
     oneway void startLockTaskModeByToken(in IBinder token);
     oneway void stopLockTaskModeByToken(in IBinder token);
diff --git a/core/java/android/app/IActivityTaskManager.aidl b/core/java/android/app/IActivityTaskManager.aidl
index f20503c..91add27 100644
--- a/core/java/android/app/IActivityTaskManager.aidl
+++ b/core/java/android/app/IActivityTaskManager.aidl
@@ -249,6 +249,11 @@
     /** Returns an interface enabling the management of window organizers. */
     IWindowOrganizerController getWindowOrganizerController();
 
+    /**
+     * Sets whether we are currently in an interactive split screen resize operation where we
+     * are changing the docked stack size.
+     */
+    void setSplitScreenResizing(boolean resizing);
     boolean supportsLocalVoiceInteraction();
 
     // Get device configuration
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 1e6412f..128a872 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -3918,34 +3918,46 @@
     public static @interface MtePolicy {}
 
     /**
-     * Set MTE policy for device. MTE_ENABLED does not necessarily enable MTE if set on a device
-     * that does not support MTE.
-     *
-     * The default policy is MTE_NOT_CONTROLLED_BY_POLICY.
-     *
-     * Memory Tagging Extension (MTE) is a CPU extension that allows to protect against certain
+     * Called by a device owner or profile owner of an organization-owned device to set the Memory
+     * Tagging Extension (MTE) policy. MTE is a CPU extension that allows to protect against certain
      * classes of security problems at a small runtime performance cost overhead.
      *
-     * @param policy the policy to be set
+     * <p>The MTE policy can only be set to {@link #MTE_DISABLED} if called by a device owner.
+     * Otherwise a {@link SecurityException} will be thrown.
+     *
+     * @throws SecurityException if caller is not device owner or profile owner of org-owned device
+     *     or if called on a parent instance
+     * @param policy the MTE policy to be set
      */
     public void setMtePolicy(@MtePolicy int policy) {
-        // TODO(b/244290023): implement
-        // This is SecurityException to temporarily make ParentProfileTest happy.
-        // This is not used.
-        throw new SecurityException("not implemented");
+        throwIfParentInstance("setMtePolicy");
+        if (mService != null) {
+            try {
+                mService.setMtePolicy(policy);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
     }
 
     /**
-     * Get currently set MTE policy. This is not necessarily the same as the state of MTE on the
-     * device, as the device might not support MTE.
+     * Called by a device owner, a profile owner of an organization-owned device or the system to
+     * get the Memory Tagging Extension (MTE) policy
      *
-     * @return the currently set policy
+     * @throws SecurityException if caller is not device owner or profile owner of org-owned device
+     *                           or system uid, or if called on a parent instance
+     * @return the currently set MTE policy
      */
     public @MtePolicy int getMtePolicy() {
-        // TODO(b/244290023): implement
-        // This is SecurityException to temporarily make ParentProfileTest happy.
-        // This is not used.
-        throw new SecurityException("not implemented");
+        throwIfParentInstance("setMtePolicy");
+        if (mService != null) {
+            try {
+                return mService.getMtePolicy();
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+        return MTE_NOT_CONTROLLED_BY_POLICY;
     }
 
     // TODO: Expose this as SystemAPI once we add the query API
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 8a40265..5383dca 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -571,4 +571,7 @@
 
     void setApplicationExemptions(String packageName, in int[]exemptions);
     int[] getApplicationExemptions(String packageName);
+
+    void setMtePolicy(int flag);
+    int getMtePolicy();
 }
diff --git a/core/java/android/companion/virtual/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java
index 57bdf2d..e8180a3 100644
--- a/core/java/android/companion/virtual/VirtualDeviceManager.java
+++ b/core/java/android/companion/virtual/VirtualDeviceManager.java
@@ -776,11 +776,11 @@
 
         private String getVirtualDisplayName() {
             try {
-                // Currently this just use the association ID, which means all of the virtual
-                // displays created using the same virtual device will have the same name. The name
-                // should only be used for informational purposes, and not for identifying the
-                // display in code.
-                return "VirtualDevice_" + mVirtualDevice.getAssociationId();
+                // Currently this just use the device ID, which means all of the virtual displays
+                // created using the same virtual device will have the same name. The name should
+                // only be used for informational purposes, and not for identifying the display in
+                // code.
+                return "VirtualDevice_" + mVirtualDevice.getDeviceId();
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
             }
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index ebc00a7..8aa0454 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -6117,9 +6117,8 @@
     public static final String EXTRA_UID = "android.intent.extra.UID";
 
     /**
-     * @hide String array of package names.
+     * String array of package names.
      */
-    @SystemApi
     public static final String EXTRA_PACKAGES = "android.intent.extra.PACKAGES";
 
     /**
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index 2c28268..84811ea 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -1528,12 +1528,14 @@
      * the application, e.g. the target SDK version.
      * @hide
      */
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public static final int HIDDEN_API_ENFORCEMENT_DEFAULT = -1;
     /**
      * No API enforcement; the app can access the entire internal private API. Only for use by
      * system apps.
      * @hide
      */
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public static final int HIDDEN_API_ENFORCEMENT_DISABLED = 0;
     /**
      * No API enforcement, but enable the detection logic and warnings. Observed behaviour is the
@@ -1541,11 +1543,13 @@
      * APIs are accessed.
      * @hide
      * */
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public static final int HIDDEN_API_ENFORCEMENT_JUST_WARN = 1;
     /**
      * Dark grey list enforcement. Enforces the dark grey and black lists
      * @hide
      */
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public static final int HIDDEN_API_ENFORCEMENT_ENABLED = 2;
 
     private static final int HIDDEN_API_ENFORCEMENT_MIN = HIDDEN_API_ENFORCEMENT_DEFAULT;
diff --git a/core/java/android/content/pm/IPackageInstaller.aidl b/core/java/android/content/pm/IPackageInstaller.aidl
index 1e928bd..115d4b0 100644
--- a/core/java/android/content/pm/IPackageInstaller.aidl
+++ b/core/java/android/content/pm/IPackageInstaller.aidl
@@ -69,4 +69,7 @@
     void setSilentUpdatesThrottleTime(long throttleTimeInSeconds);
     void checkInstallConstraints(String installerPackageName, in List<String> packageNames,
             in PackageInstaller.InstallConstraints constraints, in RemoteCallback callback);
+    void waitForInstallConstraints(String installerPackageName, in List<String> packageNames,
+            in PackageInstaller.InstallConstraints constraints, in IntentSender callback,
+            long timeout);
 }
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index 1f01ae9..f17d8fa 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -30,6 +30,7 @@
 import android.Manifest;
 import android.annotation.CallbackExecutor;
 import android.annotation.CurrentTimeMillisLong;
+import android.annotation.DurationMillisLong;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -248,6 +249,24 @@
      */
     public static final String EXTRA_STORAGE_PATH = "android.content.pm.extra.STORAGE_PATH";
 
+    /**
+     * The {@link InstallConstraints} object.
+     *
+     * @see Intent#getParcelableExtra(String, Class)
+     * @see #waitForInstallConstraints(List, InstallConstraints, IntentSender, long)
+     */
+    public static final String EXTRA_INSTALL_CONSTRAINTS =
+            "android.content.pm.extra.INSTALL_CONSTRAINTS";
+
+    /**
+     * The {@link InstallConstraintsResult} object.
+     *
+     * @see Intent#getParcelableExtra(String, Class)
+     * @see #waitForInstallConstraints(List, InstallConstraints, IntentSender, long)
+     */
+    public static final String EXTRA_INSTALL_CONSTRAINTS_RESULT =
+            "android.content.pm.extra.INSTALL_CONSTRAINTS_RESULT";
+
     /** {@hide} */
     @Deprecated
     public static final String EXTRA_PACKAGE_NAMES = "android.content.pm.extra.PACKAGE_NAMES";
@@ -885,6 +904,32 @@
     }
 
     /**
+     * Similar to {@link #checkInstallConstraints(List, InstallConstraints, Executor, Consumer)},
+     * but the callback is invoked only when the constraints are satisfied or after timeout.
+     *
+     * @param callback Called when the constraints are satisfied or after timeout.
+     *                 Intents sent to this callback contain:
+     *                 {@link Intent#EXTRA_PACKAGES} for the input package names,
+     *                 {@link #EXTRA_INSTALL_CONSTRAINTS} for the input constraints,
+     *                 {@link #EXTRA_INSTALL_CONSTRAINTS_RESULT} for the result.
+     * @param timeoutMillis The maximum time to wait, in milliseconds until the constraints are
+     *                      satisfied. Valid range is from 0 to one week. {@code 0} means the
+     *                      callback will be invoked immediately no matter constraints are
+     *                      satisfied or not.
+     */
+    public void waitForInstallConstraints(@NonNull List<String> packageNames,
+            @NonNull InstallConstraints constraints,
+            @NonNull IntentSender callback,
+            @DurationMillisLong long timeoutMillis) {
+        try {
+            mInstaller.waitForInstallConstraints(
+                    mInstallerPackageName, packageNames, constraints, callback, timeoutMillis);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Events for observing session lifecycle.
      * <p>
      * A typical session lifecycle looks like this:
@@ -3807,7 +3852,7 @@
      * Note the constraints are applied transitively. If app Foo is used by app Bar (via shared
      * library or bounded service), the constraints will also be applied to Bar.
      */
-    @DataClass(genParcelable = true, genHiddenConstructor = true)
+    @DataClass(genParcelable = true, genHiddenConstructor = true, genEqualsHashCode=true)
     public static final class InstallConstraints implements Parcelable {
         /**
          * Preset constraints suitable for gentle update.
@@ -3968,6 +4013,41 @@
 
         @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(InstallConstraints other) { ... }
+            // boolean fieldNameEquals(FieldType otherValue) { ... }
+
+            if (this == o) return true;
+            if (o == null || getClass() != o.getClass()) return false;
+            @SuppressWarnings("unchecked")
+            InstallConstraints that = (InstallConstraints) o;
+            //noinspection PointlessBooleanExpression
+            return true
+                    && mRequireDeviceIdle == that.mRequireDeviceIdle
+                    && mRequireAppNotForeground == that.mRequireAppNotForeground
+                    && mRequireAppNotInteracting == that.mRequireAppNotInteracting
+                    && mRequireAppNotTopVisible == that.mRequireAppNotTopVisible
+                    && mRequireNotInCall == that.mRequireNotInCall;
+        }
+
+        @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 + Boolean.hashCode(mRequireDeviceIdle);
+            _hash = 31 * _hash + Boolean.hashCode(mRequireAppNotForeground);
+            _hash = 31 * _hash + Boolean.hashCode(mRequireAppNotInteracting);
+            _hash = 31 * _hash + Boolean.hashCode(mRequireAppNotTopVisible);
+            _hash = 31 * _hash + Boolean.hashCode(mRequireNotInCall);
+            return _hash;
+        }
+
+        @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) { ... }
@@ -4023,10 +4103,10 @@
         };
 
         @DataClass.Generated(
-                time = 1668650523752L,
+                time = 1670207178734L,
                 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 mRequireDeviceIdle\nprivate final  boolean mRequireAppNotForeground\nprivate final  boolean mRequireAppNotInteracting\nprivate final  boolean mRequireAppNotTopVisible\nprivate final  boolean mRequireNotInCall\nclass InstallConstraints extends java.lang.Object implements [android.os.Parcelable]\nprivate  boolean mRequireDeviceIdle\nprivate  boolean mRequireAppNotForeground\nprivate  boolean mRequireAppNotInteracting\nprivate  boolean mRequireAppNotTopVisible\nprivate  boolean mRequireNotInCall\npublic @android.annotation.SuppressLint @android.annotation.NonNull android.content.pm.PackageInstaller.InstallConstraints.Builder requireDeviceIdle()\npublic @android.annotation.SuppressLint @android.annotation.NonNull android.content.pm.PackageInstaller.InstallConstraints.Builder requireAppNotForeground()\npublic @android.annotation.SuppressLint @android.annotation.NonNull android.content.pm.PackageInstaller.InstallConstraints.Builder requireAppNotInteracting()\npublic @android.annotation.SuppressLint @android.annotation.NonNull android.content.pm.PackageInstaller.InstallConstraints.Builder requireAppNotTopVisible()\npublic @android.annotation.SuppressLint @android.annotation.NonNull android.content.pm.PackageInstaller.InstallConstraints.Builder requireNotInCall()\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)")
+                inputSignatures = "public static final @android.annotation.NonNull android.content.pm.PackageInstaller.InstallConstraints GENTLE_UPDATE\nprivate final  boolean mRequireDeviceIdle\nprivate final  boolean mRequireAppNotForeground\nprivate final  boolean mRequireAppNotInteracting\nprivate final  boolean mRequireAppNotTopVisible\nprivate final  boolean mRequireNotInCall\nclass InstallConstraints extends java.lang.Object implements [android.os.Parcelable]\nprivate  boolean mRequireDeviceIdle\nprivate  boolean mRequireAppNotForeground\nprivate  boolean mRequireAppNotInteracting\nprivate  boolean mRequireAppNotTopVisible\nprivate  boolean mRequireNotInCall\npublic @android.annotation.SuppressLint @android.annotation.NonNull android.content.pm.PackageInstaller.InstallConstraints.Builder requireDeviceIdle()\npublic @android.annotation.SuppressLint @android.annotation.NonNull android.content.pm.PackageInstaller.InstallConstraints.Builder requireAppNotForeground()\npublic @android.annotation.SuppressLint @android.annotation.NonNull android.content.pm.PackageInstaller.InstallConstraints.Builder requireAppNotInteracting()\npublic @android.annotation.SuppressLint @android.annotation.NonNull android.content.pm.PackageInstaller.InstallConstraints.Builder requireAppNotTopVisible()\npublic @android.annotation.SuppressLint @android.annotation.NonNull android.content.pm.PackageInstaller.InstallConstraints.Builder requireNotInCall()\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)")
         @Deprecated
         private void __metadata() {}
 
diff --git a/core/java/android/hardware/camera2/CameraExtensionSession.java b/core/java/android/hardware/camera2/CameraExtensionSession.java
index 6f895d5..b0fafea 100644
--- a/core/java/android/hardware/camera2/CameraExtensionSession.java
+++ b/core/java/android/hardware/camera2/CameraExtensionSession.java
@@ -18,6 +18,11 @@
 
 import android.annotation.IntRange;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.hardware.camera2.impl.PublicKey;
+import android.hardware.camera2.utils.TypeReference;
+import android.util.Pair;
+import android.util.Range;
 
 import java.util.concurrent.Executor;
 
@@ -424,6 +429,28 @@
     }
 
     /**
+     * Return the realtime still {@link #capture} latency.
+     *
+     * <p>The pair will be in milliseconds with the first value indicating the capture latency from
+     * the {@link ExtensionCaptureCallback#onCaptureStarted} until
+     * {@link ExtensionCaptureCallback#onCaptureProcessStarted}
+     * and the second value containing the estimated post-processing latency from
+     * {@link ExtensionCaptureCallback#onCaptureProcessStarted} until the processed frame returns
+     * to the client.</p>
+     *
+     * <p>The estimations will take into account the current environment conditions, the camera
+     * state and will include the time spent processing the multi-frame capture request along with
+     * any additional time for encoding of the processed buffer if necessary.</p>
+     *
+     * @return The realtime still capture latency,
+     * or {@code null} if the estimation is not supported.
+     */
+    @Nullable
+    public Pair<Long, Long> getRealtimeStillCaptureLatency() throws CameraAccessException {
+        throw new UnsupportedOperationException("Subclasses must override this method");
+    }
+
+    /**
      * Close this capture session asynchronously.
      *
      * <p>Closing a session frees up the target output Surfaces of the session
diff --git a/core/java/android/hardware/camera2/extension/IImageCaptureExtenderImpl.aidl b/core/java/android/hardware/camera2/extension/IImageCaptureExtenderImpl.aidl
index 615536b..360f809 100644
--- a/core/java/android/hardware/camera2/extension/IImageCaptureExtenderImpl.aidl
+++ b/core/java/android/hardware/camera2/extension/IImageCaptureExtenderImpl.aidl
@@ -19,6 +19,7 @@
 
 import android.hardware.camera2.extension.CaptureStageImpl;
 import android.hardware.camera2.extension.ICaptureProcessorImpl;
+import android.hardware.camera2.extension.LatencyPair;
 import android.hardware.camera2.extension.LatencyRange;
 import android.hardware.camera2.extension.Size;
 import android.hardware.camera2.extension.SizeList;
@@ -43,4 +44,5 @@
     CameraMetadataNative getAvailableCaptureRequestKeys();
     CameraMetadataNative getAvailableCaptureResultKeys();
     boolean isCaptureProcessProgressAvailable();
+    @nullable LatencyPair getRealtimeCaptureLatency();
 }
diff --git a/core/java/android/hardware/camera2/extension/ISessionProcessorImpl.aidl b/core/java/android/hardware/camera2/extension/ISessionProcessorImpl.aidl
index 0eca5a7..e0f1b64 100644
--- a/core/java/android/hardware/camera2/extension/ISessionProcessorImpl.aidl
+++ b/core/java/android/hardware/camera2/extension/ISessionProcessorImpl.aidl
@@ -19,6 +19,8 @@
 import android.hardware.camera2.extension.CameraSessionConfig;
 import android.hardware.camera2.extension.ICaptureCallback;
 import android.hardware.camera2.extension.IRequestProcessorImpl;
+import android.hardware.camera2.extension.LatencyPair;
+import android.hardware.camera2.extension.LatencyRange;
 import android.hardware.camera2.extension.OutputSurface;
 
 /** @hide */
@@ -34,4 +36,5 @@
     int startCapture(in ICaptureCallback callback);
     void setParameters(in CaptureRequest captureRequest);
     int startTrigger(in CaptureRequest captureRequest, in ICaptureCallback callback);
+    @nullable LatencyPair getRealtimeCaptureLatency();
 }
diff --git a/core/java/android/hardware/camera2/extension/LatencyPair.aidl b/core/java/android/hardware/camera2/extension/LatencyPair.aidl
new file mode 100644
index 0000000..5174f1d
--- /dev/null
+++ b/core/java/android/hardware/camera2/extension/LatencyPair.aidl
@@ -0,0 +1,23 @@
+/**
+ * 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 android.hardware.camera2.extension;
+
+/** @hide */
+parcelable LatencyPair
+{
+    long first;
+    long second;
+}
diff --git a/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
index 3a8dc03..42c4411 100644
--- a/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
@@ -43,6 +43,7 @@
 import android.hardware.camera2.extension.IRequestCallback;
 import android.hardware.camera2.extension.IRequestProcessorImpl;
 import android.hardware.camera2.extension.ISessionProcessorImpl;
+import android.hardware.camera2.extension.LatencyPair;
 import android.hardware.camera2.extension.OutputConfigId;
 import android.hardware.camera2.extension.OutputSurface;
 import android.hardware.camera2.extension.ParcelCaptureResult;
@@ -61,6 +62,8 @@
 import android.os.HandlerThread;
 import android.os.RemoteException;
 import android.util.Log;
+import android.util.Pair;
+import android.util.Range;
 import android.util.Size;
 import android.view.Surface;
 
@@ -329,6 +332,28 @@
     }
 
     @Override
+    public Pair<Long, Long> getRealtimeStillCaptureLatency() throws CameraAccessException {
+        synchronized (mInterfaceLock) {
+            if (!mInitialized) {
+                throw new IllegalStateException("Uninitialized component");
+            }
+
+            try {
+                LatencyPair latency = mSessionProcessor.getRealtimeCaptureLatency();
+                if (latency != null) {
+                    return new Pair<>(latency.first, latency.second);
+                }
+
+                return null;
+            } catch (RemoteException e) {
+                Log.e(TAG, "Failed to query realtime latency! Extension service does not "
+                        + "respond");
+                throw new CameraAccessException(CameraAccessException.CAMERA_ERROR);
+            }
+        }
+    }
+
+    @Override
     public int setRepeatingRequest(@NonNull CaptureRequest request, @NonNull Executor executor,
             @NonNull ExtensionCaptureCallback listener) throws CameraAccessException {
         int seqId = -1;
diff --git a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
index 389c214..259bd7b 100644
--- a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
@@ -43,6 +43,7 @@
 import android.hardware.camera2.extension.IPreviewExtenderImpl;
 import android.hardware.camera2.extension.IProcessResultImpl;
 import android.hardware.camera2.extension.IRequestUpdateProcessorImpl;
+import android.hardware.camera2.extension.LatencyPair;
 import android.hardware.camera2.extension.ParcelImage;
 import android.hardware.camera2.params.DynamicRangeProfiles;
 import android.hardware.camera2.params.ExtensionSessionConfiguration;
@@ -59,6 +60,7 @@
 import android.util.Log;
 import android.util.LongSparseArray;
 import android.util.Pair;
+import android.util.Range;
 import android.util.Size;
 import android.view.Surface;
 
@@ -466,6 +468,28 @@
     }
 
     @Override
+    public Pair<Long, Long> getRealtimeStillCaptureLatency() throws CameraAccessException {
+        synchronized (mInterfaceLock) {
+            if (!mInitialized) {
+                throw new IllegalStateException("Uninitialized component");
+            }
+
+            try {
+                LatencyPair latency = mImageExtender.getRealtimeCaptureLatency();
+                if (latency != null) {
+                    return new Pair<>(latency.first, latency.second);
+                }
+
+                return null;
+            } catch (RemoteException e) {
+                Log.e(TAG, "Failed to query realtime latency! Extension service does not "
+                        + "respond");
+                throw new CameraAccessException(CameraAccessException.CAMERA_ERROR);
+            }
+        }
+    }
+
+    @Override
     public int setRepeatingRequest(@NonNull CaptureRequest request,
                                    @NonNull Executor executor,
                                    @NonNull ExtensionCaptureCallback listener)
diff --git a/core/java/android/hardware/display/DisplayManagerInternal.java b/core/java/android/hardware/display/DisplayManagerInternal.java
index 829908f..7409187 100644
--- a/core/java/android/hardware/display/DisplayManagerInternal.java
+++ b/core/java/android/hardware/display/DisplayManagerInternal.java
@@ -439,9 +439,6 @@
         // 1 (brighter). Set to Float.NaN if there's no override.
         public float screenAutoBrightnessAdjustmentOverride;
 
-        // If true, enables automatic brightness control.
-        public boolean useAutoBrightness;
-
         // If true, scales the brightness to a fraction of desired (as defined by
         // screenLowPowerBrightnessFactor).
         public boolean lowPowerMode;
@@ -471,7 +468,6 @@
             policy = POLICY_BRIGHT;
             useProximitySensor = false;
             screenBrightnessOverride = PowerManager.BRIGHTNESS_INVALID_FLOAT;
-            useAutoBrightness = false;
             screenAutoBrightnessAdjustmentOverride = Float.NaN;
             screenLowPowerBrightnessFactor = 0.5f;
             blockScreenOn = false;
@@ -491,7 +487,6 @@
             policy = other.policy;
             useProximitySensor = other.useProximitySensor;
             screenBrightnessOverride = other.screenBrightnessOverride;
-            useAutoBrightness = other.useAutoBrightness;
             screenAutoBrightnessAdjustmentOverride = other.screenAutoBrightnessAdjustmentOverride;
             screenLowPowerBrightnessFactor = other.screenLowPowerBrightnessFactor;
             blockScreenOn = other.blockScreenOn;
@@ -513,7 +508,6 @@
                     && useProximitySensor == other.useProximitySensor
                     && floatEquals(screenBrightnessOverride,
                             other.screenBrightnessOverride)
-                    && useAutoBrightness == other.useAutoBrightness
                     && floatEquals(screenAutoBrightnessAdjustmentOverride,
                             other.screenAutoBrightnessAdjustmentOverride)
                     && screenLowPowerBrightnessFactor
@@ -539,7 +533,6 @@
             return "policy=" + policyToString(policy)
                     + ", useProximitySensor=" + useProximitySensor
                     + ", screenBrightnessOverride=" + screenBrightnessOverride
-                    + ", useAutoBrightness=" + useAutoBrightness
                     + ", screenAutoBrightnessAdjustmentOverride="
                     + screenAutoBrightnessAdjustmentOverride
                     + ", screenLowPowerBrightnessFactor=" + screenLowPowerBrightnessFactor
diff --git a/core/java/android/hardware/radio/IRadioService.aidl b/core/java/android/hardware/radio/IRadioService.aidl
index 9349cf7..c7131a7 100644
--- a/core/java/android/hardware/radio/IRadioService.aidl
+++ b/core/java/android/hardware/radio/IRadioService.aidl
@@ -31,7 +31,7 @@
     List<RadioManager.ModuleProperties> listModules();
 
     ITuner openTuner(int moduleId, in RadioManager.BandConfig bandConfig, boolean withAudio,
-            in ITunerCallback callback);
+            in ITunerCallback callback, int targetSdkVersion);
 
     ICloseHandle addAnnouncementListener(in int[] enabledTypes,
             in IAnnouncementListener listener);
diff --git a/core/java/android/hardware/radio/ITuner.aidl b/core/java/android/hardware/radio/ITuner.aidl
index 7bf234b..e68c3cc 100644
--- a/core/java/android/hardware/radio/ITuner.aidl
+++ b/core/java/android/hardware/radio/ITuner.aidl
@@ -49,7 +49,7 @@
     /**
      * @throws IllegalStateException if called out of sequence
      */
-    void scan(boolean directionDown, boolean skipSubChannel);
+    void seek(boolean directionDown, boolean skipSubChannel);
 
     /**
      * @throws IllegalArgumentException if invalid arguments are passed
diff --git a/core/java/android/hardware/radio/ITunerCallback.aidl b/core/java/android/hardware/radio/ITunerCallback.aidl
index f98947b..13092cc 100644
--- a/core/java/android/hardware/radio/ITunerCallback.aidl
+++ b/core/java/android/hardware/radio/ITunerCallback.aidl
@@ -24,6 +24,13 @@
 /** {@hide} */
 oneway interface ITunerCallback {
     void onError(int status);
+
+    /**
+     * Callback called when tuning operations, such as tune, step, seek, failed.
+     *
+     * @param result Tuning result of {@link RadioTuner#TunerResultType} type.
+     * @param selector Program selector used for the tuning operation.
+     */
     void onTuneFailed(int result, in ProgramSelector selector);
     void onConfigurationChanged(in RadioManager.BandConfig config);
     void onCurrentProgramInfoChanged(in RadioManager.ProgramInfo info);
@@ -36,6 +43,18 @@
     void onProgramListUpdated(in ProgramList.Chunk chunk);
 
     /**
+     * Callback for passing updates to config flags from {@link IRadioService} to
+     * {@link RadioTuner}.
+     *
+     * @param flag Config flag (defined in {@link RadioManager.ConfigFlag}) updated
+     * @param value Updated value for the config flag
+     */
+    void onConfigFlagUpdated(int flag, boolean value);
+
+    /**
+     * Callback for passing updates to vendor-specific parameter values from
+     * {@link IRadioService} to {@link RadioTuner}.
+     *
      * @param parameters Vendor-specific key-value pairs
      */
     void onParametersUpdated(in Map<String, String> parameters);
diff --git a/core/java/android/hardware/radio/ProgramSelector.java b/core/java/android/hardware/radio/ProgramSelector.java
index 8a92135..7faa285 100644
--- a/core/java/android/hardware/radio/ProgramSelector.java
+++ b/core/java/android/hardware/radio/ProgramSelector.java
@@ -167,7 +167,10 @@
     public static final int IDENTIFIER_TYPE_HD_STATION_NAME = 10004;
     /**
      * @see {@link IDENTIFIER_TYPE_DAB_SID_EXT}
+     *
+     * @deprecated use {@link #IDENTIFIER_TYPE_DAB_DMB_SID_EXT} instead
      */
+    @Deprecated
     public static final int IDENTIFIER_TYPE_DAB_SIDECC = 5;
     /**
      * 28bit compound primary identifier for Digital Audio Broadcasting.
@@ -183,7 +186,10 @@
      *
      * The remaining bits should be set to zeros when writing on the chip side
      * and ignored when read.
+     *
+     * @deprecated use {@link #IDENTIFIER_TYPE_DAB_DMB_SID_EXT} instead
      */
+    @Deprecated
     public static final int IDENTIFIER_TYPE_DAB_SID_EXT = IDENTIFIER_TYPE_DAB_SIDECC;
     /** 16bit */
     public static final int IDENTIFIER_TYPE_DAB_ENSEMBLE = 6;
@@ -197,7 +203,7 @@
     public static final int IDENTIFIER_TYPE_DRMO_FREQUENCY = 10;
     /**
      * 1: AM, 2:FM
-     * @deprecated use {@link IDENTIFIER_TYPE_DRMO_FREQUENCY} instead
+     * @deprecated use {@link #IDENTIFIER_TYPE_DRMO_FREQUENCY} instead
      */
     @Deprecated
     public static final int IDENTIFIER_TYPE_DRMO_MODULATION = 11;
@@ -206,6 +212,23 @@
     /** 0-999 range */
     public static final int IDENTIFIER_TYPE_SXM_CHANNEL = 13;
     /**
+     * 44bit compound primary identifier for Digital Audio Broadcasting and
+     * Digital Multimedia Broadcasting.
+     *
+     * <p>Consists of (from the LSB):
+     * - 32bit: SId;
+     * - 8bit: ECC code;
+     * - 4bit: SCIdS.
+     *
+     * <p>SCIdS (Service Component Identifier within the Service) value
+     * of 0 represents the main service, while 1 and above represents
+     * secondary services.
+     *
+     * The remaining bits should be set to zeros when writing on the chip side
+     * and ignored when read.
+     */
+    public static final int IDENTIFIER_TYPE_DAB_DMB_SID_EXT = 14;
+    /**
      * Primary identifier for vendor-specific radio technology.
      * The value format is determined by a vendor.
      *
@@ -219,12 +242,12 @@
      */
     public static final int IDENTIFIER_TYPE_VENDOR_END = PROGRAM_TYPE_VENDOR_END;
     /**
-     * @deprecated use {@link IDENTIFIER_TYPE_VENDOR_START} instead
+     * @deprecated use {@link #IDENTIFIER_TYPE_VENDOR_START} instead
      */
     @Deprecated
     public static final int IDENTIFIER_TYPE_VENDOR_PRIMARY_START = IDENTIFIER_TYPE_VENDOR_START;
     /**
-     * @deprecated use {@link IDENTIFIER_TYPE_VENDOR_END} instead
+     * @deprecated use {@link #IDENTIFIER_TYPE_VENDOR_END} instead
      */
     @Deprecated
     public static final int IDENTIFIER_TYPE_VENDOR_PRIMARY_END = IDENTIFIER_TYPE_VENDOR_END;
@@ -245,6 +268,7 @@
         IDENTIFIER_TYPE_DRMO_MODULATION,
         IDENTIFIER_TYPE_SXM_SERVICE_ID,
         IDENTIFIER_TYPE_SXM_CHANNEL,
+        IDENTIFIER_TYPE_DAB_DMB_SID_EXT,
     })
     @IntRange(from = IDENTIFIER_TYPE_VENDOR_START, to = IDENTIFIER_TYPE_VENDOR_END)
     @Retention(RetentionPolicy.SOURCE)
@@ -285,7 +309,7 @@
      * Type of a radio technology.
      *
      * @return program type.
-     * @deprecated use {@link getPrimaryId} instead
+     * @deprecated use {@link #getPrimaryId} instead
      */
     @Deprecated
     public @ProgramType int getProgramType() {
diff --git a/core/java/android/hardware/radio/RadioManager.java b/core/java/android/hardware/radio/RadioManager.java
index 9a217f9..f072e3b 100644
--- a/core/java/android/hardware/radio/RadioManager.java
+++ b/core/java/android/hardware/radio/RadioManager.java
@@ -82,6 +82,24 @@
     /** Method return status: time out before operation completion */
     public static final int STATUS_TIMED_OUT = -110;
 
+    /**
+     *  Radio operation status types
+     *
+     * @hide
+     */
+    @IntDef(prefix = { "STATUS_" }, value = {
+            STATUS_OK,
+            STATUS_ERROR,
+            STATUS_PERMISSION_DENIED,
+            STATUS_NO_INIT,
+            STATUS_BAD_VALUE,
+            STATUS_DEAD_OBJECT,
+            STATUS_INVALID_OPERATION,
+            STATUS_TIMED_OUT,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface RadioStatusType{}
+
 
     // keep in sync with radio_class_t in /system/core/incluse/system/radio.h
     /** Radio module class supporting FM (including HD radio) and AM */
@@ -330,6 +348,7 @@
          * program list.
          * @return the number of audio sources available.
          */
+        @RadioStatusType
         public int getNumAudioSources() {
             return mNumAudioSources;
         }
@@ -1724,6 +1743,7 @@
      * </ul>
      */
     @RequiresPermission(Manifest.permission.ACCESS_BROADCAST_RADIO)
+    @RadioStatusType
     public int listModules(List<ModuleProperties> modules) {
         if (modules == null) {
             Log.e(TAG, "the output list must not be empty");
@@ -1776,7 +1796,7 @@
         ITuner tuner;
         TunerCallbackAdapter halCallback = new TunerCallbackAdapter(callback, handler);
         try {
-            tuner = mService.openTuner(moduleId, config, withAudio, halCallback);
+            tuner = mService.openTuner(moduleId, config, withAudio, halCallback, mTargetSdkVersion);
         } catch (RemoteException | IllegalArgumentException | IllegalStateException ex) {
             Log.e(TAG, "Failed to open tuner", ex);
             return null;
@@ -1853,6 +1873,7 @@
 
     @NonNull private final Context mContext;
     @NonNull private final IRadioService mService;
+    private final int mTargetSdkVersion;
 
     /**
      * @hide
@@ -1869,5 +1890,6 @@
     public RadioManager(Context context, IRadioService service) {
         mContext = context;
         mService = service;
+        mTargetSdkVersion = mContext.getApplicationInfo().targetSdkVersion;
     }
 }
diff --git a/core/java/android/hardware/radio/RadioTuner.java b/core/java/android/hardware/radio/RadioTuner.java
index 969db96..9b2bcde 100644
--- a/core/java/android/hardware/radio/RadioTuner.java
+++ b/core/java/android/hardware/radio/RadioTuner.java
@@ -16,14 +16,20 @@
 
 package android.hardware.radio;
 
+import android.Manifest;
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
 import android.graphics.Bitmap;
 import android.os.Handler;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.List;
 import java.util.Map;
+import java.util.concurrent.Executor;
 
 /**
  * RadioTuner interface provides methods to control a radio tuner on the device: selecting and
@@ -43,16 +49,21 @@
     public static final int DIRECTION_DOWN    = 1;
 
     /**
-     * Close the tuner interface. The {@link Callback} callback will not be called
-     * anymore and associated resources will be released.
-     * Must be called when the tuner is not needed to make hardware resources available to others.
+     * Close the tuner interface.
+     *
+     * <p>The {@link Callback} callback will not be called anymore and associated resources will be
+     * released. Must be called when the tuner is not needed to make hardware resources available
+     * to others.
      * */
+    @RequiresPermission(Manifest.permission.ACCESS_BROADCAST_RADIO)
     public abstract void close();
 
     /**
      * Set the active band configuration for this module.
-     * Must be a valid configuration obtained via buildConfig() from a valid BandDescriptor listed
-     * in the ModuleProperties of the module with the specified ID.
+     *
+     * <p>Must be a valid configuration obtained via buildConfig() from a valid BandDescriptor
+     * listed in the ModuleProperties of the module with the specified ID.
+     *
      * @param config The desired band configuration (FmBandConfig or AmBandConfig).
      * @return
      * <ul>
@@ -67,10 +78,13 @@
      * @deprecated Only applicable for HAL 1.x.
      */
     @Deprecated
+    @RequiresPermission(Manifest.permission.ACCESS_BROADCAST_RADIO)
+    @RadioManager.RadioStatusType
     public abstract int setConfiguration(RadioManager.BandConfig config);
 
     /**
      * Get current configuration.
+     *
      * @param config a BandConfig array of lengh 1 where the configuration is returned.
      * @return
      * <ul>
@@ -86,11 +100,15 @@
      * @deprecated Only applicable for HAL 1.x.
      */
     @Deprecated
+    @RequiresPermission(Manifest.permission.ACCESS_BROADCAST_RADIO)
+    @RadioManager.RadioStatusType
     public abstract int getConfiguration(RadioManager.BandConfig[] config);
 
 
     /**
-     * Set mute state. When muted, the radio tuner audio source is not available for playback on
+     * Set mute state.
+     *
+     * <p>When muted, the radio tuner audio source is not available for playback on
      * any audio device. when unmuted, the radio tuner audio source is output as a media source
      * and renderd over the audio device selected for media use case.
      * The radio tuner audio source is muted by default when the tuner is first attached.
@@ -107,6 +125,8 @@
      *  service fails, </li>
      * </ul>
      */
+    @RequiresPermission(Manifest.permission.ACCESS_BROADCAST_RADIO)
+    @RadioManager.RadioStatusType
     public abstract int setMute(boolean mute);
 
     /**
@@ -115,13 +135,19 @@
      * @return {@code true} if the radio tuner audio source is muted or a problem occured
      * retrieving the mute state, {@code false} otherwise.
      */
+    @RequiresPermission(Manifest.permission.ACCESS_BROADCAST_RADIO)
     public abstract boolean getMute();
 
     /**
      * Step up or down by one channel spacing.
-     * The operation is asynchronous and {@link Callback}
-     * onProgramInfoChanged() will be called when step completes or
-     * onError() when cancelled or timeout.
+     *
+     * <p>The operation is asynchronous and {@link Callback#onProgramInfoChanged}
+     * will be called when step completes or {@link Callback#onTuneFailed}
+     * when timeout or canceled.
+     *
+     * <p>When this operation is called by users other than current user or system user, it is
+     * ignored silently.
+     *
      * @param direction {@link #DIRECTION_UP} or {@link #DIRECTION_DOWN}.
      * @param skipSubChannel indicates to skip sub channels when the configuration currently
      * selected supports sub channel (e.g HD Radio). N/A otherwise.
@@ -136,13 +162,50 @@
      *  service fails, </li>
      * </ul>
      */
+    @RequiresPermission(Manifest.permission.ACCESS_BROADCAST_RADIO)
+    @RadioManager.RadioStatusType
     public abstract int step(int direction, boolean skipSubChannel);
 
     /**
      * Scan up or down to next valid station.
-     * The operation is asynchronous and {@link Callback}
-     * onProgramInfoChanged() will be called when scan completes or
-     * onError() when cancelled or timeout.
+     *
+     * <p>The operation is asynchronous and {@link Callback#onProgramInfoChanged}
+     * will be called when scan completes or {@link Callback#onTuneFailed}
+     * when timeout or canceled.
+     *
+     * <p>When this operation is called by users other than current user or system user, it is
+     * ignored silently.
+     *
+     * @param direction {@link #DIRECTION_UP} or {@link #DIRECTION_DOWN}.
+     * @param skipSubChannel indicates to skip sub channels when the configuration currently
+     * selected supports sub channel (e.g HD Radio). N/A otherwise.
+     * @return
+     * <ul>
+     *  <li>{@link RadioManager#STATUS_OK} in case of success, </li>
+     *  <li>{@link RadioManager#STATUS_ERROR} in case of unspecified error, </li>
+     *  <li>{@link RadioManager#STATUS_NO_INIT} if the native service cannot be reached, </li>
+     *  <li>{@link RadioManager#STATUS_BAD_VALUE} if parameters are invalid, </li>
+     *  <li>{@link RadioManager#STATUS_INVALID_OPERATION} if the call is out of sequence, </li>
+     *  <li>{@link RadioManager#STATUS_DEAD_OBJECT} if the binder transaction to the native
+     *  service fails, </li>
+     * </ul>
+     * @deprecated Use {@link #seek(int, boolean)} instead.
+     */
+    @Deprecated
+    @RequiresPermission(Manifest.permission.ACCESS_BROADCAST_RADIO)
+    @RadioManager.RadioStatusType
+    public abstract int scan(int direction, boolean skipSubChannel);
+
+    /**
+     * Seek up or down to next valid station.
+     *
+     * <p>The operation is asynchronous and {@link Callback#onProgramInfoChanged}
+     * will be called when seek completes or {@link Callback#onTuneFailed}
+     * when timeout or canceled.
+     *
+     * <p>When this operation is called by users other than current user or system user, it is
+     * ignore silently.
+     *
      * @param direction {@link #DIRECTION_UP} or {@link #DIRECTION_DOWN}.
      * @param skipSubChannel indicates to skip sub channels when the configuration currently
      * selected supports sub channel (e.g HD Radio). N/A otherwise.
@@ -157,13 +220,22 @@
      *  service fails, </li>
      * </ul>
      */
-    public abstract int scan(int direction, boolean skipSubChannel);
+    @RequiresPermission(Manifest.permission.ACCESS_BROADCAST_RADIO)
+    @RadioManager.RadioStatusType
+    public int seek(int direction, boolean skipSubChannel) {
+        throw new UnsupportedOperationException("Seeking is not supported");
+    }
 
     /**
      * Tune to a specific frequency.
-     * The operation is asynchronous and {@link Callback}
-     * onProgramInfoChanged() will be called when tune completes or
-     * onError() when cancelled or timeout.
+     *
+     * <p>The operation is asynchronous and {@link Callback#onProgramInfoChanged}
+     * will be called when tune completes or {@link Callback#onTuneFailed}
+     * when timeout or canceled.
+     *
+     * <p>When this operation is called by users other than current user or system user, it is
+     * ignored silently.
+     *
      * @param channel the specific channel or frequency to tune to.
      * @param subChannel the specific sub-channel to tune to. N/A if the selected configuration
      * does not support cub channels.
@@ -177,25 +249,37 @@
      *  <li>{@link RadioManager#STATUS_DEAD_OBJECT} if the binder transaction to the native
      *  service fails, </li>
      * </ul>
-     * @deprecated Use {@link tune(ProgramSelector)} instead.
+     * @deprecated Use {@link #tune(ProgramSelector)} instead.
      */
     @Deprecated
+    @RequiresPermission(Manifest.permission.ACCESS_BROADCAST_RADIO)
+    @RadioManager.RadioStatusType
     public abstract int tune(int channel, int subChannel);
 
     /**
      * Tune to a program.
      *
-     * The operation is asynchronous and {@link Callback} onProgramInfoChanged() will be called
-     * when tune completes or onError() when cancelled or on timeout.
+     * <p>The operation is asynchronous and {@link Callback#onProgramInfoChanged}
+     * will be called when tune completes or {@link Callback#onTuneFailed}
+     * when timeout or canceled.
+     *
+     * <p>When this operation is called by users other than current user or system user, it is
+     * ignored silently.
      *
      * @throws IllegalArgumentException if the provided selector is invalid
      */
+    @RequiresPermission(Manifest.permission.ACCESS_BROADCAST_RADIO)
     public abstract void tune(@NonNull ProgramSelector selector);
 
     /**
      * Cancel a pending scan or tune operation.
-     * If an operation is pending, {@link Callback} onError() will be called with
+     *
+     * <p>If an operation is pending, {@link Callback#onTuneFailed} will be called with
      * {@link #ERROR_CANCELLED}.
+     *
+     * <p>When this operation is called by users other than current user or system
+     * user, it is ignored silently.
+     *
      * @return
      * <ul>
      *  <li>{@link RadioManager#STATUS_OK} in case of success, </li>
@@ -207,21 +291,27 @@
      *  service fails, </li>
      * </ul>
      */
+    @RequiresPermission(Manifest.permission.ACCESS_BROADCAST_RADIO)
+    @RadioManager.RadioStatusType
     public abstract int cancel();
 
     /**
      * Cancels traffic or emergency announcement.
      *
-     * If there was no announcement to cancel, no action is taken.
+     * <p>If there was no announcement to cancel, no action is taken.
      *
-     * There is a race condition between calling cancelAnnouncement and the actual announcement
+     * <p>There is a race condition between calling cancelAnnouncement and the actual announcement
      * being finished, so onTrafficAnnouncement / onEmergencyAnnouncement callback should be
      * tracked with proper locking.
+     * @deprecated Only applicable for HAL 1.x.
      */
+    @Deprecated
+    @RequiresPermission(Manifest.permission.ACCESS_BROADCAST_RADIO)
     public abstract void cancelAnnouncement();
 
     /**
      * Get current station information.
+     *
      * @param info a ProgramInfo array of lengh 1 where the information is returned.
      * @return
      * <ul>
@@ -233,23 +323,27 @@
      *  <li>{@link RadioManager#STATUS_DEAD_OBJECT} if the binder transaction to the native
      *  service fails, </li>
      * </ul>
-     * @deprecated Use {@link onProgramInfoChanged} callback instead.
+     * @deprecated Use {@link Callback#onProgramInfoChanged(RadioManager.ProgramInfo)} callback
+     * instead.
      */
     @Deprecated
+    @RequiresPermission(Manifest.permission.ACCESS_BROADCAST_RADIO)
+    @RadioManager.RadioStatusType
     public abstract int getProgramInformation(RadioManager.ProgramInfo[] info);
 
     /**
      * Retrieves a {@link Bitmap} for the given image ID or null,
      * if the image was missing from the tuner.
      *
-     * This involves doing a call to the tuner, so the bitmap should be cached
+     * <p>This involves doing a call to the tuner, so the bitmap should be cached
      * on the application side.
      *
-     * If the method returns null for non-zero ID, it means the image was
+     * <p>If the method returns null for non-zero ID, it means the image was
      * updated on the tuner side. There is a race conditon between fetching
      * image for an old ID and tuner updating the image (and cleaning up the
      * old image). In such case, a new ProgramInfo with updated image id will
-     * be sent with a {@link onProgramInfoChanged} callback.
+     * be sent with a {@link Callback#onProgramInfoChanged(RadioManager.ProgramInfo)}
+     * callback.
      *
      * @param id The image identifier, retrieved with
      *           {@link RadioMetadata#getBitmapId(String)}.
@@ -258,14 +352,16 @@
      * @hide This API is not thoroughly elaborated yet
      */
     @SuppressWarnings("HiddenAbstractMethod")
+    @RequiresPermission(Manifest.permission.ACCESS_BROADCAST_RADIO)
     public abstract @Nullable Bitmap getMetadataImage(int id);
 
     /**
      * Initiates a background scan to update internally cached program list.
      *
-     * It may not be necessary to initiate the scan explicitly - the scan MAY be performed on boot.
+     * <p>It may not be necessary to initiate the scan explicitly - the scan MAY be performed on
+     * boot.
      *
-     * The operation is asynchronous and {@link Callback} backgroundScanComplete or onError will
+     * <p>The operation is asynchronous and {@link Callback} backgroundScanComplete or onError will
      * be called if the return value of this call was {@code true}. As result of this call
      * programListChanged may be triggered (if the scanned list differs).
      *
@@ -273,13 +369,16 @@
      * is unavailable; ie. temporarily due to ongoing foreground playback in single-tuner device
      * or permanently if the feature is not supported
      * (see ModuleProperties#isBackgroundScanningSupported()).
+     * @deprecated Only applicable for HAL 1.x.
      */
+    @Deprecated
+    @RequiresPermission(Manifest.permission.ACCESS_BROADCAST_RADIO)
     public abstract boolean startBackgroundScan();
 
     /**
      * Get the list of discovered radio stations.
      *
-     * To get the full list, set filter to null or empty map.
+     * <p>To get the full list, set filter to null or empty map.
      * Keys must be prefixed with unique vendor Java-style namespace,
      * eg. 'com.somecompany.parameter1'.
      *
@@ -288,24 +387,27 @@
      * @throws IllegalStateException if the scan is in progress or has not been started,
      *         startBackgroundScan() call may fix it.
      * @throws IllegalArgumentException if the vendorFilter argument is not valid.
-     * @deprecated Use {@link getDynamicProgramList} instead.
+     * @deprecated Use {@link #getDynamicProgramList(ProgramList.Filter)} instead.
      */
     @Deprecated
+    @RequiresPermission(Manifest.permission.ACCESS_BROADCAST_RADIO)
     public abstract @NonNull List<RadioManager.ProgramInfo>
             getProgramList(@Nullable Map<String, String> vendorFilter);
 
     /**
      * Get the dynamic list of discovered radio stations.
      *
-     * The list object is updated asynchronously; to get the updates register
-     * with {@link ProgramList#addListCallback}.
+     * <p>The list object is updated asynchronously; to get the updates register
+     * with {@link ProgramList#registerListCallback(ProgramList.ListCallback)}
+     * or {@link ProgramList#registerListCallback(Executor, ProgramList.ListCallback)}.
      *
-     * When the returned object is no longer used, it must be closed.
+     * <p>When the returned object is no longer used, it must be closed.
      *
      * @param filter filter for the list, or null to get the full list.
      * @return the dynamic program list object, close it after use
      *         or {@code null} if program list is not supported by the tuner
      */
+    @RequiresPermission(Manifest.permission.ACCESS_BROADCAST_RADIO)
     public @Nullable ProgramList getDynamicProgramList(@Nullable ProgramList.Filter filter) {
         return null;
     }
@@ -316,26 +418,28 @@
      * @throws IllegalStateException if the switch is not supported at current
      *         configuration.
      * @return {@code true} if analog is forced, {@code false} otherwise.
-     * @deprecated Use {@link isConfigFlagSet(int)} instead.
+     * @deprecated Use {@link #isConfigFlagSet(int)} instead.
      */
     @Deprecated
+    @RequiresPermission(Manifest.permission.ACCESS_BROADCAST_RADIO)
     public abstract boolean isAnalogForced();
 
     /**
      * Forces the analog playback for the supporting radio technology.
      *
-     * User may disable digital playback for FM HD Radio or hybrid FM/DAB with
+     * <p>User may disable digital playback for FM HD Radio or hybrid FM/DAB with
      * this option. This is purely user choice, ie. does not reflect digital-
      * analog handover managed from the HAL implementation side.
      *
-     * Some radio technologies may not support this, ie. DAB.
+     * <p>Some radio technologies may not support this, ie. DAB.
      *
      * @param isForced {@code true} to force analog, {@code false} for a default behaviour.
      * @throws IllegalStateException if the switch is not supported at current
      *         configuration.
-     * @deprecated Use {@link setConfigFlag(int, boolean)} instead.
+     * @deprecated Use {@link #setConfigFlag(int, boolean)}  instead.
      */
     @Deprecated
+    @RequiresPermission(Manifest.permission.ACCESS_BROADCAST_RADIO)
     public abstract void setAnalogForced(boolean isForced);
 
     /**
@@ -344,6 +448,7 @@
      * @param flag Flag to check.
      * @return True, if the flag is supported.
      */
+    @RequiresPermission(Manifest.permission.ACCESS_BROADCAST_RADIO)
     public boolean isConfigFlagSupported(@RadioManager.ConfigFlag int flag) {
         return false;
     }
@@ -351,29 +456,34 @@
     /**
      * Fetches the current setting of a given config flag.
      *
-     * The success/failure result is consistent with isConfigFlagSupported.
+     * <p>The success/failure result is consistent with isConfigFlagSupported.
      *
      * @param flag Flag to fetch.
      * @return The current value of the flag.
      * @throws IllegalStateException if the flag is not applicable right now.
      * @throws UnsupportedOperationException if the flag is not supported at all.
      */
+    @RequiresPermission(Manifest.permission.ACCESS_BROADCAST_RADIO)
     public boolean isConfigFlagSet(@RadioManager.ConfigFlag int flag) {
-        throw new UnsupportedOperationException();
+        throw new UnsupportedOperationException("isConfigFlagSet is not supported");
     }
 
     /**
      * Sets the config flag.
      *
-     * The success/failure result is consistent with isConfigFlagSupported.
+     * <p>The success/failure result is consistent with isConfigFlagSupported.
+     *
+     * <p>When this operation is called by users other than current user or system user,
+     * it is ignored silently.
      *
      * @param flag Flag to set.
      * @param value The new value of a given flag.
      * @throws IllegalStateException if the flag is not applicable right now.
      * @throws UnsupportedOperationException if the flag is not supported at all.
      */
+    @RequiresPermission(Manifest.permission.ACCESS_BROADCAST_RADIO)
     public void setConfigFlag(@RadioManager.ConfigFlag int flag, boolean value) {
-        throw new UnsupportedOperationException();
+        throw new UnsupportedOperationException("Setting config flag is not supported");
     }
 
     /**
@@ -381,30 +491,37 @@
      * The framework does not interpret the parameters, they are passed
      * in an opaque manner between a vendor application and HAL.
      *
-     * Framework does not make any assumptions on the keys or values, other than
+     * <p>Framework does not make any assumptions on the keys or values, other than
      * ones stated in VendorKeyValue documentation (a requirement of key
      * prefixes).
-     * See VendorKeyValue at hardware/interfaces/broadcastradio/2.0/types.hal.
+     * See VendorKeyValue at hardware/interfaces/broadcastradio/2.0/types.hal for
+     * HIDL 2.0 HAL or
+     * hardware/interfaces/broadcastradio/aidl/android/hardware/broadcastradio/VendorKeyValue.aidl
+     * for AIDL HAL.
      *
-     * For each pair in the result map, the key will be one of the keys
+     * <p>For each pair in the result map, the key will be one of the keys
      * contained in the input (possibly with wildcards expanded), and the value
      * will be a vendor-specific result status (such as "OK" or an error code).
      * The implementation may choose to return an empty map, or only return
      * a status for a subset of the provided inputs, at its discretion.
      *
-     * Application and HAL must not use keys with unknown prefix. In particular,
+     * <p>Application and HAL must not use keys with unknown prefix. In particular,
      * it must not place a key-value pair in results vector for unknown key from
      * parameters vector - instead, an unknown key should simply be ignored.
      * In other words, results vector may contain a subset of parameter keys
      * (however, the framework doesn't enforce a strict subset - the only
      * formal requirement is vendor domain prefix for keys).
      *
+     * <p>When this operation is called by users other than current user or system user,
+     * it is ignored silently.
+     *
      * @param parameters Vendor-specific key-value pairs.
      * @return Operation completion status for parameters being set.
      */
+    @RequiresPermission(Manifest.permission.ACCESS_BROADCAST_RADIO)
     public @NonNull Map<String, String>
             setParameters(@NonNull Map<String, String> parameters) {
-        throw new UnsupportedOperationException();
+        throw new UnsupportedOperationException("Setting parameters is not supported");
     }
 
     /**
@@ -412,23 +529,24 @@
      * The framework does not interpret the parameters, they are passed
      * in an opaque manner between a vendor application and HAL.
      *
-     * Framework does not cache set/get requests, so it's possible for
+     * <p>Framework does not cache set/get requests, so it's possible for
      * getParameter to return a different value than previous setParameter call.
      *
-     * The syntax and semantics of keys are up to the vendor (as long as prefix
+     * <p>The syntax and semantics of keys are up to the vendor (as long as prefix
      * rules are obeyed). For instance, vendors may include some form of
      * wildcard support. In such case, result vector may be of different size
      * than requested keys vector. However, wildcards are not recognized by
      * framework and they are passed as-is to the HAL implementation.
      *
-     * Unknown keys must be ignored and not placed into results vector.
+     * <p>Unknown keys must be ignored and not placed into results vector.
      *
      * @param keys Parameter keys to fetch.
      * @return Vendor-specific key-value pairs.
      */
+    @RequiresPermission(Manifest.permission.ACCESS_BROADCAST_RADIO)
     public @NonNull Map<String, String>
             getParameters(@NonNull List<String> keys) {
-        throw new UnsupportedOperationException();
+        throw new UnsupportedOperationException("Getting parameters is not supported");
     }
 
     /**
@@ -436,14 +554,16 @@
      * Only valid if a configuration has been applied.
      * @return {@code true} if the antenna is connected, {@code false} otherwise.
      *
-     * @deprecated Use {@link onAntennaState} callback instead
+     * @deprecated Use {@link Callback#onAntennaState(boolean)} callback instead
      */
     @Deprecated
+    @RequiresPermission(Manifest.permission.ACCESS_BROADCAST_RADIO)
     public abstract boolean isAntennaConnected();
 
     /**
      * Indicates if this client actually controls the tuner.
-     * Control is always granted after
+     *
+     * <p>Control is always granted after
      * {@link RadioManager#openTuner(int,
      * RadioManager.BandConfig, boolean, Callback, Handler)}
      * returns a non null tuner interface.
@@ -451,48 +571,102 @@
      * When this happens, {@link Callback#onControlChanged(boolean)} is received.
      * The client can either wait for control to be returned (which is indicated by the same
      * callback) or close and reopen the tuner interface.
+     *
      * @return {@code true} if this interface controls the tuner,
      * {@code false} otherwise or if a problem occured retrieving the state.
      */
+    @RequiresPermission(Manifest.permission.ACCESS_BROADCAST_RADIO)
     public abstract boolean hasControl();
 
     /** Indicates a failure of radio IC or driver.
-     * The application must close and re open the tuner
-     * @deprecated See {@link onError} callback.
+     *
+     * <p>The application must close and re open the tuner
+     *
+     * @deprecated See {@link RadioTuner.Callback#onError(int)} callback.
      */
     @Deprecated
     public static final int ERROR_HARDWARE_FAILURE = 0;
     /** Indicates a failure of the radio service.
-     * The application must close and re open the tuner
-     * @deprecated See {@link onError} callback.
+     *
+     * <p>The application must close and re open the tuner
+     * @deprecated See {@link RadioTuner.Callback#onError(int)} callback.
      */
     @Deprecated
     public static final  int ERROR_SERVER_DIED = 1;
     /** A pending seek or tune operation was cancelled
-     * @deprecated See {@link onError} callback.
+     * @deprecated See {@link RadioTuner.Callback#onError(int)} callback.
      */
     @Deprecated
     public static final  int ERROR_CANCELLED = 2;
     /** A pending seek or tune operation timed out
-     * @deprecated See {@link onError} callback.
+     * @deprecated See {@link RadioTuner.Callback#onError(int)} callback.
      */
     @Deprecated
     public static final  int ERROR_SCAN_TIMEOUT = 3;
     /** The requested configuration could not be applied
-     * @deprecated See {@link onError} callback.
+     * @deprecated See {@link RadioTuner.Callback#onError(int)} callback.
      */
     @Deprecated
     public static final  int ERROR_CONFIG = 4;
     /** Background scan was interrupted due to hardware becoming temporarily unavailable.
-     * @deprecated See {@link onError} callback.
+     * @deprecated See {@link RadioTuner.Callback#onError(int)} callback.
      */
     @Deprecated
     public static final int ERROR_BACKGROUND_SCAN_UNAVAILABLE = 5;
     /** Background scan failed due to other error, ie. HW failure.
-     * @deprecated See {@link onError} callback.
+     * @deprecated See {@link RadioTuner.Callback#onError(int)} callback.
      */
     @Deprecated
     public static final int ERROR_BACKGROUND_SCAN_FAILED = 6;
+    /** Result when a tune, seek, or step operation runs without error.
+     */
+    public static final int TUNER_RESULT_OK = 0;
+    /** Result when internal error occurs in HAL.
+     * See {@link RadioTuner.Callback#onTuneFailed(int, ProgramSelector)} callback.
+     */
+    public static final int TUNER_RESULT_INTERNAL_ERROR = 1;
+    /** Result used when the input argument for the method is invalid.
+     * See {@link RadioTuner.Callback#onTuneFailed(int, ProgramSelector)} callback.
+     */
+    public static final int TUNER_RESULT_INVALID_ARGUMENTS = 2;
+    /** Result when HAL is of invalid state.
+     * See {@link RadioTuner.Callback#onTuneFailed(int, ProgramSelector)} callback.
+     */
+    public static final int TUNER_RESULT_INVALID_STATE = 3;
+    /** Result when the operation is not supported.
+     * See {@link RadioTuner.Callback#onTuneFailed(int, ProgramSelector)} callback.
+     */
+    public static final int TUNER_RESULT_NOT_SUPPORTED = 4;
+    /** Result when a tune, seek, or step operation is timeout
+     * See {@link RadioTuner.Callback#onTuneFailed(int, ProgramSelector)} callback.
+     */
+    public static final int TUNER_RESULT_TIMEOUT = 5;
+    /** Result when a tune, seek, or step operation is canceled before processed.
+     * See {@link RadioTuner.Callback#onTuneFailed(int, ProgramSelector)} callback.
+     */
+    public static final int TUNER_RESULT_CANCELED = 6;
+    /** Result when a tune, seek, or step operation fails due to unknown error.
+     * See {@link RadioTuner.Callback#onTuneFailed(int, ProgramSelector)} callback.
+     */
+    public static final int TUNER_RESULT_UNKNOWN_ERROR = 7;
+
+    /**
+     *  Tuning operation result types
+     *
+     * @hide
+     */
+    @IntDef(prefix = { "TUNER_RESULT_" }, value = {
+            TUNER_RESULT_OK,
+            TUNER_RESULT_INTERNAL_ERROR,
+            TUNER_RESULT_INVALID_ARGUMENTS,
+            TUNER_RESULT_INVALID_STATE,
+            TUNER_RESULT_NOT_SUPPORTED,
+            TUNER_RESULT_TIMEOUT,
+            TUNER_RESULT_CANCELED,
+            TUNER_RESULT_UNKNOWN_ERROR,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface TunerResultType{}
 
     /**
      * Callback provided by the client application when opening a {@link RadioTuner}
@@ -506,8 +680,9 @@
          * {@link #ERROR_CANCELLED}, {@link #ERROR_SCAN_TIMEOUT},
          * {@link #ERROR_CONFIG}
          *
-         * @deprecated Use {@link onTuneFailed} for tune, scan and step;
-         *             other use cases (configuration, background scan) are already deprecated.
+         * @deprecated Use {@link RadioTuner.Callback#onTuneFailed(int, ProgramSelector)} for
+         *             tune, scan and step; other use cases (configuration, background scan)
+         *             are already deprecated.
          */
         public void onError(int status) {}
 
@@ -518,7 +693,7 @@
          * @param selector ProgramSelector argument of tune that failed;
          *                 null for scan and step.
          */
-        public void onTuneFailed(int result, @Nullable ProgramSelector selector) {}
+        public void onTuneFailed(@TunerResultType int result, @Nullable ProgramSelector selector) {}
 
         /**
          * onConfigurationChanged() is called upon successful completion of
@@ -533,7 +708,7 @@
         /**
          * Called when program info (including metadata) for the current program has changed.
          *
-         * It happens either upon successful completion of {@link RadioTuner#step(int, boolean)},
+         * <p>It happens either upon successful completion of {@link RadioTuner#step(int, boolean)},
          * {@link RadioTuner#scan(int, boolean)}, {@link RadioTuner#tune(int, int)}; when
          * a switching to alternate frequency occurs; or when metadata is updated.
          */
@@ -589,16 +764,29 @@
         /**
          * Called when available program list changed.
          *
-         * Use {@link RadioTuner#getProgramList(String)} to get an actual list.
+         * Use {@link RadioTuner#getProgramList(Map)} to get an actual list.
          */
         public void onProgramListChanged() {}
 
         /**
+         * Called when config flags are updated asynchronously due to internal events
+         * in broadcast radio HAL.
+         *
+         * {@link RadioTuner#setConfigFlag(int, boolean)} must not trigger this
+         * callback.
+         *
+         * @param flag Config flag updated
+         * @param value Value of the updated config flag
+         */
+        public void onConfigFlagUpdated(@RadioManager.ConfigFlag int flag, boolean value) {}
+
+        /**
          * Generic callback for passing updates to vendor-specific parameter values.
-         * The framework does not interpret the parameters, they are passed
+         *
+         * <p>The framework does not interpret the parameters, they are passed
          * in an opaque manner between a vendor application and HAL.
          *
-         * It's up to the HAL implementation if and how to implement this callback,
+         * <p>It's up to the HAL implementation if and how to implement this callback,
          * as long as it obeys the prefix rule. In particular, only selected keys
          * may be notified this way. However, setParameters must not trigger
          * this callback, while an internal event can change parameters
diff --git a/core/java/android/hardware/radio/TunerAdapter.java b/core/java/android/hardware/radio/TunerAdapter.java
index 4a18333..bdbca91 100644
--- a/core/java/android/hardware/radio/TunerAdapter.java
+++ b/core/java/android/hardware/radio/TunerAdapter.java
@@ -154,7 +154,7 @@
     @Override
     public int scan(int direction, boolean skipSubChannel) {
         try {
-            mTuner.scan(/* directionDown= */ direction == RadioTuner.DIRECTION_DOWN,
+            mTuner.seek(/* directionDown= */ direction == RadioTuner.DIRECTION_DOWN,
                     skipSubChannel);
         } catch (IllegalStateException e) {
             Log.e(TAG, "Can't scan", e);
@@ -167,6 +167,21 @@
     }
 
     @Override
+    public int seek(int direction, boolean skipSubChannel) {
+        try {
+            mTuner.seek(/* directionDown= */ direction == RadioTuner.DIRECTION_DOWN,
+                    skipSubChannel);
+        } catch (IllegalStateException e) {
+            Log.e(TAG, "Can't seek", e);
+            return RadioManager.STATUS_INVALID_OPERATION;
+        } catch (RemoteException e) {
+            Log.e(TAG, "Service died", e);
+            return RadioManager.STATUS_DEAD_OBJECT;
+        }
+        return RadioManager.STATUS_OK;
+    }
+
+    @Override
     public int tune(int channel, int subChannel) {
         try {
             int band;
diff --git a/core/java/android/hardware/radio/TunerCallbackAdapter.java b/core/java/android/hardware/radio/TunerCallbackAdapter.java
index b9782a8..22f5902 100644
--- a/core/java/android/hardware/radio/TunerCallbackAdapter.java
+++ b/core/java/android/hardware/radio/TunerCallbackAdapter.java
@@ -246,6 +246,11 @@
     }
 
     @Override
+    public void onConfigFlagUpdated(@RadioManager.ConfigFlag int flag, boolean value) {
+        mHandler.post(() -> mCallback.onConfigFlagUpdated(flag, value));
+    }
+
+    @Override
     public void onParametersUpdated(Map<String, String> parameters) {
         mHandler.post(() -> mCallback.onParametersUpdated(parameters));
     }
diff --git a/core/java/android/net/vcn/VcnConfig.java b/core/java/android/net/vcn/VcnConfig.java
index fd3fe37..dcf0026 100644
--- a/core/java/android/net/vcn/VcnConfig.java
+++ b/core/java/android/net/vcn/VcnConfig.java
@@ -15,16 +15,23 @@
  */
 package android.net.vcn;
 
+import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
+
 import static com.android.internal.annotations.VisibleForTesting.Visibility;
+import static com.android.server.vcn.util.PersistableBundleUtils.INTEGER_DESERIALIZER;
+import static com.android.server.vcn.util.PersistableBundleUtils.INTEGER_SERIALIZER;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
+import android.net.NetworkCapabilities;
 import android.net.NetworkRequest;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.PersistableBundle;
 import android.util.ArraySet;
+import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.Preconditions;
@@ -32,6 +39,7 @@
 
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.Iterator;
 import java.util.Objects;
 import java.util.Set;
 
@@ -46,22 +54,36 @@
 public final class VcnConfig implements Parcelable {
     @NonNull private static final String TAG = VcnConfig.class.getSimpleName();
 
+    private static final Set<Integer> ALLOWED_TRANSPORTS = new ArraySet<>();
+
+    static {
+        ALLOWED_TRANSPORTS.add(TRANSPORT_WIFI);
+        ALLOWED_TRANSPORTS.add(TRANSPORT_CELLULAR);
+    }
+
     private static final String PACKAGE_NAME_KEY = "mPackageName";
     @NonNull private final String mPackageName;
 
     private static final String GATEWAY_CONNECTION_CONFIGS_KEY = "mGatewayConnectionConfigs";
     @NonNull private final Set<VcnGatewayConnectionConfig> mGatewayConnectionConfigs;
 
+    private static final Set<Integer> RESTRICTED_TRANSPORTS_DEFAULT =
+            Collections.singleton(TRANSPORT_WIFI);
+    private static final String RESTRICTED_TRANSPORTS_KEY = "mRestrictedTransports";
+    @NonNull private final Set<Integer> mRestrictedTransports;
+
     private static final String IS_TEST_MODE_PROFILE_KEY = "mIsTestModeProfile";
     private final boolean mIsTestModeProfile;
 
     private VcnConfig(
             @NonNull String packageName,
             @NonNull Set<VcnGatewayConnectionConfig> gatewayConnectionConfigs,
+            @NonNull Set<Integer> restrictedTransports,
             boolean isTestModeProfile) {
         mPackageName = packageName;
         mGatewayConnectionConfigs =
                 Collections.unmodifiableSet(new ArraySet<>(gatewayConnectionConfigs));
+        mRestrictedTransports = Collections.unmodifiableSet(new ArraySet<>(restrictedTransports));
         mIsTestModeProfile = isTestModeProfile;
 
         validate();
@@ -82,6 +104,20 @@
                 new ArraySet<>(
                         PersistableBundleUtils.toList(
                                 gatewayConnectionConfigsBundle, VcnGatewayConnectionConfig::new));
+
+        final PersistableBundle restrictedTransportsBundle =
+                in.getPersistableBundle(RESTRICTED_TRANSPORTS_KEY);
+        if (restrictedTransportsBundle == null) {
+            // RESTRICTED_TRANSPORTS_KEY was added in U and does not exist in VcnConfigs created in
+            // older platforms
+            mRestrictedTransports = RESTRICTED_TRANSPORTS_DEFAULT;
+        } else {
+            mRestrictedTransports =
+                    new ArraySet<Integer>(
+                            PersistableBundleUtils.toList(
+                                    restrictedTransportsBundle, INTEGER_DESERIALIZER));
+        }
+
         mIsTestModeProfile = in.getBoolean(IS_TEST_MODE_PROFILE_KEY);
 
         validate();
@@ -91,6 +127,19 @@
         Objects.requireNonNull(mPackageName, "packageName was null");
         Preconditions.checkCollectionNotEmpty(
                 mGatewayConnectionConfigs, "gatewayConnectionConfigs was empty");
+
+        final Iterator<Integer> iterator = mRestrictedTransports.iterator();
+        while (iterator.hasNext()) {
+            final int transport = iterator.next();
+            if (!ALLOWED_TRANSPORTS.contains(transport)) {
+                iterator.remove();
+                Log.w(
+                        TAG,
+                        "Found invalid transport "
+                                + transport
+                                + " which might be from a new version of VcnConfig");
+            }
+        }
     }
 
     /**
@@ -110,6 +159,16 @@
     }
 
     /**
+     * Retrieve the transports that will be restricted by the VCN.
+     *
+     * @see Builder#setRestrictedUnderlyingNetworkTransports(Set)
+     */
+    @NonNull
+    public Set<Integer> getRestrictedUnderlyingNetworkTransports() {
+        return Collections.unmodifiableSet(mRestrictedTransports);
+    }
+
+    /**
      * Returns whether or not this VcnConfig is restricted to test networks.
      *
      * @hide
@@ -134,6 +193,12 @@
                         new ArrayList<>(mGatewayConnectionConfigs),
                         VcnGatewayConnectionConfig::toPersistableBundle);
         result.putPersistableBundle(GATEWAY_CONNECTION_CONFIGS_KEY, gatewayConnectionConfigsBundle);
+
+        final PersistableBundle restrictedTransportsBundle =
+                PersistableBundleUtils.fromList(
+                        new ArrayList<>(mRestrictedTransports), INTEGER_SERIALIZER);
+        result.putPersistableBundle(RESTRICTED_TRANSPORTS_KEY, restrictedTransportsBundle);
+
         result.putBoolean(IS_TEST_MODE_PROFILE_KEY, mIsTestModeProfile);
 
         return result;
@@ -141,7 +206,8 @@
 
     @Override
     public int hashCode() {
-        return Objects.hash(mPackageName, mGatewayConnectionConfigs, mIsTestModeProfile);
+        return Objects.hash(
+                mPackageName, mGatewayConnectionConfigs, mRestrictedTransports, mIsTestModeProfile);
     }
 
     @Override
@@ -153,6 +219,7 @@
         final VcnConfig rhs = (VcnConfig) other;
         return mPackageName.equals(rhs.mPackageName)
                 && mGatewayConnectionConfigs.equals(rhs.mGatewayConnectionConfigs)
+                && mRestrictedTransports.equals(rhs.mRestrictedTransports)
                 && mIsTestModeProfile == rhs.mIsTestModeProfile;
     }
 
@@ -189,12 +256,15 @@
         @NonNull
         private final Set<VcnGatewayConnectionConfig> mGatewayConnectionConfigs = new ArraySet<>();
 
+        @NonNull private final Set<Integer> mRestrictedTransports = new ArraySet<>();
+
         private boolean mIsTestModeProfile = false;
 
         public Builder(@NonNull Context context) {
             Objects.requireNonNull(context, "context was null");
 
             mPackageName = context.getOpPackageName();
+            mRestrictedTransports.addAll(RESTRICTED_TRANSPORTS_DEFAULT);
         }
 
         /**
@@ -225,6 +295,36 @@
             return this;
         }
 
+        private void validateRestrictedTransportsOrThrow(Set<Integer> restrictedTransports) {
+            Objects.requireNonNull(restrictedTransports, "transports was null");
+
+            for (int transport : restrictedTransports) {
+                if (!ALLOWED_TRANSPORTS.contains(transport)) {
+                    throw new IllegalArgumentException("Invalid transport " + transport);
+                }
+            }
+        }
+
+        /**
+         * Sets transports that will be restricted by the VCN.
+         *
+         * @param transports transports that will be restricted by VCN. Networks that include any
+         *     of the transports will be marked as restricted. Only {@link
+         *     NetworkCapabilities#TRANSPORT_WIFI} and {@link
+         *     NetworkCapabilities#TRANSPORT_CELLULAR} are allowed. {@link
+         *     NetworkCapabilities#TRANSPORT_WIFI} is marked restricted by default.
+         * @return this {@link Builder} instance, for chaining
+         * @throws IllegalArgumentException if the input contains unsupported transport types.
+         */
+        @NonNull
+        public Builder setRestrictedUnderlyingNetworkTransports(@NonNull Set<Integer> transports) {
+            validateRestrictedTransportsOrThrow(transports);
+
+            mRestrictedTransports.clear();
+            mRestrictedTransports.addAll(transports);
+            return this;
+        }
+
         /**
          * Restricts this VcnConfig to matching with test networks (only).
          *
@@ -248,7 +348,11 @@
          */
         @NonNull
         public VcnConfig build() {
-            return new VcnConfig(mPackageName, mGatewayConnectionConfigs, mIsTestModeProfile);
+            return new VcnConfig(
+                    mPackageName,
+                    mGatewayConnectionConfigs,
+                    mRestrictedTransports,
+                    mIsTestModeProfile);
         }
     }
 }
diff --git a/core/java/android/nfc/NfcAdapter.java b/core/java/android/nfc/NfcAdapter.java
index f545c30..0c7f529 100644
--- a/core/java/android/nfc/NfcAdapter.java
+++ b/core/java/android/nfc/NfcAdapter.java
@@ -30,6 +30,7 @@
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.content.IntentFilter;
+import android.content.pm.IPackageManager;
 import android.content.pm.PackageManager;
 import android.net.Uri;
 import android.nfc.tech.MifareClassic;
@@ -525,6 +526,66 @@
     }
 
     /**
+     * Helper to check if this device has FEATURE_NFC_BEAM, but without using
+     * a context.
+     * Equivalent to
+     * context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_NFC_BEAM)
+     */
+    private static boolean hasBeamFeature() {
+        IPackageManager pm = ActivityThread.getPackageManager();
+        if (pm == null) {
+            Log.e(TAG, "Cannot get package manager, assuming no Android Beam feature");
+            return false;
+        }
+        try {
+            return pm.hasSystemFeature(PackageManager.FEATURE_NFC_BEAM, 0);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Package manager query failed, assuming no Android Beam feature", e);
+            return false;
+        }
+    }
+
+    /**
+     * Helper to check if this device has FEATURE_NFC, but without using
+     * a context.
+     * Equivalent to
+     * context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_NFC)
+     */
+    private static boolean hasNfcFeature() {
+        IPackageManager pm = ActivityThread.getPackageManager();
+        if (pm == null) {
+            Log.e(TAG, "Cannot get package manager, assuming no NFC feature");
+            return false;
+        }
+        try {
+            return pm.hasSystemFeature(PackageManager.FEATURE_NFC, 0);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Package manager query failed, assuming no NFC feature", e);
+            return false;
+        }
+    }
+
+    /**
+     * Helper to check if this device is NFC HCE capable, by checking for
+     * FEATURE_NFC_HOST_CARD_EMULATION and/or FEATURE_NFC_HOST_CARD_EMULATION_NFCF,
+     * but without using a context.
+     */
+    private static boolean hasNfcHceFeature() {
+        IPackageManager pm = ActivityThread.getPackageManager();
+        if (pm == null) {
+            Log.e(TAG, "Cannot get package manager, assuming no NFC feature");
+            return false;
+        }
+        try {
+            return pm.hasSystemFeature(PackageManager.FEATURE_NFC_HOST_CARD_EMULATION, 0)
+                || pm.hasSystemFeature(PackageManager.FEATURE_NFC_HOST_CARD_EMULATION_NFCF, 0);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Package manager query failed, assuming no NFC feature", e);
+            return false;
+        }
+    }
+
+    /**
      * Return list of Secure Elements which support off host card emulation.
      *
      * @return List<String> containing secure elements on the device which supports
@@ -533,21 +594,23 @@
      * @hide
      */
     public @NonNull List<String> getSupportedOffHostSecureElements() {
-        if (mContext == null) {
-            throw new UnsupportedOperationException("You need a context on NfcAdapter to use the "
-                    + " getSupportedOffHostSecureElements APIs");
-        }
         List<String> offHostSE = new ArrayList<String>();
-        PackageManager pm = mContext.getPackageManager();
+        IPackageManager pm = ActivityThread.getPackageManager();
         if (pm == null) {
             Log.e(TAG, "Cannot get package manager, assuming no off-host CE feature");
             return offHostSE;
         }
-        if (pm.hasSystemFeature(PackageManager.FEATURE_NFC_OFF_HOST_CARD_EMULATION_UICC)) {
-            offHostSE.add("SIM");
-        }
-        if (pm.hasSystemFeature(PackageManager.FEATURE_NFC_OFF_HOST_CARD_EMULATION_ESE)) {
-            offHostSE.add("eSE");
+        try {
+            if (pm.hasSystemFeature(PackageManager.FEATURE_NFC_OFF_HOST_CARD_EMULATION_UICC, 0)) {
+                offHostSE.add("SIM");
+            }
+            if (pm.hasSystemFeature(PackageManager.FEATURE_NFC_OFF_HOST_CARD_EMULATION_ESE, 0)) {
+                offHostSE.add("eSE");
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "Package manager query failed, assuming no off-host CE feature", e);
+            offHostSE.clear();
+            return offHostSE;
         }
         return offHostSE;
     }
@@ -559,19 +622,10 @@
      */
     @UnsupportedAppUsage
     public static synchronized NfcAdapter getNfcAdapter(Context context) {
-        if (context == null) {
-            if (sNullContextNfcAdapter == null) {
-                sNullContextNfcAdapter = new NfcAdapter(null);
-            }
-            return sNullContextNfcAdapter;
-        }
         if (!sIsInitialized) {
-            PackageManager pm = context.getPackageManager();
-            sHasNfcFeature = pm.hasSystemFeature(PackageManager.FEATURE_NFC);
-            sHasBeamFeature = pm.hasSystemFeature(PackageManager.FEATURE_NFC_BEAM);
-            boolean hasHceFeature =
-                    pm.hasSystemFeature(PackageManager.FEATURE_NFC_HOST_CARD_EMULATION)
-                    || pm.hasSystemFeature(PackageManager.FEATURE_NFC_HOST_CARD_EMULATION_NFCF);
+            sHasNfcFeature = hasNfcFeature();
+            sHasBeamFeature = hasBeamFeature();
+            boolean hasHceFeature = hasNfcHceFeature();
             /* is this device meant to have NFC */
             if (!sHasNfcFeature && !hasHceFeature) {
                 Log.v(TAG, "this device does not have NFC support");
@@ -607,6 +661,12 @@
 
             sIsInitialized = true;
         }
+        if (context == null) {
+            if (sNullContextNfcAdapter == null) {
+                sNullContextNfcAdapter = new NfcAdapter(null);
+            }
+            return sNullContextNfcAdapter;
+        }
         NfcAdapter adapter = sNfcAdapters.get(context);
         if (adapter == null) {
             adapter = new NfcAdapter(context);
@@ -617,12 +677,8 @@
 
     /** get handle to NFC service interface */
     private static INfcAdapter getServiceInterface() {
-        if (!sHasNfcFeature) {
-            /* NFC is not supported */
-            return null;
-        }
         /* get a handle to NFC service */
-        IBinder b = ServiceManager.waitForService("nfc");
+        IBinder b = ServiceManager.getService("nfc");
         if (b == null) {
             return null;
         }
@@ -652,15 +708,6 @@
                     "context not associated with any application (using a mock context?)");
         }
 
-        synchronized (NfcAdapter.class) {
-            if (!sIsInitialized) {
-                PackageManager pm = context.getPackageManager();
-                sHasNfcFeature = pm.hasSystemFeature(PackageManager.FEATURE_NFC);
-            }
-            if (!sHasNfcFeature) {
-                return null;
-            }
-        }
         if (getServiceInterface() == null) {
             // NFC is not available
             return null;
diff --git a/core/java/android/nfc/cardemulation/CardEmulation.java b/core/java/android/nfc/cardemulation/CardEmulation.java
index 6a42091..0b56d19 100644
--- a/core/java/android/nfc/cardemulation/CardEmulation.java
+++ b/core/java/android/nfc/cardemulation/CardEmulation.java
@@ -22,9 +22,11 @@
 import android.annotation.SdkConstant;
 import android.annotation.SdkConstant.SdkConstantType;
 import android.app.Activity;
+import android.app.ActivityThread;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
+import android.content.pm.IPackageManager;
 import android.content.pm.PackageManager;
 import android.nfc.INfcCardEmulation;
 import android.nfc.NfcAdapter;
@@ -156,13 +158,18 @@
             throw new UnsupportedOperationException();
         }
         if (!sIsInitialized) {
-            PackageManager pm = context.getPackageManager();
+            IPackageManager pm = ActivityThread.getPackageManager();
             if (pm == null) {
                 Log.e(TAG, "Cannot get PackageManager");
                 throw new UnsupportedOperationException();
             }
-            if (!pm.hasSystemFeature(PackageManager.FEATURE_NFC_HOST_CARD_EMULATION)) {
-                Log.e(TAG, "This device does not support card emulation");
+            try {
+                if (!pm.hasSystemFeature(PackageManager.FEATURE_NFC_HOST_CARD_EMULATION, 0)) {
+                    Log.e(TAG, "This device does not support card emulation");
+                    throw new UnsupportedOperationException();
+                }
+            } catch (RemoteException e) {
+                Log.e(TAG, "PackageManager query failed.");
                 throw new UnsupportedOperationException();
             }
             sIsInitialized = true;
diff --git a/core/java/android/nfc/cardemulation/NfcFCardEmulation.java b/core/java/android/nfc/cardemulation/NfcFCardEmulation.java
index 48bbf5b6..3c92455 100644
--- a/core/java/android/nfc/cardemulation/NfcFCardEmulation.java
+++ b/core/java/android/nfc/cardemulation/NfcFCardEmulation.java
@@ -17,8 +17,10 @@
 package android.nfc.cardemulation;
 
 import android.app.Activity;
+import android.app.ActivityThread;
 import android.content.ComponentName;
 import android.content.Context;
+import android.content.pm.IPackageManager;
 import android.content.pm.PackageManager;
 import android.nfc.INfcFCardEmulation;
 import android.nfc.NfcAdapter;
@@ -68,13 +70,18 @@
             throw new UnsupportedOperationException();
         }
         if (!sIsInitialized) {
-            PackageManager pm = context.getPackageManager();
+            IPackageManager pm = ActivityThread.getPackageManager();
             if (pm == null) {
                 Log.e(TAG, "Cannot get PackageManager");
                 throw new UnsupportedOperationException();
             }
-            if (!pm.hasSystemFeature(PackageManager.FEATURE_NFC_HOST_CARD_EMULATION_NFCF)) {
-                Log.e(TAG, "This device does not support NFC-F card emulation");
+            try {
+                if (!pm.hasSystemFeature(PackageManager.FEATURE_NFC_HOST_CARD_EMULATION_NFCF, 0)) {
+                    Log.e(TAG, "This device does not support NFC-F card emulation");
+                    throw new UnsupportedOperationException();
+                }
+            } catch (RemoteException e) {
+                Log.e(TAG, "PackageManager query failed.");
                 throw new UnsupportedOperationException();
             }
             sIsInitialized = true;
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index e915d98..2a4c861 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -44,6 +44,7 @@
 import android.util.MutableBoolean;
 import android.util.Pair;
 import android.util.Printer;
+import android.util.Slog;
 import android.util.SparseArray;
 import android.util.SparseDoubleArray;
 import android.util.SparseIntArray;
@@ -52,6 +53,7 @@
 import android.view.Display;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.os.BatteryStatsHistoryIterator;
 
 import com.google.android.collect.Lists;
 
@@ -2398,9 +2400,6 @@
 
     public abstract int getHistoryUsedSize();
 
-    @UnsupportedAppUsage
-    public abstract boolean startIteratingHistoryLocked();
-
     public abstract int getHistoryStringPoolSize();
 
     public abstract int getHistoryStringPoolBytes();
@@ -2409,10 +2408,11 @@
 
     public abstract int getHistoryTagPoolUid(int index);
 
-    @UnsupportedAppUsage
-    public abstract boolean getNextHistoryLocked(HistoryItem out);
-
-    public abstract void finishIteratingHistoryLocked();
+    /**
+     * Returns a BatteryStatsHistoryIterator. Battery history will remain immutable until the
+     * {@link BatteryStatsHistoryIterator#close()} method is invoked.
+     */
+    public abstract BatteryStatsHistoryIterator iterateBatteryStatsHistory();
 
     /**
      * Returns the number of times the device has been started.
@@ -7451,80 +7451,88 @@
 
     private void dumpHistoryLocked(PrintWriter pw, int flags, long histStart, boolean checkin) {
         final HistoryPrinter hprinter = new HistoryPrinter();
-        final HistoryItem rec = new HistoryItem();
         long lastTime = -1;
         long baseTime = -1;
         boolean printed = false;
         HistoryEventTracker tracker = null;
-        while (getNextHistoryLocked(rec)) {
-            lastTime = rec.time;
-            if (baseTime < 0) {
-                baseTime = lastTime;
-            }
-            if (rec.time >= histStart) {
-                if (histStart >= 0 && !printed) {
-                    if (rec.cmd == HistoryItem.CMD_CURRENT_TIME
-                            || rec.cmd == HistoryItem.CMD_RESET
-                            || rec.cmd == HistoryItem.CMD_START
-                            || rec.cmd == HistoryItem.CMD_SHUTDOWN) {
-                        printed = true;
-                        hprinter.printNextItem(pw, rec, baseTime, checkin,
-                                (flags&DUMP_VERBOSE) != 0);
-                        rec.cmd = HistoryItem.CMD_UPDATE;
-                    } else if (rec.currentTime != 0) {
-                        printed = true;
-                        byte cmd = rec.cmd;
-                        rec.cmd = HistoryItem.CMD_CURRENT_TIME;
-                        hprinter.printNextItem(pw, rec, baseTime, checkin,
-                                (flags&DUMP_VERBOSE) != 0);
-                        rec.cmd = cmd;
+        try (BatteryStatsHistoryIterator iterator = iterateBatteryStatsHistory()) {
+            HistoryItem rec;
+            while ((rec = iterator.next()) != null) {
+                try {
+                    lastTime = rec.time;
+                    if (baseTime < 0) {
+                        baseTime = lastTime;
                     }
-                    if (tracker != null) {
-                        if (rec.cmd != HistoryItem.CMD_UPDATE) {
-                            hprinter.printNextItem(pw, rec, baseTime, checkin,
-                                    (flags&DUMP_VERBOSE) != 0);
-                            rec.cmd = HistoryItem.CMD_UPDATE;
-                        }
-                        int oldEventCode = rec.eventCode;
-                        HistoryTag oldEventTag = rec.eventTag;
-                        rec.eventTag = new HistoryTag();
-                        for (int i=0; i<HistoryItem.EVENT_COUNT; i++) {
-                            HashMap<String, SparseIntArray> active
-                                    = tracker.getStateForEvent(i);
-                            if (active == null) {
-                                continue;
+                    if (rec.time >= histStart) {
+                        if (histStart >= 0 && !printed) {
+                            if (rec.cmd == HistoryItem.CMD_CURRENT_TIME
+                                    || rec.cmd == HistoryItem.CMD_RESET
+                                    || rec.cmd == HistoryItem.CMD_START
+                                    || rec.cmd == HistoryItem.CMD_SHUTDOWN) {
+                                printed = true;
+                                hprinter.printNextItem(pw, rec, baseTime, checkin,
+                                        (flags & DUMP_VERBOSE) != 0);
+                                rec.cmd = HistoryItem.CMD_UPDATE;
+                            } else if (rec.currentTime != 0) {
+                                printed = true;
+                                byte cmd = rec.cmd;
+                                rec.cmd = HistoryItem.CMD_CURRENT_TIME;
+                                hprinter.printNextItem(pw, rec, baseTime, checkin,
+                                        (flags & DUMP_VERBOSE) != 0);
+                                rec.cmd = cmd;
                             }
-                            for (HashMap.Entry<String, SparseIntArray> ent
-                                    : active.entrySet()) {
-                                SparseIntArray uids = ent.getValue();
-                                for (int j=0; j<uids.size(); j++) {
-                                    rec.eventCode = i;
-                                    rec.eventTag.string = ent.getKey();
-                                    rec.eventTag.uid = uids.keyAt(j);
-                                    rec.eventTag.poolIdx = uids.valueAt(j);
+                            if (tracker != null) {
+                                if (rec.cmd != HistoryItem.CMD_UPDATE) {
                                     hprinter.printNextItem(pw, rec, baseTime, checkin,
-                                            (flags&DUMP_VERBOSE) != 0);
-                                    rec.wakeReasonTag = null;
-                                    rec.wakelockTag = null;
+                                            (flags & DUMP_VERBOSE) != 0);
+                                    rec.cmd = HistoryItem.CMD_UPDATE;
                                 }
+                                int oldEventCode = rec.eventCode;
+                                HistoryTag oldEventTag = rec.eventTag;
+                                rec.eventTag = new HistoryTag();
+                                for (int i = 0; i < HistoryItem.EVENT_COUNT; i++) {
+                                    Map<String, SparseIntArray> active =
+                                            tracker.getStateForEvent(i);
+                                    if (active == null) {
+                                        continue;
+                                    }
+                                    for (Map.Entry<String, SparseIntArray> ent :
+                                            active.entrySet()) {
+                                        SparseIntArray uids = ent.getValue();
+                                        for (int j = 0; j < uids.size(); j++) {
+                                            rec.eventCode = i;
+                                            rec.eventTag.string = ent.getKey();
+                                            rec.eventTag.uid = uids.keyAt(j);
+                                            rec.eventTag.poolIdx = uids.valueAt(j);
+                                            hprinter.printNextItem(pw, rec, baseTime, checkin,
+                                                    (flags & DUMP_VERBOSE) != 0);
+                                            rec.wakeReasonTag = null;
+                                            rec.wakelockTag = null;
+                                        }
+                                    }
+                                }
+                                rec.eventCode = oldEventCode;
+                                rec.eventTag = oldEventTag;
+                                tracker = null;
                             }
                         }
-                        rec.eventCode = oldEventCode;
-                        rec.eventTag = oldEventTag;
-                        tracker = null;
+                        hprinter.printNextItem(pw, rec, baseTime, checkin,
+                                (flags & DUMP_VERBOSE) != 0);
+                    } else if (false/* && rec.eventCode != HistoryItem.EVENT_NONE */) {
+                        // This is an attempt to aggregate the previous state and generate
+                        // fake events to reflect that state at the point where we start
+                        // printing real events.  It doesn't really work right, so is turned off.
+                        if (tracker == null) {
+                            tracker = new HistoryEventTracker();
+                        }
+                        tracker.updateState(rec.eventCode, rec.eventTag.string,
+                                rec.eventTag.uid, rec.eventTag.poolIdx);
                     }
+                } catch (Throwable t) {
+                    t.printStackTrace(pw);
+                    Slog.wtf(TAG, "Corrupted battery history", t);
+                    break;
                 }
-                hprinter.printNextItem(pw, rec, baseTime, checkin,
-                        (flags&DUMP_VERBOSE) != 0);
-            } else if (false && rec.eventCode != HistoryItem.EVENT_NONE) {
-                // This is an attempt to aggregate the previous state and generate
-                // fake events to reflect that state at the point where we start
-                // printing real events.  It doesn't really work right, so is turned off.
-                if (tracker == null) {
-                    tracker = new HistoryEventTracker();
-                }
-                tracker.updateState(rec.eventCode, rec.eventTag.string,
-                        rec.eventTag.uid, rec.eventTag.poolIdx);
             }
         }
         if (histStart >= 0) {
@@ -7595,25 +7603,19 @@
         if ((flags&DUMP_HISTORY_ONLY) != 0 || !filtering) {
             final long historyTotalSize = getHistoryTotalSize();
             final long historyUsedSize = getHistoryUsedSize();
-            if (startIteratingHistoryLocked()) {
-                try {
-                    pw.print("Battery History (");
-                    pw.print((100*historyUsedSize)/historyTotalSize);
-                    pw.print("% used, ");
-                    printSizeValue(pw, historyUsedSize);
-                    pw.print(" used of ");
-                    printSizeValue(pw, historyTotalSize);
-                    pw.print(", ");
-                    pw.print(getHistoryStringPoolSize());
-                    pw.print(" strings using ");
-                    printSizeValue(pw, getHistoryStringPoolBytes());
-                    pw.println("):");
-                    dumpHistoryLocked(pw, flags, histStart, false);
-                    pw.println();
-                } finally {
-                    finishIteratingHistoryLocked();
-                }
-            }
+            pw.print("Battery History (");
+            pw.print((100 * historyUsedSize) / historyTotalSize);
+            pw.print("% used, ");
+            printSizeValue(pw, historyUsedSize);
+            pw.print(" used of ");
+            printSizeValue(pw, historyTotalSize);
+            pw.print(", ");
+            pw.print(getHistoryStringPoolSize());
+            pw.print(" strings using ");
+            printSizeValue(pw, getHistoryStringPoolBytes());
+            pw.println("):");
+            dumpHistoryLocked(pw, flags, histStart, false);
+            pw.println();
         }
 
         if (filtering && (flags&(DUMP_CHARGED_ONLY|DUMP_DAILY_ONLY)) == 0) {
@@ -7770,28 +7772,24 @@
                 getEndPlatformVersion());
 
         if ((flags & (DUMP_INCLUDE_HISTORY | DUMP_HISTORY_ONLY)) != 0) {
-            if (startIteratingHistoryLocked()) {
-                try {
-                    for (int i=0; i<getHistoryStringPoolSize(); i++) {
-                        pw.print(BATTERY_STATS_CHECKIN_VERSION); pw.print(',');
-                        pw.print(HISTORY_STRING_POOL); pw.print(',');
-                        pw.print(i);
-                        pw.print(",");
-                        pw.print(getHistoryTagPoolUid(i));
-                        pw.print(",\"");
-                        String str = getHistoryTagPoolString(i);
-                        if (str != null) {
-                            str = str.replace("\\", "\\\\");
-                            str = str.replace("\"", "\\\"");
-                            pw.print(str);
-                        }
-                        pw.print("\"");
-                        pw.println();
-                    }
-                    dumpHistoryLocked(pw, flags, histStart, true);
-                } finally {
-                    finishIteratingHistoryLocked();
+            for (int i = 0; i < getHistoryStringPoolSize(); i++) {
+                pw.print(BATTERY_STATS_CHECKIN_VERSION);
+                pw.print(',');
+                pw.print(HISTORY_STRING_POOL);
+                pw.print(',');
+                pw.print(i);
+                pw.print(",");
+                pw.print(getHistoryTagPoolUid(i));
+                pw.print(",\"");
+                String str = getHistoryTagPoolString(i);
+                if (str != null) {
+                    str = str.replace("\\", "\\\\");
+                    str = str.replace("\"", "\\\"");
+                    pw.print(str);
                 }
+                pw.print("\"");
+                pw.println();
+                dumpHistoryLocked(pw, flags, histStart, true);
             }
         }
 
@@ -8331,17 +8329,12 @@
     }
 
     private void dumpProtoHistoryLocked(ProtoOutputStream proto, int flags, long histStart) {
-        if (!startIteratingHistoryLocked()) {
-            return;
-        }
-
         proto.write(BatteryStatsServiceDumpHistoryProto.REPORT_VERSION, CHECKIN_VERSION);
         proto.write(BatteryStatsServiceDumpHistoryProto.PARCEL_VERSION, getParcelVersion());
         proto.write(BatteryStatsServiceDumpHistoryProto.START_PLATFORM_VERSION,
                 getStartPlatformVersion());
         proto.write(BatteryStatsServiceDumpHistoryProto.END_PLATFORM_VERSION,
                 getEndPlatformVersion());
-        try {
             long token;
             // History string pool (HISTORY_STRING_POOL)
             for (int i = 0; i < getHistoryStringPoolSize(); ++i) {
@@ -8353,14 +8346,15 @@
                 proto.end(token);
             }
 
-            // History data (HISTORY_DATA)
-            final HistoryPrinter hprinter = new HistoryPrinter();
-            final HistoryItem rec = new HistoryItem();
-            long lastTime = -1;
-            long baseTime = -1;
-            boolean printed = false;
-            HistoryEventTracker tracker = null;
-            while (getNextHistoryLocked(rec)) {
+        // History data (HISTORY_DATA)
+        final HistoryPrinter hprinter = new HistoryPrinter();
+        long lastTime = -1;
+        long baseTime = -1;
+        boolean printed = false;
+        HistoryEventTracker tracker = null;
+        try (BatteryStatsHistoryIterator iterator = iterateBatteryStatsHistory()) {
+            HistoryItem rec;
+            while ((rec = iterator.next()) != null) {
                 lastTime = rec.time;
                 if (baseTime < 0) {
                     baseTime = lastTime;
@@ -8427,8 +8421,6 @@
                 proto.write(BatteryStatsServiceDumpHistoryProto.CSV_LINES,
                         "NEXT: " + (lastTime + 1));
             }
-        } finally {
-            finishIteratingHistoryLocked();
         }
     }
 
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index e90ba6e..90aca14 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -6210,6 +6210,7 @@
             MOVED_TO_GLOBAL.add(Settings.Global.DEFAULT_DNS_SERVER);
             MOVED_TO_GLOBAL.add(Settings.Global.PREFERRED_NETWORK_MODE);
             MOVED_TO_GLOBAL.add(Settings.Global.WEBVIEW_DATA_REDUCTION_PROXY_KEY);
+            MOVED_TO_GLOBAL.add(Settings.Global.SECURE_FRP_MODE);
         }
 
         /** @hide */
@@ -7072,7 +7073,10 @@
          * device is removed from this mode.
          * <p>
          * Type: int (0 for false, 1 for true)
+         *
+         * @deprecated Use Global.SECURE_FRP_MODE
          */
+        @Deprecated
         @Readable
         public static final String SECURE_FRP_MODE = "secure_frp_mode";
 
@@ -11897,7 +11901,21 @@
         public static final String DEVICE_PROVISIONED = "device_provisioned";
 
         /**
-         * Whether bypassing the device policy management role holder qualifcation is allowed,
+         * Indicates whether the device is under restricted secure FRP mode.
+         * Secure FRP mode is enabled when the device is under FRP. On solving of FRP challenge,
+         * device is removed from this mode.
+         * <p>
+         * Type: int (0 for false, 1 for true)
+         *
+         * @hide
+         */
+        @SystemApi
+        @Readable
+        @SuppressLint("NoSettingsProvider")
+        public static final String SECURE_FRP_MODE = "secure_frp_mode";
+
+        /**
+         * Whether bypassing the device policy management role holder qualification is allowed,
          * (0 = false, 1 = true).
          *
          * @hide
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index 9d95d0b..84a233f 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -394,7 +394,7 @@
             public void resized(ClientWindowFrames frames, boolean reportDraw,
                     MergedConfiguration mergedConfiguration, InsetsState insetsState,
                     boolean forceLayout, boolean alwaysConsumeSystemBars, int displayId,
-                    int syncSeqId, boolean dragResizing) {
+                    int syncSeqId, int resizeMode) {
                 Message msg = mCaller.obtainMessageIO(MSG_WINDOW_RESIZED,
                         reportDraw ? 1 : 0,
                         mergedConfiguration);
diff --git a/core/java/android/telephony/TelephonyRegistryManager.java b/core/java/android/telephony/TelephonyRegistryManager.java
index 0a1538de..519647d 100644
--- a/core/java/android/telephony/TelephonyRegistryManager.java
+++ b/core/java/android/telephony/TelephonyRegistryManager.java
@@ -45,6 +45,7 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.listeners.ListenerExecutor;
+import com.android.internal.telephony.ICarrierConfigChangeListener;
 import com.android.internal.telephony.ICarrierPrivilegesCallback;
 import com.android.internal.telephony.IOnSubscriptionsChangedListener;
 import com.android.internal.telephony.ITelephonyRegistry;
@@ -54,8 +55,10 @@
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Set;
 import java.util.WeakHashMap;
+import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.Executor;
 import java.util.stream.Collectors;
 
@@ -89,6 +92,14 @@
             IOnSubscriptionsChangedListener> mOpportunisticSubscriptionChangedListenerMap
             = new HashMap<>();
 
+    /**
+     * A mapping between {@link CarrierConfigManager.CarrierConfigChangeListener} and its callback
+     * ICarrierConfigChangeListener.
+     */
+    private final ConcurrentHashMap<CarrierConfigManager.CarrierConfigChangeListener,
+                ICarrierConfigChangeListener>
+            mCarrierConfigChangeListenerMap = new ConcurrentHashMap<>();
+
 
     /** @hide **/
     public TelephonyRegistryManager(@NonNull Context context) {
@@ -1412,4 +1423,94 @@
             throw e.rethrowFromSystemServer();
         }
     }
+
+    /**
+     * Register a {@link android.telephony.CarrierConfigManager.CarrierConfigChangeListener} to get
+     * notification when carrier configurations have changed.
+     *
+     * @param executor The executor on which the callback will be executed.
+     * @param listener The CarrierConfigChangeListener to be registered with.
+     */
+    public void addCarrierConfigChangedListener(
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull CarrierConfigManager.CarrierConfigChangeListener listener) {
+        Objects.requireNonNull(executor, "Executor should be non-null.");
+        Objects.requireNonNull(listener, "Listener should be non-null.");
+        if (mCarrierConfigChangeListenerMap.get(listener) != null) {
+            Log.e(TAG, "registerCarrierConfigChangeListener: listener already present");
+            return;
+        }
+
+        ICarrierConfigChangeListener callback = new ICarrierConfigChangeListener.Stub() {
+            @Override
+            public void onCarrierConfigChanged(int slotIndex, int subId, int carrierId,
+                    int specificCarrierId) {
+                Log.d(TAG, "onCarrierConfigChanged call in ICarrierConfigChangeListener callback");
+                final long identify = Binder.clearCallingIdentity();
+                try {
+                    executor.execute(() -> listener.onCarrierConfigChanged(slotIndex, subId,
+                            carrierId, specificCarrierId));
+                } finally {
+                    Binder.restoreCallingIdentity(identify);
+                }
+            }
+        };
+
+        try {
+            sRegistry.addCarrierConfigChangeListener(callback,
+                    mContext.getOpPackageName(), mContext.getAttributionTag());
+            mCarrierConfigChangeListenerMap.put(listener, callback);
+        } catch (RemoteException re) {
+            // system server crashes
+            throw re.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Unregister to stop the notification when carrier configurations changed.
+     *
+     * @param listener The CarrierConfigChangeListener to be unregistered with.
+     */
+    public void removeCarrierConfigChangedListener(
+            @NonNull CarrierConfigManager.CarrierConfigChangeListener listener) {
+        Objects.requireNonNull(listener, "Listener should be non-null.");
+        if (mCarrierConfigChangeListenerMap.get(listener) == null) {
+            Log.e(TAG, "removeCarrierConfigChangedListener: listener was not present");
+            return;
+        }
+
+        try {
+            sRegistry.removeCarrierConfigChangeListener(
+                    mCarrierConfigChangeListenerMap.get(listener), mContext.getOpPackageName());
+            mCarrierConfigChangeListenerMap.remove(listener);
+        } catch (RemoteException re) {
+            // System sever crashes
+            throw re.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Notify the registrants the carrier configurations have changed.
+     *
+     * @param slotIndex         The SIM slot index on which to monitor and get notification.
+     * @param subId             The subscription on the SIM slot. May be
+     *                          {@link SubscriptionManager#INVALID_SUBSCRIPTION_ID}.
+     * @param carrierId         The optional carrier Id, may be
+     *                          {@link TelephonyManager#UNKNOWN_CARRIER_ID}.
+     * @param specificCarrierId The optional specific carrier Id, may be {@link
+     *                          TelephonyManager#UNKNOWN_CARRIER_ID}.
+     */
+    public void notifyCarrierConfigChanged(int slotIndex, int subId, int carrierId,
+            int specificCarrierId) {
+        // Only validate slotIndex, all others are optional and allowed to be invalid
+        if (!SubscriptionManager.isValidPhoneId(slotIndex)) {
+            Log.e(TAG, "notifyCarrierConfigChanged, ignored: invalid slotIndex " + slotIndex);
+            return;
+        }
+        try {
+            sRegistry.notifyCarrierConfigChanged(slotIndex, subId, carrierId, specificCarrierId);
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+    }
 }
diff --git a/core/java/android/view/AttachedSurfaceControl.java b/core/java/android/view/AttachedSurfaceControl.java
index c692981..3b082bc 100644
--- a/core/java/android/view/AttachedSurfaceControl.java
+++ b/core/java/android/view/AttachedSurfaceControl.java
@@ -140,13 +140,13 @@
     }
 
     /**
-     * Returns a SyncTarget that can be used to sync {@link AttachedSurfaceControl} in a
+     * Returns a SurfaceSyncGroup that can be used to sync {@link AttachedSurfaceControl} in a
      * {@link SurfaceSyncGroup}
      *
      * @hide
      */
     @Nullable
-    default SurfaceSyncGroup.SyncTarget getSyncTarget() {
+    default SurfaceSyncGroup getOrCreateSurfaceSyncGroup() {
         return null;
     }
 }
diff --git a/core/java/android/view/IWindow.aidl b/core/java/android/view/IWindow.aidl
index d554514..8e16f24 100644
--- a/core/java/android/view/IWindow.aidl
+++ b/core/java/android/view/IWindow.aidl
@@ -57,7 +57,7 @@
     void resized(in ClientWindowFrames frames, boolean reportDraw,
             in MergedConfiguration newMergedConfiguration, in InsetsState insetsState,
             boolean forceLayout, boolean alwaysConsumeSystemBars, int displayId,
-            int syncSeqId, boolean dragResizing);
+            int syncSeqId, int resizeMode);
 
     /**
      * Called when this window retrieved control over a specified set of insets sources.
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index 0aba80d..6d9f99f 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -457,6 +457,12 @@
     int getDockedStackSide();
 
     /**
+     * Sets the region the user can touch the divider. This region will be excluded from the region
+     * which is used to cause a focus switch when dispatching touch.
+     */
+    void setDockedTaskDividerTouchRegion(in Rect touchableRegion);
+
+    /**
      * Registers a listener that will be called when the pinned task state changes.
      */
     void registerPinnedTaskListener(int displayId, IPinnedTaskListener listener);
diff --git a/core/java/android/view/ScrollCaptureConnection.java b/core/java/android/view/ScrollCaptureConnection.java
index c50f70a..f0c7909 100644
--- a/core/java/android/view/ScrollCaptureConnection.java
+++ b/core/java/android/view/ScrollCaptureConnection.java
@@ -129,7 +129,7 @@
             close();
         }
         mCancellation = null;
-        Trace.asyncTraceForTrackEnd(TRACE_TAG_GRAPHICS, TRACE_TRACK, START_CAPTURE, mTraceId);
+        Trace.asyncTraceForTrackEnd(TRACE_TAG_GRAPHICS, TRACE_TRACK, mTraceId);
     }
 
     @BinderThread
@@ -164,7 +164,7 @@
         } finally {
             mCancellation = null;
         }
-        Trace.asyncTraceForTrackEnd(TRACE_TAG_GRAPHICS, TRACE_TRACK, REQUEST_IMAGE, mTraceId);
+        Trace.asyncTraceForTrackEnd(TRACE_TAG_GRAPHICS, TRACE_TRACK, mTraceId);
     }
 
     @BinderThread
@@ -200,8 +200,8 @@
             mCancellation = null;
             close();
         }
-        Trace.asyncTraceForTrackEnd(TRACE_TAG_GRAPHICS, TRACE_TRACK, END_CAPTURE, mTraceId);
-        Trace.asyncTraceForTrackEnd(TRACE_TAG_GRAPHICS, TRACE_TRACK, SESSION, mTraceId);
+        Trace.asyncTraceForTrackEnd(TRACE_TAG_GRAPHICS, TRACE_TRACK, mTraceId);
+        Trace.asyncTraceForTrackEnd(TRACE_TAG_GRAPHICS, TRACE_TRACK, mTraceId);
     }
 
     @Override
diff --git a/core/java/android/view/SurfaceControlViewHost.java b/core/java/android/view/SurfaceControlViewHost.java
index 1889772..0a134be 100644
--- a/core/java/android/view/SurfaceControlViewHost.java
+++ b/core/java/android/view/SurfaceControlViewHost.java
@@ -404,14 +404,6 @@
     }
 
     /**
-     * @hide
-     */
-    @TestApi
-    public void relayout(WindowManager.LayoutParams attrs) {
-        relayout(attrs, SurfaceControl.Transaction::apply);
-    }
-
-    /**
      * Forces relayout and draw and allows to set a custom callback when it is finished
      * @hide
      */
@@ -423,6 +415,14 @@
     }
 
     /**
+     * @hide
+     */
+    @TestApi
+    public void relayout(WindowManager.LayoutParams attrs) {
+        mViewRoot.setLayoutParams(attrs, false);
+    }
+
+    /**
      * Modify the size of the root view.
      *
      * @param width Width in pixels
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index 33ea92d..9db084e 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -982,8 +982,8 @@
 
                 final boolean redrawNeeded = sizeChanged || creating || hintChanged
                         || (mVisible && !mDrawFinished) || alphaChanged || relativeZChanged;
-                boolean shouldSyncBuffer =
-                        redrawNeeded && viewRoot.wasRelayoutRequested() && viewRoot.isInLocalSync();
+                boolean shouldSyncBuffer = redrawNeeded && viewRoot.wasRelayoutRequested()
+                        && viewRoot.isInWMSRequestedSync();
                 SyncBufferTransactionCallback syncBufferTransactionCallback = null;
                 if (shouldSyncBuffer) {
                     syncBufferTransactionCallback = new SyncBufferTransactionCallback();
@@ -1073,35 +1073,34 @@
     private void handleSyncBufferCallback(SurfaceHolder.Callback[] callbacks,
             SyncBufferTransactionCallback syncBufferTransactionCallback) {
 
-        getViewRootImpl().addToSync((parentSyncGroup, syncBufferCallback) ->
-                redrawNeededAsync(callbacks, () -> {
-                    Transaction t = null;
-                    if (mBlastBufferQueue != null) {
-                        mBlastBufferQueue.stopContinuousSyncTransaction();
-                        t = syncBufferTransactionCallback.waitForTransaction();
-                    }
+        final SurfaceSyncGroup surfaceSyncGroup = new SurfaceSyncGroup();
+        getViewRootImpl().addToSync(surfaceSyncGroup);
+        redrawNeededAsync(callbacks, () -> {
+            Transaction t = null;
+            if (mBlastBufferQueue != null) {
+                mBlastBufferQueue.stopContinuousSyncTransaction();
+                t = syncBufferTransactionCallback.waitForTransaction();
+            }
 
-                    syncBufferCallback.onTransactionReady(t);
-                    onDrawFinished();
-                }));
+            surfaceSyncGroup.onTransactionReady(t);
+            onDrawFinished();
+        });
     }
 
     private void handleSyncNoBuffer(SurfaceHolder.Callback[] callbacks) {
-        final SurfaceSyncGroup syncGroup = new SurfaceSyncGroup();
+        final SurfaceSyncGroup surfaceSyncGroup = new SurfaceSyncGroup();
         synchronized (mSyncGroups) {
-            mSyncGroups.add(syncGroup);
+            mSyncGroups.add(surfaceSyncGroup);
         }
 
-        syncGroup.addToSync((parentSyncGroup, syncBufferCallback) ->
-                redrawNeededAsync(callbacks, () -> {
-                    syncBufferCallback.onTransactionReady(null);
-                    onDrawFinished();
-                    synchronized (mSyncGroups) {
-                        mSyncGroups.remove(syncGroup);
-                    }
-                }));
+        redrawNeededAsync(callbacks, () -> {
+            synchronized (mSyncGroups) {
+                mSyncGroups.remove(surfaceSyncGroup);
+            }
+            surfaceSyncGroup.onTransactionReady(null);
+            onDrawFinished();
+        });
 
-        syncGroup.markSyncReady();
     }
 
     private void redrawNeededAsync(SurfaceHolder.Callback[] callbacks,
@@ -1119,7 +1118,7 @@
         if (viewRoot != null) {
             synchronized (mSyncGroups) {
                 for (SurfaceSyncGroup syncGroup : mSyncGroups) {
-                    viewRoot.mergeSync(syncGroup);
+                    viewRoot.addToSync(syncGroup);
                 }
             }
         }
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index eaa6820..b2973ef 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -51,6 +51,8 @@
 import static android.view.ViewRootImplProto.WINDOW_ATTRIBUTES;
 import static android.view.ViewRootImplProto.WIN_FRAME;
 import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION;
+import static android.view.WindowCallbacks.RESIZE_MODE_FREEFORM;
+import static android.view.WindowCallbacks.RESIZE_MODE_INVALID;
 import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS;
 import static android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS;
 import static android.view.WindowInsetsController.APPEARANCE_LOW_PROFILE_BARS;
@@ -513,6 +515,7 @@
     private boolean mPendingDragResizing;
     private boolean mDragResizing;
     private boolean mInvalidateRootRequested;
+    private int mResizeMode = RESIZE_MODE_INVALID;
     private int mCanvasOffsetX;
     private int mCanvasOffsetY;
     private boolean mActivityRelaunched;
@@ -592,19 +595,21 @@
     String mLastPerformDrawSkippedReason;
     /** The reason the last call to performTraversals() returned without drawing */
     String mLastPerformTraversalsSkipDrawReason;
-    /** The state of the local sync, if one is in progress. Can be one of the states below. */
-    int mLocalSyncState;
+    /** The state of the WMS requested sync, if one is in progress. Can be one of the states
+     * below. */
+    int mWmsRequestSyncGroupState;
 
-    // The possible states of the local sync, see createSyncIfNeeded()
-    private final int LOCAL_SYNC_NONE = 0;
-    private final int LOCAL_SYNC_PENDING = 1;
-    private final int LOCAL_SYNC_RETURNED = 2;
-    private final int LOCAL_SYNC_MERGED = 3;
+    // The possible states of the WMS requested sync, see createSyncIfNeeded()
+    private static final int WMS_SYNC_NONE = 0;
+    private static final int WMS_SYNC_PENDING = 1;
+    private static final int WMS_SYNC_RETURNED = 2;
+    private static final int WMS_SYNC_MERGED = 3;
 
     /**
-     * Set whether the draw should send the buffer to system server. When set to true, VRI will
-     * create a sync transaction with BBQ and send the resulting buffer to system server. If false,
-     * VRI will not try to sync a buffer in BBQ, but still report when a draw occurred.
+     * Set whether the requested SurfaceSyncGroup should sync the buffer. When set to true, VRI will
+     * create a sync transaction with BBQ and send the resulting buffer back to the
+     * SurfaceSyncGroup. If false, VRI will not try to sync a buffer in BBQ, but still report when a
+     * draw occurred.
      */
     private boolean mSyncBuffer = false;
 
@@ -846,8 +851,19 @@
         return mHandwritingInitiator;
     }
 
-    private SurfaceSyncGroup mSyncGroup;
-    private SurfaceSyncGroup.TransactionReadyCallback mTransactionReadyCallback;
+    /**
+     * A SurfaceSyncGroup that is created when WMS requested to sync the buffer
+     */
+    private SurfaceSyncGroup mWmsRequestSyncGroup;
+
+    /**
+     * The SurfaceSyncGroup that represents the active VRI SurfaceSyncGroup. This is non null if
+     * anyone requested the SurfaceSyncGroup for this VRI to ensure that anyone trying to sync with
+     * this VRI are collected together. The SurfaceSyncGroup is cleared when the VRI draws since
+     * that is the stop point where all changes are have been applied. A new SurfaceSyncGroup is
+     * created after that point when something wants to sync VRI again.
+     */
+    private SurfaceSyncGroup mActiveSurfaceSyncGroup;
 
     private static final Object sSyncProgressLock = new Object();
     // The count needs to be static since it's used to enable or disable RT animations which is
@@ -1790,7 +1806,7 @@
         CompatibilityInfo.applyOverrideScaleIfNeeded(mergedConfiguration);
         final boolean forceNextWindowRelayout = args.argi1 != 0;
         final int displayId = args.argi3;
-        final boolean dragResizing = args.argi5 != 0;
+        final int resizeMode = args.argi5;
 
         final Rect frame = frames.frame;
         final Rect displayFrame = frames.displayFrame;
@@ -1806,14 +1822,16 @@
         final boolean attachedFrameChanged = LOCAL_LAYOUT
                 && !Objects.equals(mTmpFrames.attachedFrame, attachedFrame);
         final boolean displayChanged = mDisplay.getDisplayId() != displayId;
+        final boolean resizeModeChanged = mResizeMode != resizeMode;
         final boolean compatScaleChanged = mTmpFrames.compatScale != compatScale;
         if (msg == MSG_RESIZED && !frameChanged && !configChanged && !attachedFrameChanged
-                && !displayChanged && !forceNextWindowRelayout
+                && !displayChanged && !resizeModeChanged && !forceNextWindowRelayout
                 && !compatScaleChanged) {
             return;
         }
 
-        mPendingDragResizing = dragResizing;
+        mPendingDragResizing = resizeMode != RESIZE_MODE_INVALID;
+        mResizeMode = resizeMode;
         mTmpFrames.compatScale = compatScale;
         mInvCompatScale = 1f / compatScale;
 
@@ -3010,7 +3028,7 @@
                         frame.width() < desiredWindowWidth && frame.width() != mWidth)
                 || (lp.height == ViewGroup.LayoutParams.WRAP_CONTENT &&
                         frame.height() < desiredWindowHeight && frame.height() != mHeight));
-        windowShouldResize |= mDragResizing && mPendingDragResizing;
+        windowShouldResize |= mDragResizing && mResizeMode == RESIZE_MODE_FREEFORM;
 
         // If the activity was just relaunched, it might have unfrozen the task bounds (while
         // relaunching), so we need to force a call into window manager to pick up the latest
@@ -3257,7 +3275,7 @@
                                         && mWinFrame.height() == mPendingBackDropFrame.height();
                         // TODO: Need cutout?
                         startDragResizing(mPendingBackDropFrame, !backdropSizeMatchesFrame,
-                                mAttachInfo.mContentInsets, mAttachInfo.mStableInsets);
+                                mAttachInfo.mContentInsets, mAttachInfo.mStableInsets, mResizeMode);
                     } else {
                         // We shouldn't come here, but if we come we should end the resize.
                         endDragResizing();
@@ -3638,6 +3656,12 @@
         boolean cancelAndRedraw = cancelDueToPreDrawListener
                  || (cancelDraw && mDrewOnceForSync);
         if (!cancelAndRedraw) {
+            // A sync was already requested before the WMS requested sync. This means we need to
+            // sync the buffer, regardless if WMS wants to sync the buffer.
+            if (mActiveSurfaceSyncGroup != null) {
+                mSyncBuffer = true;
+            }
+
             createSyncIfNeeded();
             mDrewOnceForSync = true;
         }
@@ -3651,8 +3675,8 @@
                 mPendingTransitions.clear();
             }
 
-            if (mTransactionReadyCallback != null) {
-                mTransactionReadyCallback.onTransactionReady(null);
+            if (mActiveSurfaceSyncGroup != null) {
+                mActiveSurfaceSyncGroup.onTransactionReady(null);
             }
         } else if (cancelAndRedraw) {
             mLastPerformTraversalsSkipDrawReason = cancelDueToPreDrawListener
@@ -3667,8 +3691,8 @@
                 }
                 mPendingTransitions.clear();
             }
-            if (!performDraw() && mTransactionReadyCallback != null) {
-                mTransactionReadyCallback.onTransactionReady(null);
+            if (!performDraw() && mActiveSurfaceSyncGroup != null) {
+                mActiveSurfaceSyncGroup.onTransactionReady(null);
             }
         }
 
@@ -3682,39 +3706,40 @@
         if (!cancelAndRedraw) {
             mReportNextDraw = false;
             mLastReportNextDrawReason = null;
-            mTransactionReadyCallback = null;
+            mActiveSurfaceSyncGroup = null;
             mSyncBuffer = false;
-            if (isInLocalSync()) {
-                mSyncGroup.markSyncReady();
-                mSyncGroup = null;
-                mLocalSyncState = LOCAL_SYNC_NONE;
+            if (isInWMSRequestedSync()) {
+                mWmsRequestSyncGroup.onTransactionReady(null);
+                mWmsRequestSyncGroup = null;
+                mWmsRequestSyncGroupState = WMS_SYNC_NONE;
             }
         }
     }
 
     private void createSyncIfNeeded() {
-        // Started a sync already or there's nothing needing to sync
-        if (isInLocalSync() || !mReportNextDraw) {
+        // WMS requested sync already started or there's nothing needing to sync
+        if (isInWMSRequestedSync() || !mReportNextDraw) {
             return;
         }
 
         final int seqId = mSyncSeqId;
-        mLocalSyncState = LOCAL_SYNC_PENDING;
-        mSyncGroup = new SurfaceSyncGroup(transaction -> {
-            mLocalSyncState = LOCAL_SYNC_RETURNED;
+        mWmsRequestSyncGroupState = WMS_SYNC_PENDING;
+        mWmsRequestSyncGroup = new SurfaceSyncGroup(t -> {
+            mWmsRequestSyncGroupState = WMS_SYNC_RETURNED;
             // Callback will be invoked on executor thread so post to main thread.
             mHandler.postAtFrontOfQueue(() -> {
-                if (transaction != null) {
-                    mSurfaceChangedTransaction.merge(transaction);
+                if (t != null) {
+                    mSurfaceChangedTransaction.merge(t);
                 }
-                mLocalSyncState = LOCAL_SYNC_MERGED;
+                mWmsRequestSyncGroupState = WMS_SYNC_MERGED;
                 reportDrawFinished(seqId);
             });
         });
         if (DEBUG_BLAST) {
-            Log.d(mTag, "Setup new sync id=" + mSyncGroup);
+            Log.d(mTag, "Setup new sync id=" + mWmsRequestSyncGroup);
         }
-        mSyncGroup.addToSync(mSyncTarget);
+
+        mWmsRequestSyncGroup.addToSync(this);
         notifySurfaceSyncStarted();
     }
 
@@ -4365,19 +4390,11 @@
         return mAttachInfo.mThreadedRenderer != null && mAttachInfo.mThreadedRenderer.isEnabled();
     }
 
-    void addToSync(SurfaceSyncGroup.SyncTarget syncable) {
-        if (!isInLocalSync()) {
-            return;
-        }
-        mSyncGroup.addToSync(syncable);
-    }
-
     /**
-     * This VRI is currently in the middle of a sync request, but specifically one initiated from
-     * within VRI.
+     * This VRI is currently in the middle of a sync request that was initiated by WMS.
      */
-    public boolean isInLocalSync() {
-        return mSyncGroup != null;
+    public boolean isInWMSRequestedSync() {
+        return mWmsRequestSyncGroup != null;
     }
 
     private void addFrameCommitCallbackIfNeeded() {
@@ -4444,7 +4461,7 @@
             return false;
         }
 
-        final boolean fullRedrawNeeded = mFullRedrawNeeded || mTransactionReadyCallback != null;
+        final boolean fullRedrawNeeded = mFullRedrawNeeded || mActiveSurfaceSyncGroup != null;
         mFullRedrawNeeded = false;
 
         mIsDrawing = true;
@@ -4452,9 +4469,9 @@
 
         addFrameCommitCallbackIfNeeded();
 
-        boolean usingAsyncReport = isHardwareEnabled() && mTransactionReadyCallback != null;
+        boolean usingAsyncReport = isHardwareEnabled() && mActiveSurfaceSyncGroup != null;
         if (usingAsyncReport) {
-            registerCallbacksForSync(mSyncBuffer, mTransactionReadyCallback);
+            registerCallbacksForSync(mSyncBuffer, mActiveSurfaceSyncGroup);
         } else if (mHasPendingTransactions) {
             // These callbacks are only needed if there's no sync involved and there were calls to
             // applyTransactionOnDraw. These callbacks check if the draw failed for any reason and
@@ -4505,11 +4522,10 @@
             }
 
             if (mSurfaceHolder != null && mSurface.isValid()) {
-                final SurfaceSyncGroup.TransactionReadyCallback transactionReadyCallback =
-                        mTransactionReadyCallback;
+                final SurfaceSyncGroup surfaceSyncGroup = mActiveSurfaceSyncGroup;
                 SurfaceCallbackHelper sch = new SurfaceCallbackHelper(() ->
-                        mHandler.post(() -> transactionReadyCallback.onTransactionReady(null)));
-                mTransactionReadyCallback = null;
+                        mHandler.post(() -> surfaceSyncGroup.onTransactionReady(null)));
+                mActiveSurfaceSyncGroup = null;
 
                 SurfaceHolder.Callback callbacks[] = mSurfaceHolder.getCallbacks();
 
@@ -4520,8 +4536,8 @@
                 }
             }
         }
-        if (mTransactionReadyCallback != null && !usingAsyncReport) {
-            mTransactionReadyCallback.onTransactionReady(null);
+        if (mActiveSurfaceSyncGroup != null && !usingAsyncReport) {
+            mActiveSurfaceSyncGroup.onTransactionReady(null);
         }
         if (mPerformContentCapture) {
             performContentCaptureInitialReport();
@@ -8611,8 +8627,8 @@
             writer.println(innerPrefix + "mLastPerformDrawFailedReason="
                 + mLastPerformDrawSkippedReason);
         }
-        if (mLocalSyncState != LOCAL_SYNC_NONE) {
-            writer.println(innerPrefix + "mLocalSyncState=" + mLocalSyncState);
+        if (mWmsRequestSyncGroupState != WMS_SYNC_NONE) {
+            writer.println(innerPrefix + "mWmsRequestSyncGroupState=" + mWmsRequestSyncGroupState);
         }
         writer.println(innerPrefix + "mLastReportedMergedConfiguration="
                 + mLastReportedMergedConfiguration);
@@ -8825,7 +8841,7 @@
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     private void dispatchResized(ClientWindowFrames frames, boolean reportDraw,
             MergedConfiguration mergedConfiguration, InsetsState insetsState, boolean forceLayout,
-            boolean alwaysConsumeSystemBars, int displayId, int syncSeqId, boolean dragResizing) {
+            boolean alwaysConsumeSystemBars, int displayId, int syncSeqId, int resizeMode) {
         Message msg = mHandler.obtainMessage(reportDraw ? MSG_RESIZED_REPORT : MSG_RESIZED);
         SomeArgs args = SomeArgs.obtain();
         final boolean sameProcessCall = (Binder.getCallingPid() == android.os.Process.myPid());
@@ -8847,7 +8863,7 @@
         args.argi2 = alwaysConsumeSystemBars ? 1 : 0;
         args.argi3 = displayId;
         args.argi4 = syncSeqId;
-        args.argi5 = dragResizing ? 1 : 0;
+        args.argi5 = resizeMode;
 
         msg.obj = args;
         mHandler.sendMessage(msg);
@@ -10239,11 +10255,11 @@
         public void resized(ClientWindowFrames frames, boolean reportDraw,
                 MergedConfiguration mergedConfiguration, InsetsState insetsState,
                 boolean forceLayout, boolean alwaysConsumeSystemBars, int displayId, int syncSeqId,
-                boolean dragResizing) {
+                int resizeMode) {
             final ViewRootImpl viewAncestor = mViewAncestor.get();
             if (viewAncestor != null) {
                 viewAncestor.dispatchResized(frames, reportDraw, mergedConfiguration, insetsState,
-                        forceLayout, alwaysConsumeSystemBars, displayId, syncSeqId, dragResizing);
+                        forceLayout, alwaysConsumeSystemBars, displayId, syncSeqId, resizeMode);
             }
         }
 
@@ -10448,13 +10464,13 @@
      * Start a drag resizing which will inform all listeners that a window resize is taking place.
      */
     private void startDragResizing(Rect initialBounds, boolean fullscreen, Rect systemInsets,
-            Rect stableInsets) {
+            Rect stableInsets, int resizeMode) {
         if (!mDragResizing) {
             mDragResizing = true;
             if (mUseMTRenderer) {
                 for (int i = mWindowCallbacks.size() - 1; i >= 0; i--) {
                     mWindowCallbacks.get(i).onWindowDragResizeStart(
-                            initialBounds, fullscreen, systemInsets, stableInsets);
+                            initialBounds, fullscreen, systemInsets, stableInsets, resizeMode);
                 }
             }
             mFullRedrawNeeded = true;
@@ -11200,7 +11216,7 @@
     }
 
     private void registerCallbacksForSync(boolean syncBuffer,
-            final SurfaceSyncGroup.TransactionReadyCallback transactionReadyCallback) {
+            final SurfaceSyncGroup surfaceSyncGroup) {
         if (!isHardwareEnabled()) {
             return;
         }
@@ -11227,7 +11243,7 @@
                 // pendingDrawFinished.
                 if ((syncResult
                         & (SYNC_LOST_SURFACE_REWARD_IF_FOUND | SYNC_CONTEXT_IS_STOPPED)) != 0) {
-                    transactionReadyCallback.onTransactionReady(
+                    surfaceSyncGroup.onTransactionReady(
                             mBlastBufferQueue.gatherPendingTransactions(frame));
                     return null;
                 }
@@ -11238,7 +11254,7 @@
 
                 if (syncBuffer) {
                     mBlastBufferQueue.syncNextTransaction(
-                            transactionReadyCallback::onTransactionReady);
+                            surfaceSyncGroup::onTransactionReady);
                 }
 
                 return didProduceBuffer -> {
@@ -11258,7 +11274,7 @@
                         // since the frame didn't draw on this vsync. It's possible the frame will
                         // draw later, but it's better to not be sync than to block on a frame that
                         // may never come.
-                        transactionReadyCallback.onTransactionReady(
+                        surfaceSyncGroup.onTransactionReady(
                                 mBlastBufferQueue.gatherPendingTransactions(frame));
                         return;
                     }
@@ -11267,35 +11283,23 @@
                     // syncNextTransaction callback. Instead, just report back to the Syncer so it
                     // knows that this sync request is complete.
                     if (!syncBuffer) {
-                        transactionReadyCallback.onTransactionReady(null);
+                        surfaceSyncGroup.onTransactionReady(null);
                     }
                 };
             }
         });
     }
 
-    public final SurfaceSyncGroup.SyncTarget mSyncTarget = new SurfaceSyncGroup.SyncTarget() {
-        @Override
-        public void onAddedToSyncGroup(SurfaceSyncGroup parentSyncGroup,
-                SurfaceSyncGroup.TransactionReadyCallback transactionReadyCallback) {
-            updateSyncInProgressCount(parentSyncGroup);
-            if (!isInLocalSync()) {
-                // Always sync the buffer if the sync request did not come from VRI.
-                mSyncBuffer = true;
-            }
-
-            if (mTransactionReadyCallback != null) {
-                Log.d(mTag, "Already set sync for the next draw.");
-                mTransactionReadyCallback.onTransactionReady(null);
-            }
-            if (DEBUG_BLAST) {
-                Log.d(mTag, "Setting syncFrameCallback");
-            }
-            mTransactionReadyCallback = transactionReadyCallback;
+    @Override
+    public SurfaceSyncGroup getOrCreateSurfaceSyncGroup() {
+        if (mActiveSurfaceSyncGroup == null) {
+            mActiveSurfaceSyncGroup = new SurfaceSyncGroup();
+            updateSyncInProgressCount(mActiveSurfaceSyncGroup);
             if (!mIsInTraversal && !mTraversalScheduled) {
                 scheduleTraversals();
             }
         }
+        return mActiveSurfaceSyncGroup;
     };
 
     private final Executor mSimpleExecutor = Runnable::run;
@@ -11320,15 +11324,10 @@
         });
     }
 
-    @Override
-    public SurfaceSyncGroup.SyncTarget getSyncTarget() {
-        return mSyncTarget;
-    }
-
-    void mergeSync(SurfaceSyncGroup otherSyncGroup) {
-        if (!isInLocalSync()) {
+    void addToSync(SurfaceSyncGroup syncable) {
+        if (mActiveSurfaceSyncGroup == null) {
             return;
         }
-        mSyncGroup.merge(otherSyncGroup);
+        mActiveSurfaceSyncGroup.addToSync(syncable, false /* parentSyncGroupMerge */);
     }
 }
diff --git a/core/java/android/view/WindowCallbacks.java b/core/java/android/view/WindowCallbacks.java
index 94b2d93..a7f0ef0 100644
--- a/core/java/android/view/WindowCallbacks.java
+++ b/core/java/android/view/WindowCallbacks.java
@@ -28,6 +28,22 @@
  */
 public interface WindowCallbacks {
 
+    int RESIZE_MODE_INVALID = -1;
+
+    /**
+     * The window is being resized by dragging one of the window corners,
+     * in this case the surface would be fullscreen-sized. The client should
+     * render to the actual frame location (instead of (0,curScrollY)).
+     */
+    int RESIZE_MODE_FREEFORM = 0;
+
+    /**
+     * The window is being resized by dragging on the docked divider. The client should render
+     * at (0, 0) and extend its background to the background frame passed into
+     * {@link IWindow#resized}.
+     */
+    int RESIZE_MODE_DOCKED_DIVIDER = 1;
+
     /**
      * Called by the system when the window got changed by the user, before the layouter got called.
      * It also gets called when the insets changed, or when the window switched between a fullscreen
@@ -53,7 +69,7 @@
      * @param stableInsets The stable insets for the window.
      */
     void onWindowDragResizeStart(Rect initialBounds, boolean fullscreen, Rect systemInsets,
-            Rect stableInsets);
+            Rect stableInsets, int resizeMode);
 
     /**
      * Called when a drag resize ends.
diff --git a/core/java/android/view/WindowlessWindowManager.java b/core/java/android/view/WindowlessWindowManager.java
index 2d6c1d9..69340aa 100644
--- a/core/java/android/view/WindowlessWindowManager.java
+++ b/core/java/android/view/WindowlessWindowManager.java
@@ -16,6 +16,8 @@
 
 package android.view;
 
+import static android.view.WindowCallbacks.RESIZE_MODE_INVALID;
+
 import android.annotation.Nullable;
 import android.content.res.Configuration;
 import android.graphics.PixelFormat;
@@ -548,7 +550,7 @@
                 mTmpConfig.setConfiguration(mConfiguration, mConfiguration);
                 s.mClient.resized(mTmpFrames, false /* reportDraw */, mTmpConfig, state,
                         false /* forceLayout */, false /* alwaysConsumeSystemBars */, s.mDisplayId,
-                        Integer.MAX_VALUE, false /* dragResizing */);
+                        Integer.MAX_VALUE, RESIZE_MODE_INVALID);
             } catch (RemoteException e) {
                 // Too bad
             }
diff --git a/core/java/android/view/accessibility/AccessibilityDisplayProxy.java b/core/java/android/view/accessibility/AccessibilityDisplayProxy.java
index 44b6deb..a757236 100644
--- a/core/java/android/view/accessibility/AccessibilityDisplayProxy.java
+++ b/core/java/android/view/accessibility/AccessibilityDisplayProxy.java
@@ -242,11 +242,7 @@
             super(context, executor, new AccessibilityService.Callbacks() {
                 @Override
                 public void onAccessibilityEvent(AccessibilityEvent event) {
-                    // TODO(254545943): Remove check when event processing is done more upstream in
-                    // AccessibilityManagerService.
-                    if (event.getDisplayId() == mDisplayId) {
-                        AccessibilityDisplayProxy.this.onAccessibilityEvent(event);
-                    }
+                    AccessibilityDisplayProxy.this.onAccessibilityEvent(event);
                 }
 
                 @Override
diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java
index 423c560..9abbba9 100644
--- a/core/java/android/view/accessibility/AccessibilityManager.java
+++ b/core/java/android/view/accessibility/AccessibilityManager.java
@@ -25,6 +25,7 @@
 import android.accessibilityservice.AccessibilityShortcutInfo;
 import android.annotation.CallbackExecutor;
 import android.annotation.ColorInt;
+import android.annotation.FloatRange;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -75,6 +76,7 @@
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
+import java.util.Objects;
 import java.util.concurrent.Executor;
 
 /**
@@ -138,6 +140,21 @@
     public static final int AUTOCLICK_DELAY_DEFAULT = 600;
 
     /**
+     * The contrast is defined as a float in [-1, 1], with a default value of 0.
+     * @hide
+     */
+    public static final float CONTRAST_MIN_VALUE = -1f;
+
+    /** @hide */
+    public static final float CONTRAST_MAX_VALUE = 1f;
+
+    /** @hide */
+    public static final float CONTRAST_DEFAULT_VALUE = 0f;
+
+    /** @hide */
+    public static final float CONTRAST_NOT_SET = Float.MIN_VALUE;
+
+    /**
      * Activity action: Launch UI to manage which accessibility service or feature is assigned
      * to the navigation bar Accessibility button.
      * <p>
@@ -246,6 +263,8 @@
     @UnsupportedAppUsage(trackingBug = 123768939L)
     boolean mIsHighTextContrastEnabled;
 
+    private float mUiContrast;
+
     boolean mIsAudioDescriptionByDefaultRequested;
 
     // accessibility tracing state
@@ -270,6 +289,9 @@
     private final ArrayMap<HighTextContrastChangeListener, Handler>
             mHighTextContrastStateChangeListeners = new ArrayMap<>();
 
+    private final ArrayMap<UiContrastChangeListener, Executor>
+            mUiContrastChangeListeners = new ArrayMap<>();
+
     private final ArrayMap<AccessibilityServicesStateChangeListener, Executor>
             mServicesStateChangeListeners = new ArrayMap<>();
 
@@ -336,7 +358,7 @@
          *
          * @param manager The manager that is calling back
          */
-        void onAccessibilityServicesStateChanged(@NonNull  AccessibilityManager manager);
+        void onAccessibilityServicesStateChanged(@NonNull AccessibilityManager manager);
     }
 
     /**
@@ -358,6 +380,21 @@
     }
 
     /**
+     * Listener for the UI contrast. To listen for changes to
+     * the UI contrast on the device, implement this interface and
+     * register it with the system by calling {@link #addUiContrastChangeListener}.
+     */
+    public interface UiContrastChangeListener {
+
+        /**
+         * Called when the color contrast enabled state changes.
+         *
+         * @param uiContrast The color contrast as in {@link #getUiContrast}
+         */
+        void onUiContrastChanged(@FloatRange(from = -1.0f, to = 1.0f) float uiContrast);
+    }
+
+    /**
      * Listener for the audio description by default state. To listen for
      * changes to the audio description by default state on the device,
      * implement this interface and register it with the system by calling
@@ -471,6 +508,16 @@
                 updateFocusAppearanceLocked(strokeWidth, color);
             }
         }
+
+        @Override
+        public void setUiContrast(float contrast) {
+            synchronized (mLock) {
+                // if value changed in the settings, update the cached value and notify listeners
+                if (Math.abs(mUiContrast - contrast) < 1e-10) return;
+                mUiContrast = contrast;
+            }
+            mHandler.obtainMessage(MyCallback.MSG_NOTIFY_CONTRAST_CHANGED).sendToTarget();
+        }
     };
 
     /**
@@ -641,7 +688,7 @@
     /**
      * Returns if the high text contrast in the system is enabled.
      * <p>
-     * <strong>Note:</strong> You need to query this only if you application is
+     * <strong>Note:</strong> You need to query this only if your application is
      * doing its own rendering and does not rely on the platform rendering pipeline.
      * </p>
      *
@@ -661,6 +708,24 @@
     }
 
     /**
+     * Returns the color contrast for the user.
+     * <p>
+     * <strong>Note:</strong> You need to query this only if your application is
+     * doing its own rendering and does not rely on the platform rendering pipeline.
+     * </p>
+     * @return The color contrast, float in [-1, 1] where
+     *          0 corresponds to the default contrast
+     *         -1 corresponds to the minimum contrast that the user can set
+     *          1 corresponds to the maximum contrast that the user can set
+     */
+    @FloatRange(from = -1.0f, to = 1.0f)
+    public float getUiContrast() {
+        synchronized (mLock) {
+            return mUiContrast;
+        }
+    }
+
+    /**
      * Sends an {@link AccessibilityEvent}.
      *
      * @param event The event to send.
@@ -1240,6 +1305,35 @@
     }
 
     /**
+     * Registers a {@link UiContrastChangeListener} for the current user.
+     *
+     * @param executor The executor on which the listener should be called back.
+     * @param listener The listener.
+     */
+    public void addUiContrastChangeListener(
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull UiContrastChangeListener listener) {
+        Objects.requireNonNull(executor);
+        Objects.requireNonNull(listener);
+        synchronized (mLock) {
+            mUiContrastChangeListeners.put(listener, executor);
+        }
+    }
+
+    /**
+     * Unregisters a {@link UiContrastChangeListener} for the current user.
+     * If the listener was not registered, does nothing and returns.
+     *
+     * @param listener The listener to unregister.
+     */
+    public void removeUiContrastChangeListener(@NonNull UiContrastChangeListener listener) {
+        Objects.requireNonNull(listener);
+        synchronized (mLock) {
+            mUiContrastChangeListeners.remove(listener);
+        }
+    }
+
+    /**
      * Registers a {@link AudioDescriptionRequestedChangeListener}
      * for changes in the audio description by default state of the system.
      * The value could be read via {@link #isAudioDescriptionRequested}.
@@ -2004,6 +2098,7 @@
             mRelevantEventTypes = IntPair.second(userStateAndRelevantEvents);
             updateUiTimeout(service.getRecommendedTimeoutMillis());
             updateFocusAppearanceLocked(service.getFocusStrokeWidth(), service.getFocusColor());
+            mUiContrast = service.getUiContrast();
             mService = service;
         } catch (RemoteException re) {
             Log.e(LOG_TAG, "AccessibilityManagerService is dead", re);
@@ -2082,6 +2177,22 @@
     }
 
     /**
+     * Notifies the registered {@link UiContrastChangeListener}s if the value changed.
+     */
+    private void notifyUiContrastChanged() {
+        final ArrayMap<UiContrastChangeListener, Executor> listeners;
+        synchronized (mLock) {
+            listeners = new ArrayMap<>(mUiContrastChangeListeners);
+        }
+
+        listeners.entrySet().forEach(entry -> {
+            UiContrastChangeListener listener = entry.getKey();
+            Executor executor = entry.getValue();
+            executor.execute(() -> listener.onUiContrastChanged(mUiContrast));
+        });
+    }
+
+    /**
      * Notifies the registered {@link AudioDescriptionStateChangeListener}s.
      */
     private void notifyAudioDescriptionbyDefaultStateChanged() {
@@ -2171,6 +2282,7 @@
 
     private final class MyCallback implements Handler.Callback {
         public static final int MSG_SET_STATE = 1;
+        public static final int MSG_NOTIFY_CONTRAST_CHANGED = 2;
 
         @Override
         public boolean handleMessage(Message message) {
@@ -2182,6 +2294,9 @@
                         setStateLocked(state);
                     }
                 } break;
+                case MSG_NOTIFY_CONTRAST_CHANGED: {
+                    notifyUiContrastChanged();
+                }
             }
             return true;
         }
diff --git a/core/java/android/view/accessibility/IAccessibilityManager.aidl b/core/java/android/view/accessibility/IAccessibilityManager.aidl
index 364c7c8..c2d899a 100644
--- a/core/java/android/view/accessibility/IAccessibilityManager.aidl
+++ b/core/java/android/view/accessibility/IAccessibilityManager.aidl
@@ -118,4 +118,6 @@
 
     // Used by UiAutomation for tests on the InputFilter
     void injectInputEventToInputFilter(in InputEvent event);
+
+    float getUiContrast();
 }
diff --git a/core/java/android/view/accessibility/IAccessibilityManagerClient.aidl b/core/java/android/view/accessibility/IAccessibilityManagerClient.aidl
index 041399c..931f862 100644
--- a/core/java/android/view/accessibility/IAccessibilityManagerClient.aidl
+++ b/core/java/android/view/accessibility/IAccessibilityManagerClient.aidl
@@ -31,4 +31,6 @@
     void setRelevantEventTypes(int eventTypes);
 
     void setFocusAppearance(int strokeWidth, int color);
+
+    void setUiContrast(float contrast);
 }
diff --git a/core/java/android/view/inputmethod/TextBoundsInfo.java b/core/java/android/view/inputmethod/TextBoundsInfo.java
index 4e87405..dd05543 100644
--- a/core/java/android/view/inputmethod/TextBoundsInfo.java
+++ b/core/java/android/view/inputmethod/TextBoundsInfo.java
@@ -25,6 +25,7 @@
 import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.text.Layout;
 import android.text.SegmentFinder;
 
 import com.android.internal.util.ArrayUtils;
@@ -314,6 +315,554 @@
     }
 
     /**
+     * Return the index of the closest character to the given position.
+     * It's similar to the text layout API {@link Layout#getOffsetForHorizontal(int, float)}.
+     * And it's mainly used to find the cursor index (the index of the character before which the
+     * cursor should be placed) for the given position. It's guaranteed that the returned index is
+     * a grapheme break. Check {@link #getGraphemeSegmentFinder()} for more information.
+     *
+     * <p>It's assumed that the editor lays out text in horizontal lines from top to bottom and each
+     * line is laid out according to the display algorithm specified in
+     * <a href="https://unicode.org/reports/tr9/#Basic_Display_Algorithm"> unicode bidirectional
+     * algorithm</a>.
+     * </p>
+     *
+     * <p> This method won't check the text ranges whose line information is missing. For example,
+     * the {@link TextBoundsInfo}'s range is from index 5 to 15. If the associated
+     * {@link SegmentFinder} only identifies one line range from 7 to 12. Then this method
+     * won't check the text in the ranges of [5, 7) and [12, 15).
+     * </p>
+     *
+     * @param x the x coordinates of the interested location, in the editor's coordinates.
+     * @param y the y coordinates of the interested location, in the editor's coordinates.
+     * @return the index of the character whose position is closest to the given location. It will
+     * return -1 if it can't find a character.
+     *
+     * @see Layout#getOffsetForHorizontal(int, float)
+     */
+    public int getOffsetForPosition(float x, float y) {
+        final int[] lineRange = new int[2];
+        final RectF lineBounds = new RectF();
+        getLineInfo(y, lineRange, lineBounds);
+        // No line is found, return -1;
+        if (lineRange[0] == -1 || lineRange[1] == -1) return -1;
+        final int lineStart = lineRange[0];
+        final int lineEnd = lineRange[1];
+
+        final boolean lineEndsWithLinefeed =
+                (getCharacterFlags(lineEnd - 1) & FLAG_CHARACTER_LINEFEED) != 0;
+
+        // Consider the following 2 cases:
+        // Case 1:
+        //   Text: "AB\nCD"
+        //   Layout: AB
+        //           CD
+        // Case 2:
+        //   Text: "ABCD"
+        //   Layout: AB
+        //           CD
+        // If user wants to insert a 'X' character at the end of the first line:
+        //   In case 1, 'X' is inserted before the last character '\n'.
+        //   In case 2, 'X' is inserted after the last character 'B'.
+        // So if a line ends with linefeed, it shouldn't check the cursor position after the last
+        // character.
+        final int lineLimit;
+        if (lineEndsWithLinefeed) {
+            lineLimit = lineEnd;
+        } else {
+            lineLimit = lineEnd + 1;
+        }
+        // Point graphemeStart to the start of the first grapheme segment intersects with the line.
+        int graphemeStart = mGraphemeSegmentFinder.nextEndBoundary(lineStart);
+        // The grapheme information is missing.
+        if (graphemeStart == SegmentFinder.DONE) return -1;
+        graphemeStart = mGraphemeSegmentFinder.previousStartBoundary(graphemeStart);
+
+        int target = -1;
+        float minDistance = Float.MAX_VALUE;
+        while (graphemeStart != SegmentFinder.DONE && graphemeStart < lineLimit) {
+            if (graphemeStart >= lineStart) {
+                float cursorPosition = getCursorHorizontalPosition(graphemeStart, lineStart,
+                        lineEnd, lineBounds.left, lineBounds.right);
+                final float distance = Math.abs(cursorPosition - x);
+                if (distance < minDistance) {
+                    minDistance = distance;
+                    target = graphemeStart;
+                }
+            }
+            graphemeStart = mGraphemeSegmentFinder.nextStartBoundary(graphemeStart);
+        }
+
+        return target;
+    }
+
+    /**
+     * Whether the primary position at the given index is the previous character's trailing
+     * position. <br/>
+     *
+     * For LTR character, trailing position is its right edge. For RTL character, trailing position
+     * is its left edge.
+     *
+     * The primary position is defined as the position of a newly inserted character with the
+     * context direction at the given offset. In contrast, the secondary position is the position
+     * of a newly inserted character with the context's opposite direction at the given offset.
+     *
+     * In Android, the trailing position is used for primary position when the direction run after
+     * the given index has a higher level than the current direction run.
+     *
+     * <p>
+     * For example:
+     * (L represents LTR character, and R represents RTL character. The number is the index)
+     * <pre>
+     * input text:          L0 L1 L2 R3 R4 R5 L6 L7 L8
+     * render result:       L0 L1 L2 R5 R4 R3 L6 L7 L8
+     * BiDi Run:            [ Run 0 ][ Run 1 ][ Run 2 ]
+     * BiDi Level:          0  0  0  1  1  1  0  0  0
+     * </pre>
+     *
+     * The index 3 is a BiDi transition point, the cursor can be placed either after L2 or before
+     * R3. Because the bidi level of run 1 is higher than the run 0, this method returns true. And
+     * the cursor should be placed after L2.
+     * <pre>
+     * render result:       L0 L1 L2 R5 R4 R3 L6 L7 L8
+     * position after L2:           |
+     * position before R3:                   |
+     * result position:             |
+     * </pre>
+     *
+     * The index 6 is also a Bidi transition point, the 2 possible cursor positions are exactly the
+     * same as index 3. However, since the bidi level of run 2 is higher than the run 1, this
+     * method returns false. And the cursor should be placed before L6.
+     * <pre>
+     * render result:       L0 L1 L2 R5 R4 R3 L6 L7 L8
+     * position after R5:           |
+     * position before L6:                   |
+     * result position:                      |
+     * </pre>
+     *
+     * This method helps guarantee that the cursor index and the cursor position forms a one to
+     * one relation.
+     * </p>
+     *
+     * @param offset the offset of the character in front of which the cursor is placed. It must be
+     *              the start index of a grapheme. And it must be in the range from lineStart to
+     *              lineEnd. An offset equal to lineEnd is allowed. It indicates that the cursor is
+     *              placed at the end of current line instead of the start of the following line.
+     * @param lineStart the start index of the line that index belongs to, inclusive.
+     * @param lineEnd the end index of the line that index belongs to, exclusive.
+     * @return true if primary position is the trailing position of the previous character.
+     *
+     * @see #getCursorHorizontalPosition(int, int, int, float, float)
+     */
+    private boolean primaryIsTrailingPrevious(int offset, int lineStart, int lineEnd) {
+        final int bidiLevel;
+        if (offset < lineEnd) {
+            bidiLevel = getCharacterBidiLevel(offset);
+        } else {
+            // index equals to lineEnd, use line's BiDi level for the BiDi run.
+            boolean lineIsRtl =
+                    (getCharacterFlags(offset - 1) & FLAG_LINE_IS_RTL) == FLAG_LINE_IS_RTL;
+            bidiLevel = lineIsRtl ? 1 : 0;
+        }
+        final int bidiLevelBefore;
+        if (offset > lineStart) {
+            // Here it assumes index is always the start of a grapheme. And (index - 1) belongs to
+            // the previous grapheme.
+            bidiLevelBefore = getCharacterBidiLevel(offset - 1);
+        } else {
+            // index equals to lineStart, use line's BiDi level for previous BiDi run.
+            boolean lineIsRtl =
+                    (getCharacterFlags(offset) & FLAG_LINE_IS_RTL) == FLAG_LINE_IS_RTL;
+            bidiLevelBefore = lineIsRtl ? 1 : 0;
+        }
+        return bidiLevelBefore < bidiLevel;
+    }
+
+    /**
+     * Returns the x coordinates of the cursor at the given index. (The index of the character
+     * before which the cursor should be placed.)
+     *
+     * @param index the character index before which the cursor is placed. It must be the start
+     *              index of a grapheme. It must be in the range from lineStart to lineEnd.
+     *              An index equal to lineEnd is allowed. It indicates that the cursor is
+     *              placed at the end of current line instead of the start of the following line.
+     * @param lineStart start index of the line that index belongs to, inclusive.
+     * @param lineEnd end index of the line that index belongs, exclusive.
+     * @return the x coordinates of the cursor at the given index,
+     *
+     * @see #primaryIsTrailingPrevious(int, int, int)
+     */
+    private float getCursorHorizontalPosition(int index, int lineStart, int lineEnd,
+            float lineLeft, float lineRight) {
+        Preconditions.checkArgumentInRange(index, lineStart, lineEnd, "index");
+        final boolean lineIsRtl = (getCharacterFlags(lineStart) & FLAG_LINE_IS_RTL) != 0;
+        final boolean isPrimaryIsTrailingPrevious =
+                primaryIsTrailingPrevious(index, lineStart, lineEnd);
+
+        // The index of the character used to compute the cursor position.
+        final int targetIndex;
+        // Whether to use the start position of the character.
+        // For LTR character start is the left edge. For RTL character, start is the right edge.
+        final boolean isStart;
+        if (isPrimaryIsTrailingPrevious) {
+            // (index - 1) belongs to the previous line(if any), return the line start position.
+            if (index <= lineStart) {
+                return lineIsRtl ? lineRight : lineLeft;
+            }
+            targetIndex = index - 1;
+            isStart = false;
+        } else {
+            // index belongs to the next line(if any), return the line end position.
+            if (index >= lineEnd) {
+                return lineIsRtl ? lineLeft : lineRight;
+            }
+            targetIndex = index;
+            isStart = true;
+        }
+
+        // The BiDi level is odd when the character is RTL.
+        final boolean isRtl = (getCharacterBidiLevel(targetIndex) & 1) != 0;
+        final int offset = targetIndex - mStart;
+        // If the character is RTL, the start is the right edge. Otherwise, the start is the
+        // left edge:
+        //  +-----------------------+
+        //  |       | start | end   |
+        //  |-------+-------+-------|
+        //  | RTL   | right | left  |
+        //  |-------+-------+-------|
+        //  | LTR   | left  | right |
+        //  +-------+-------+-------+
+        return (isRtl != isStart) ? mCharacterBounds[4 * offset] : mCharacterBounds[4 * offset + 2];
+    }
+
+    /**
+     * Return the minimal rectangle that contains all the characters in the given range.
+     *
+     * @param start the start index of the given range, inclusive.
+     * @param end the end index of the given range, exclusive.
+     * @param rectF the {@link RectF} to receive the bounds.
+     */
+    private void getBoundsForRange(int start, int end, @NonNull RectF rectF) {
+        Preconditions.checkArgumentInRange(start, mStart, mEnd - 1, "start");
+        Preconditions.checkArgumentInRange(end, start, mEnd, "end");
+        if (end <= start) {
+            rectF.setEmpty();
+            return;
+        }
+
+        rectF.left = Float.MAX_VALUE;
+        rectF.top = Float.MAX_VALUE;
+        rectF.right = Float.MIN_VALUE;
+        rectF.bottom = Float.MIN_VALUE;
+        for (int index = start; index < end; ++index) {
+            final int offset = index - mStart;
+            rectF.left = Math.min(rectF.left, mCharacterBounds[4 * offset]);
+            rectF.top = Math.min(rectF.top, mCharacterBounds[4 * offset + 1]);
+            rectF.right = Math.max(rectF.right, mCharacterBounds[4 * offset + 2]);
+            rectF.bottom = Math.max(rectF.bottom, mCharacterBounds[4 * offset + 3]);
+        }
+    }
+
+    /**
+     * Return the character range and bounds of the closest line to the given {@code y} coordinate,
+     * in the editor's local coordinates.
+     *
+     * If the given y is above the first line or below the last line -1 will be returned for line
+     * start and end.
+     *
+     * This method assumes that the lines are laid out from the top to bottom.
+     *
+     * @param y the y coordinates used to search for the line.
+     * @param characterRange a two element array used to receive the character range of the line.
+     *                       If no valid line is found -1 will be returned for both start and end.
+     * @param bounds {@link RectF} to receive the line bounds result, nullable. If given, it can
+     *                            still be modified even if no valid line is found.
+     */
+    private void getLineInfo(float y, @NonNull int[] characterRange, @Nullable RectF bounds) {
+        characterRange[0] = -1;
+        characterRange[1] = -1;
+
+        // Starting from the first line.
+        int currentLineEnd = mLineSegmentFinder.nextEndBoundary(mStart);
+        if (currentLineEnd == SegmentFinder.DONE) return;
+        int currentLineStart = mLineSegmentFinder.previousStartBoundary(currentLineEnd);
+
+        float top = Float.MAX_VALUE;
+        float bottom = Float.MIN_VALUE;
+        float minDistance = Float.MAX_VALUE;
+        final RectF currentLineBounds = new RectF();
+        while (currentLineStart != SegmentFinder.DONE && currentLineStart < mEnd) {
+            final int lineStartInRange = Math.max(mStart, currentLineStart);
+            final int lineEndInRange = Math.min(mEnd, currentLineEnd);
+            getBoundsForRange(lineStartInRange, lineEndInRange, currentLineBounds);
+
+            top = Math.min(currentLineBounds.top, top);
+            bottom = Math.max(currentLineBounds.bottom, bottom);
+
+            final float distance = verticalDistance(currentLineBounds, y);
+
+            if (distance == 0f) {
+                characterRange[0] = currentLineStart;
+                characterRange[1] = currentLineEnd;
+                if (bounds != null) {
+                    bounds.set(currentLineBounds);
+                }
+                return;
+            }
+
+            if (distance < minDistance) {
+                minDistance = distance;
+                characterRange[0] = currentLineStart;
+                characterRange[1] = currentLineEnd;
+                if (bounds != null) {
+                    bounds.set(currentLineBounds);
+                }
+            }
+            if (y < bounds.top) break;
+            currentLineStart = mLineSegmentFinder.nextStartBoundary(currentLineStart);
+            currentLineEnd = mLineSegmentFinder.nextEndBoundary(currentLineEnd);
+        }
+
+        // y is above the first line or below the last line. The founded line is still invalid,
+        // clear the result.
+        if (y < top || y > bottom) {
+            characterRange[0] = -1;
+            characterRange[1] = -1;
+            if (bounds != null) {
+                bounds.setEmpty();
+            }
+        }
+    }
+
+    /**
+     * Finds the range of text which is inside the specified rectangle area. This method is a
+     * counterpart of the
+     * {@link Layout#getRangeForRect(RectF, SegmentFinder, Layout.TextInclusionStrategy)}.
+     *
+     * <p>It's assumed that the editor lays out text in horizontal lines from top to bottom
+     * and each line is laid out according to the display algorithm specified in
+     * <a href="https://unicode.org/reports/tr9/#Basic_Display_Algorithm"> unicode bidirectional
+     * algorithm</a>.
+     * </p>
+     *
+     * <p> This method won't check the text ranges whose line information is missing. For example,
+     * the {@link TextBoundsInfo}'s range is from index 5 to 15. If the associated line
+     * {@link SegmentFinder} only identifies one line range from 7 to 12. Then this method
+     * won't check the text in the ranges of [5, 7) and [12, 15).
+     * </p>
+     *
+     * @param area area for which the text range will be found
+     * @param segmentFinder SegmentFinder for determining the ranges of text to be considered as a
+     *     text segment
+     * @param inclusionStrategy strategy for determining whether a text segment is inside the
+     *          specified area
+     * @return the text range stored in a two element int array. The first element is the
+     * start (inclusive) of the text range, and the second element is the end (exclusive) character
+     * offsets of the text range, or null if there are no text segments inside the area.
+     *
+     * @see Layout#getRangeForRect(RectF, SegmentFinder, Layout.TextInclusionStrategy)
+     */
+    @Nullable
+    public int[] getRangeForRect(@NonNull RectF area, @NonNull SegmentFinder segmentFinder,
+            @NonNull Layout.TextInclusionStrategy inclusionStrategy) {
+        int lineEnd = mLineSegmentFinder.nextEndBoundary(mStart);
+        // Line information is missing.
+        if (lineEnd == SegmentFinder.DONE) return null;
+        int lineStart = mLineSegmentFinder.previousStartBoundary(lineEnd);
+
+        int start = -1;
+        while (lineStart != SegmentFinder.DONE && start == -1) {
+            start = getStartForRectWithinLine(lineStart, lineEnd, area, segmentFinder,
+                    inclusionStrategy);
+            lineStart = mLineSegmentFinder.nextStartBoundary(lineStart);
+            lineEnd = mLineSegmentFinder.nextEndBoundary(lineEnd);
+        }
+
+        // Can't find the start index; the specified contains no valid segment.
+        if (start == -1) return null;
+
+        lineStart = mLineSegmentFinder.previousStartBoundary(mEnd);
+        // Line information is missing.
+        if (lineStart == SegmentFinder.DONE) return null;
+        lineEnd = mLineSegmentFinder.nextEndBoundary(lineStart);
+        int end = -1;
+        while (lineEnd > start && end == -1) {
+            end = getEndForRectWithinLine(lineStart, lineEnd, area, segmentFinder,
+                    inclusionStrategy);
+            lineStart = mLineSegmentFinder.previousStartBoundary(lineStart);
+            lineEnd = mLineSegmentFinder.previousEndBoundary(lineEnd);
+        }
+
+        // We've already found start, end is guaranteed to be found at this point.
+        start = segmentFinder.previousStartBoundary(start + 1);
+        end = segmentFinder.nextEndBoundary(end - 1);
+        return new int[] { start, end };
+    }
+
+    /**
+     * Find the start character index of the first text segments within a line inside the specified
+     * {@code area}.
+     *
+     * @param lineStart the start of this line, inclusive .
+     * @param lineEnd the end of this line, exclusive.
+     * @param area the area inside which the text segments will be found.
+     * @param segmentFinder SegmentFinder for determining the ranges of text to be considered a
+     *                      text segment.
+     * @param inclusionStrategy strategy for determining whether a text segment is inside the
+     *                          specified area.
+     * @return the start index of the first segment in the area.
+     */
+    private int getStartForRectWithinLine(int lineStart, int lineEnd, @NonNull RectF area,
+            @NonNull SegmentFinder segmentFinder,
+            @NonNull Layout.TextInclusionStrategy inclusionStrategy) {
+        if (lineStart >= lineEnd) return -1;
+
+        int runStart = lineStart;
+        int runLevel = -1;
+        // Check the BiDi runs and search for the start index.
+        for (int index = lineStart; index < lineEnd; ++index) {
+            final int level = getCharacterBidiLevel(index);
+            if (level != runLevel) {
+                final int start = getStartForRectWithinRun(runStart, index, area, segmentFinder,
+                        inclusionStrategy);
+                if (start != -1) {
+                    return start;
+                }
+
+                runStart = index;
+                runLevel = level;
+            }
+        }
+        return getStartForRectWithinRun(runStart, lineEnd, area, segmentFinder, inclusionStrategy);
+    }
+
+    /**
+     * Find the start character index of the first text segments within the directional run inside
+     * the specified {@code area}.
+     *
+     * @param runStart the start of this directional run, inclusive.
+     * @param runEnd the end of this directional run, exclusive.
+     * @param area the area inside which the text segments will be found.
+     * @param segmentFinder SegmentFinder for determining the ranges of text to be considered a
+     *                      text segment.
+     * @param inclusionStrategy strategy for determining whether a text segment is inside the
+     *                          specified area.
+     * @return the start index of the first segment in the area.
+     */
+    private int getStartForRectWithinRun(int runStart, int runEnd, @NonNull RectF area,
+            @NonNull SegmentFinder segmentFinder,
+            @NonNull Layout.TextInclusionStrategy inclusionStrategy) {
+        if (runStart >= runEnd) return -1;
+
+        int segmentEndOffset = segmentFinder.nextEndBoundary(runStart);
+        // No segment is found in run.
+        if (segmentEndOffset == SegmentFinder.DONE) return -1;
+        int segmentStartOffset = segmentFinder.previousStartBoundary(segmentEndOffset);
+
+        final RectF segmentBounds = new RectF();
+        while (segmentStartOffset != SegmentFinder.DONE && segmentStartOffset < runEnd) {
+            final int start = Math.max(runStart, segmentStartOffset);
+            final int end = Math.min(runEnd, segmentEndOffset);
+            getBoundsForRange(start, end, segmentBounds);
+            // Find the first segment inside the area, return the start.
+            if (inclusionStrategy.isSegmentInside(segmentBounds, area)) return start;
+
+            segmentStartOffset = segmentFinder.nextStartBoundary(segmentStartOffset);
+            segmentEndOffset = segmentFinder.nextEndBoundary(segmentEndOffset);
+        }
+        return -1;
+    }
+
+    /**
+     * Find the end character index of the last text segments within a line inside the specified
+     * {@code area}.
+     *
+     * @param lineStart the start of this line, inclusive .
+     * @param lineEnd the end of this line, exclusive.
+     * @param area the area inside which the text segments will be found.
+     * @param segmentFinder SegmentFinder for determining the ranges of text to be considered a
+     *                      text segment.
+     * @param inclusionStrategy strategy for determining whether a text segment is inside the
+     *                          specified area.
+     * @return the end index of the last segment in the area.
+     */
+    private int getEndForRectWithinLine(int lineStart, int lineEnd, @NonNull RectF area,
+            @NonNull SegmentFinder segmentFinder,
+            @NonNull Layout.TextInclusionStrategy inclusionStrategy) {
+        if (lineStart >= lineEnd) return -1;
+        lineStart = Math.max(lineStart, mStart);
+        lineEnd = Math.min(lineEnd, mEnd);
+
+        // The exclusive run end index.
+        int runEnd = lineEnd;
+        int runLevel = -1;
+        // Check the BiDi runs backwards and search for the end index.
+        for (int index = lineEnd - 1; index >= lineStart; --index) {
+            final int level = getCharacterBidiLevel(index);
+            if (level != runLevel) {
+                final int end = getEndForRectWithinRun(index + 1, runEnd, area, segmentFinder,
+                        inclusionStrategy);
+                if (end != -1) return end;
+
+                runEnd = index + 1;
+                runLevel = level;
+            }
+        }
+        return getEndForRectWithinRun(lineStart, runEnd, area, segmentFinder, inclusionStrategy);
+    }
+
+    /**
+     * Find the end character index of the last text segments within the directional run inside the
+     * specified {@code area}.
+     *
+     * @param runStart the start of this directional run, inclusive.
+     * @param runEnd the end of this directional run, exclusive.
+     * @param area the area inside which the text segments will be found.
+     * @param segmentFinder SegmentFinder for determining the ranges of text to be considered a
+     *                      text segment.
+     * @param inclusionStrategy strategy for determining whether a text segment is inside the
+     *                          specified area.
+     * @return the end index of the last segment in the area.
+     */
+    private int getEndForRectWithinRun(int runStart, int runEnd, @NonNull RectF area,
+            @NonNull SegmentFinder segmentFinder,
+            @NonNull Layout.TextInclusionStrategy inclusionStrategy) {
+        if (runStart >= runEnd) return -1;
+
+        int segmentStart = segmentFinder.previousStartBoundary(runEnd);
+        // No segment is found before the runEnd.
+        if (segmentStart == SegmentFinder.DONE) return -1;
+        int segmentEnd = segmentFinder.nextEndBoundary(segmentStart);
+
+        final RectF segmentBounds = new RectF();
+        while (segmentEnd != SegmentFinder.DONE && segmentEnd > runStart) {
+            final int start = Math.max(runStart, segmentStart);
+            final int end = Math.min(runEnd, segmentEnd);
+            getBoundsForRange(start, end, segmentBounds);
+            // Find the last segment inside the area, return the end.
+            if (inclusionStrategy.isSegmentInside(segmentBounds, area)) return end;
+
+            segmentStart = segmentFinder.previousStartBoundary(segmentStart);
+            segmentEnd = segmentFinder.previousEndBoundary(segmentEnd);
+        }
+        return -1;
+    }
+
+    /**
+     * Get the vertical distance from the {@code pointF} to the {@code rectF}. It's useful to find
+     * the corresponding line for a given point.
+     */
+    private static float verticalDistance(@NonNull RectF rectF, float y) {
+        if (rectF.top <= y && y < rectF.bottom) {
+            return 0f;
+        }
+        if (y < rectF.top) {
+            return rectF.top - y;
+        }
+        return y - rectF.bottom;
+    }
+
+    /**
      * Describe the kinds of special objects contained in this Parcelable
      * instance's marshaled representation. For example, if the object will
      * include a file descriptor in the output of {@link #writeToParcel(Parcel, int)},
diff --git a/core/java/android/window/SurfaceSyncGroup.java b/core/java/android/window/SurfaceSyncGroup.java
index 3950739..250652a 100644
--- a/core/java/android/window/SurfaceSyncGroup.java
+++ b/core/java/android/window/SurfaceSyncGroup.java
@@ -40,64 +40,65 @@
  * mechanism so each sync implementation doesn't need to handle it themselves. The SurfaceSyncGroup
  * class is used the following way.
  *
- * 1. {@link #SurfaceSyncGroup()} constructor is called
- * 2. {@link #addToSync(SyncTarget)} is called for every SyncTarget object that wants to be
- * included in the sync. If the addSync is called for an {@link AttachedSurfaceControl} or
- * {@link SurfaceView} it needs to be called on the UI thread. When addToSync is called, it's
+ * 1. {@link #addToSync(SurfaceSyncGroup, boolean)} is called for every SurfaceSyncGroup object that
+ * wants to be included in the sync. If the addSync is called for an {@link AttachedSurfaceControl}
+ * or {@link SurfaceView} it needs to be called on the UI thread. When addToSync is called, it's
  * guaranteed that any UI updates that were requested before addToSync but after the last frame
  * drew, will be included in the sync.
- * 3. {@link #markSyncReady()} should be called when all the {@link SyncTarget}s have been added
- * to the SurfaceSyncGroup. At this point, the SurfaceSyncGroup is closed and no more SyncTargets
- * can be added to it.
- * 4. The SurfaceSyncGroup will gather the data for each SyncTarget using the steps described below.
- * When all the SyncTargets have finished, the syncRequestComplete will be invoked and the
- * transaction will either be applied or sent to the caller. In most cases, only the
- * SurfaceSyncGroup  should be handling the Transaction object directly. However, there are some
+ * 2. {@link #markSyncReady()} should be called when all the {@link SurfaceSyncGroup}s have been
+ * added to the SurfaceSyncGroup. At this point, the SurfaceSyncGroup is closed and no more
+ * SurfaceSyncGroups can be added to it.
+ * 3. The SurfaceSyncGroup will gather the data for each SurfaceSyncGroup using the steps described
+ * below. When all the SurfaceSyncGroups have finished, the syncRequestComplete will be invoked and
+ * the transaction will either be applied or sent to the caller. In most cases, only the
+ * SurfaceSyncGroup should be handling the Transaction object directly. However, there are some
  * cases where the framework needs to send the Transaction elsewhere, like in ViewRootImpl, so that
  * option is provided.
  *
- * The following is what happens within the {@link SurfaceSyncGroup}
- * 1. Each SyncTarget will get a {@link SyncTarget#onAddedToSyncGroup} callback that contains a
- * {@link TransactionReadyCallback}.
- * 2. Each {@link SyncTarget} needs to invoke
- * {@link TransactionReadyCallback#onTransactionReady(Transaction)}. This makes sure the
- * SurfaceSyncGroup knows when the SyncTarget is complete, allowing the SurfaceSyncGroup to get the
- * Transaction that contains the buffer.
- * 3. When the final TransactionReadyCallback finishes for the SurfaceSyncGroup, in most cases the
- * transaction is applied and then the sync complete callbacks are invoked, letting the callers know
- * the sync is now complete.
+ * The following is what happens within the {@link android.window.SurfaceSyncGroup}
+ * 1. Each SurfaceSyncGroup will get a
+ * {@link SurfaceSyncGroup#onAddedToSyncGroup(SurfaceSyncGroup, TransactionReadyCallback)} callback
+ * that contains a  {@link TransactionReadyCallback}.
+ * 2. Each {@link SurfaceSyncGroup} needs to invoke
+ * {@link SurfaceSyncGroup#onTransactionReady(Transaction)}.
+ * This makes sure the parent SurfaceSyncGroup knows when the SurfaceSyncGroup is complete, allowing
+ * the parent SurfaceSyncGroup to get the Transaction that contains the changes for the child
+ * SurfaceSyncGroup
+ * 3. When the final TransactionReadyCallback finishes for the child SurfaceSyncGroups, the
+ * transaction is either applied if it's the top most parent or the final merged transaction is sent
+ * up to its parent SurfaceSyncGroup.
  *
  * @hide
  */
-public final class SurfaceSyncGroup {
+public class SurfaceSyncGroup {
     private static final String TAG = "SurfaceSyncGroup";
     private static final boolean DEBUG = false;
 
     private static Supplier<Transaction> sTransactionFactory = Transaction::new;
 
     /**
-     * Class that collects the {@link SyncTarget}s and notifies when all the surfaces have
+     * Class that collects the {@link SurfaceSyncGroup}s and notifies when all the surfaces have
      * a frame ready.
      */
     private final Object mLock = new Object();
 
     @GuardedBy("mLock")
-    private final Set<Integer> mPendingSyncs = new ArraySet<>();
+    private final Set<TransactionReadyCallback> mPendingSyncs = new ArraySet<>();
     @GuardedBy("mLock")
     private final Transaction mTransaction = sTransactionFactory.get();
     @GuardedBy("mLock")
     private boolean mSyncReady;
 
     @GuardedBy("mLock")
-    private Consumer<Transaction> mSyncRequestCompleteCallback;
-
-    @GuardedBy("mLock")
-    private final Set<SurfaceSyncGroup> mMergedSyncGroups = new ArraySet<>();
-
-    @GuardedBy("mLock")
     private boolean mFinished;
 
     @GuardedBy("mLock")
+    private TransactionReadyCallback mTransactionReadyCallback;
+
+    @GuardedBy("mLock")
+    private SurfaceSyncGroup mParentSyncGroup;
+
+    @GuardedBy("mLock")
     private final ArraySet<Pair<Executor, Runnable>> mSyncCompleteCallbacks = new ArraySet<>();
 
     /**
@@ -122,16 +123,16 @@
     /**
      * Creates a sync.
      *
-     * @param syncRequestComplete The complete callback that contains the syncId and transaction
-     *                            with all the sync data merged. The Transaction passed back can be
-     *                            null.
+     * @param transactionReadyCallback The complete callback that contains the syncId and
+     *                                 transaction with all the sync data merged. The Transaction
+     *                                 passed back can be null.
      *
      * NOTE: Only should be used by ViewRootImpl
      * @hide
      */
-    public SurfaceSyncGroup(Consumer<Transaction> syncRequestComplete) {
-        mSyncRequestCompleteCallback = transaction -> {
-            syncRequestComplete.accept(transaction);
+    public SurfaceSyncGroup(Consumer<Transaction> transactionReadyCallback) {
+        mTransactionReadyCallback = transaction -> {
+            transactionReadyCallback.accept(transaction);
             synchronized (mLock) {
                 for (Pair<Executor, Runnable> callback : mSyncCompleteCallbacks) {
                     callback.first.execute(callback.second);
@@ -157,6 +158,31 @@
     }
 
     /**
+     * Mark the sync set as ready to complete. No more data can be added to the specified
+     * syncId.
+     * Once the sync set is marked as ready, it will be able to complete once all Syncables in the
+     * set have completed their sync
+     */
+    public void markSyncReady() {
+        onTransactionReady(null);
+    }
+
+    /**
+     * Similar to {@link #markSyncReady()}, but a transaction is passed in to merge with the
+     * SurfaceSyncGroup.
+     * @param t The transaction that merges into the main Transaction for the SurfaceSyncGroup.
+     */
+    public void onTransactionReady(@Nullable Transaction t) {
+        synchronized (mLock) {
+            mSyncReady = true;
+            if (t != null) {
+                mTransaction.merge(t);
+            }
+            checkIfSyncIsComplete();
+        }
+    }
+
+    /**
      * Add a SurfaceView to a sync set. This is different than
      * {@link #addToSync(AttachedSurfaceControl)} because it requires the caller to notify the start
      * and finish drawing in order to sync.
@@ -171,7 +197,13 @@
     @UiThread
     public boolean addToSync(SurfaceView surfaceView,
             Consumer<SurfaceViewFrameCallback> frameCallbackConsumer) {
-        return addToSync(new SurfaceViewSyncTarget(surfaceView, frameCallbackConsumer));
+        SurfaceSyncGroup surfaceSyncGroup = new SurfaceSyncGroup();
+        if (addToSync(surfaceSyncGroup, false /* parentSyncGroupMerge */)) {
+            frameCallbackConsumer.accept(
+                    () -> surfaceView.syncNextFrame(surfaceSyncGroup::onTransactionReady));
+            return true;
+        }
+        return false;
     }
 
     /**
@@ -185,29 +217,38 @@
         if (viewRoot == null) {
             return false;
         }
-        SyncTarget syncTarget = viewRoot.getSyncTarget();
-        if (syncTarget == null) {
+        SurfaceSyncGroup surfaceSyncGroup = viewRoot.getOrCreateSurfaceSyncGroup();
+        if (surfaceSyncGroup == null) {
             return false;
         }
-        return addToSync(syncTarget);
+        return addToSync(surfaceSyncGroup, false /* parentSyncGroupMerge */);
     }
 
     /**
-     * Add a {@link SyncTarget} to a sync set. The sync set will wait for all
+     * Add a {@link SurfaceSyncGroup} to a sync set. The sync set will wait for all
      * SyncableSurfaces to complete before notifying.
      *
-     * @param syncTarget A SyncTarget that implements how to handle syncing transactions.
-     * @return true if the SyncTarget was successfully added to the SyncGroup, false otherwise.
+     * @param surfaceSyncGroup A SyncableSurface that implements how to handle syncing
+     *                         buffers.
+     * @return true if the SyncGroup was successfully added to the current SyncGroup, false
+     * otherwise.
      */
-    public boolean addToSync(SyncTarget syncTarget) {
+    public boolean addToSync(SurfaceSyncGroup surfaceSyncGroup, boolean parentSyncGroupMerge) {
         TransactionReadyCallback transactionReadyCallback = new TransactionReadyCallback() {
             @Override
             public void onTransactionReady(Transaction t) {
                 synchronized (mLock) {
                     if (t != null) {
+                        // When an older parent sync group is added due to a child syncGroup getting
+                        // added to multiple groups, we need to maintain merge order so the older
+                        // parentSyncGroup transactions are overwritten by anything in the newer
+                        // parentSyncGroup.
+                        if (parentSyncGroupMerge) {
+                            t.merge(mTransaction);
+                        }
                         mTransaction.merge(t);
                     }
-                    mPendingSyncs.remove(hashCode());
+                    mPendingSyncs.remove(this);
                     checkIfSyncIsComplete();
                 }
             }
@@ -216,48 +257,16 @@
         synchronized (mLock) {
             if (mSyncReady) {
                 Log.e(TAG, "Sync " + this + " was already marked as ready. No more "
-                        + "SyncTargets can be added.");
+                        + "SurfaceSyncGroups can be added.");
                 return false;
             }
-            mPendingSyncs.add(transactionReadyCallback.hashCode());
+            mPendingSyncs.add(transactionReadyCallback);
         }
-        syncTarget.onAddedToSyncGroup(this, transactionReadyCallback);
+        surfaceSyncGroup.onAddedToSyncGroup(this, transactionReadyCallback);
         return true;
     }
 
     /**
-     * Mark the sync set as ready to complete. No more data can be added to the specified
-     * syncId.
-     * Once the sync set is marked as ready, it will be able to complete once all Syncables in the
-     * set have completed their sync
-     */
-    public void markSyncReady() {
-        synchronized (mLock) {
-            mSyncReady = true;
-            checkIfSyncIsComplete();
-        }
-    }
-
-    @GuardedBy("mLock")
-    private void checkIfSyncIsComplete() {
-        if (!mSyncReady || !mPendingSyncs.isEmpty() || !mMergedSyncGroups.isEmpty()) {
-            if (DEBUG) {
-                Log.d(TAG, "Syncable is not complete. mSyncReady=" + mSyncReady
-                        + " mPendingSyncs=" + mPendingSyncs.size() + " mergedSyncs="
-                        + mMergedSyncGroups.size());
-            }
-            return;
-        }
-
-        if (DEBUG) {
-            Log.d(TAG, "Successfully finished sync id=" + this);
-        }
-
-        mSyncRequestCompleteCallback.accept(mTransaction);
-        mFinished = true;
-    }
-
-    /**
      * Add a Transaction to this sync set. This allows the caller to provide other info that
      * should be synced with the transactions.
      */
@@ -267,99 +276,68 @@
         }
     }
 
-    private void updateCallback(Consumer<Transaction> transactionConsumer) {
+    @GuardedBy("mLock")
+    private void checkIfSyncIsComplete() {
+        if (mFinished) {
+            if (DEBUG) {
+                Log.d(TAG, "SurfaceSyncGroup=" + this + " is already complete");
+            }
+            return;
+        }
+
+        if (!mSyncReady || !mPendingSyncs.isEmpty()) {
+            if (DEBUG) {
+                Log.d(TAG, "SurfaceSyncGroup=" + this + " is not complete. mSyncReady="
+                        + mSyncReady + " mPendingSyncs=" + mPendingSyncs.size());
+            }
+            return;
+        }
+
+        if (DEBUG) {
+            Log.d(TAG, "Successfully finished sync id=" + this);
+        }
+        mTransactionReadyCallback.onTransactionReady(mTransaction);
+        mFinished = true;
+    }
+
+    private void onAddedToSyncGroup(SurfaceSyncGroup parentSyncGroup,
+            TransactionReadyCallback transactionReadyCallback) {
+        boolean finished = false;
         synchronized (mLock) {
             if (mFinished) {
-                Log.e(TAG, "Attempting to merge SyncGroup " + this + " when sync is"
-                        + " already complete");
-                transactionConsumer.accept(null);
-            }
-
-            final Consumer<Transaction> oldCallback = mSyncRequestCompleteCallback;
-            mSyncRequestCompleteCallback = transaction -> {
-                oldCallback.accept(null);
-                transactionConsumer.accept(transaction);
-            };
-        }
-    }
-
-    /**
-     * Merge a SyncGroup into this SyncGroup. Since SyncGroups could still have pending SyncTargets,
-     * we need to make sure those can still complete before the mergeTo SyncGroup is considered
-     * complete.
-     *
-     * We keep track of all the merged SyncGroups until they are marked as done, and then they
-     * are removed from the set. This SyncGroup is not considered done until all the merged
-     * SyncGroups are done.
-     *
-     * When the merged SyncGroup is complete, it will invoke the original syncRequestComplete
-     * callback but send an empty transaction to ensure the changes are applied early. This
-     * is needed in case the original sync is relying on the callback to continue processing.
-     *
-     * @param otherSyncGroup The other SyncGroup to merge into this one.
-     */
-    public void merge(SurfaceSyncGroup otherSyncGroup) {
-        synchronized (mLock) {
-            mMergedSyncGroups.add(otherSyncGroup);
-        }
-        otherSyncGroup.updateCallback(transaction -> {
-            synchronized (mLock) {
-                mMergedSyncGroups.remove(otherSyncGroup);
-                if (transaction != null) {
-                    mTransaction.merge(transaction);
+                finished = true;
+            } else {
+                // If this SurfaceSyncGroup was already added to a different SurfaceSyncGroup, we
+                // need to combine everything. We can add the old SurfaceSyncGroup parent to the new
+                // parent so the new parent doesn't complete until the old parent does.
+                // Additionally, the old parent will not get the final transaction object and
+                // instead will send it to the new parent, ensuring that any other SurfaceSyncGroups
+                // from the original parent are also combined with the new parent SurfaceSyncGroup.
+                if (mParentSyncGroup != null) {
+                    Log.d(TAG, "Already part of sync group " + mParentSyncGroup + " " + this);
+                    parentSyncGroup.addToSync(mParentSyncGroup, true /* parentSyncGroupMerge */);
                 }
-                checkIfSyncIsComplete();
+                mParentSyncGroup = parentSyncGroup;
+                final TransactionReadyCallback lastCallback = mTransactionReadyCallback;
+                mTransactionReadyCallback = t -> {
+                    lastCallback.onTransactionReady(null);
+                    transactionReadyCallback.onTransactionReady(t);
+                };
             }
-        });
-    }
-
-    /**
-     * Wrapper class to help synchronize SurfaceViews
-     */
-    private static class SurfaceViewSyncTarget implements SyncTarget {
-        private final SurfaceView mSurfaceView;
-        private final Consumer<SurfaceViewFrameCallback> mFrameCallbackConsumer;
-
-        SurfaceViewSyncTarget(SurfaceView surfaceView,
-                Consumer<SurfaceViewFrameCallback> frameCallbackConsumer) {
-            mSurfaceView = surfaceView;
-            mFrameCallbackConsumer = frameCallbackConsumer;
         }
 
-        @Override
-        public void onAddedToSyncGroup(SurfaceSyncGroup parentSyncGroup,
-                TransactionReadyCallback transactionReadyCallback) {
-            mFrameCallbackConsumer.accept(
-                    () -> mSurfaceView.syncNextFrame(transactionReadyCallback::onTransactionReady));
+        // Invoke the callback outside of the lock when the SurfaceSyncGroup being added was already
+        // complete.
+        if (finished) {
+            transactionReadyCallback.onTransactionReady(null);
         }
     }
-
-    /**
-     * A SyncTarget that can be added to a sync set.
-     */
-    public interface SyncTarget {
-        /**
-         * Called when the SyncTarget has been added to a SyncGroup as is ready to begin handing a
-         * sync request. When invoked, the implementor is required to call
-         * {@link TransactionReadyCallback#onTransactionReady(Transaction)} in order for this
-         * SurfaceSyncGroup to fully complete.
-         *
-         * Always invoked on the thread that initiated the call to {@link #addToSync(SyncTarget)}
-         *
-         * @param parentSyncGroup The sync group this target has been added to.
-         * @param transactionReadyCallback A TransactionReadyCallback that the caller must invoke
-         *                                 onTransactionReady
-         */
-        void onAddedToSyncGroup(SurfaceSyncGroup parentSyncGroup,
-                TransactionReadyCallback transactionReadyCallback);
-    }
-
     /**
      * Interface so the SurfaceSyncer can know when it's safe to start and when everything has been
      * completed. The caller should invoke the calls when the rendering has started and finished a
      * frame.
      */
-    public interface TransactionReadyCallback {
+    private interface TransactionReadyCallback {
         /**
          * Invoked when the transaction is ready to sync.
          *
diff --git a/core/java/com/android/internal/os/BatteryStatsHistory.java b/core/java/com/android/internal/os/BatteryStatsHistory.java
index c3361a2..66d64c4 100644
--- a/core/java/com/android/internal/os/BatteryStatsHistory.java
+++ b/core/java/com/android/internal/os/BatteryStatsHistory.java
@@ -49,6 +49,7 @@
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.ConcurrentModificationException;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -163,10 +164,6 @@
      */
     private int mCurrentParcelEnd;
     /**
-     * When iterating history files, the current record count.
-     */
-    private int mRecordCount = 0;
-    /**
      * Used when BatteryStatsImpl object is created from deserialization of a parcel,
      * such as Settings app or checkin file, to iterate over history parcels.
      */
@@ -199,10 +196,8 @@
     private boolean mMeasuredEnergyHeaderWritten = false;
     private boolean mCpuUsageHeaderWritten = false;
     private final VarintParceler mVarintParceler = new VarintParceler();
-
     private byte mLastHistoryStepLevel = 0;
-
-    private BatteryStatsHistoryIterator mBatteryStatsHistoryIterator;
+    private boolean mMutable = true;
 
     /**
      * A delegate responsible for computing additional details for a step in battery history.
@@ -493,25 +488,21 @@
      * @return always return true.
      */
     public BatteryStatsHistoryIterator iterate() {
-        mRecordCount = 0;
         mCurrentFileIndex = 0;
         mCurrentParcel = null;
         mCurrentParcelEnd = 0;
         mParcelIndex = 0;
-        mBatteryStatsHistoryIterator = new BatteryStatsHistoryIterator(this);
-        return mBatteryStatsHistoryIterator;
+        mMutable = false;
+        return new BatteryStatsHistoryIterator(this);
     }
 
     /**
      * Finish iterating history files and history buffer.
      */
-    void finishIteratingHistory() {
+    void iteratorFinished() {
         // setDataPosition so mHistoryBuffer Parcel can be written.
         mHistoryBuffer.setDataPosition(mHistoryBuffer.dataSize());
-        mBatteryStatsHistoryIterator = null;
-        if (DEBUG) {
-            Slog.d(TAG, "Battery history records iterated: " + mRecordCount);
-        }
+        mMutable = true;
     }
 
     /**
@@ -519,17 +510,11 @@
      * history file, when reached the mActiveFile (highest numbered history file), do not read from
      * mActiveFile, read from history buffer instead because the buffer has more updated data.
      *
-     * @param out a history item.
      * @return The parcel that has next record. null if finished all history files and history
      * buffer
      */
-    public Parcel getNextParcel(HistoryItem out) {
-        if (mRecordCount == 0) {
-            // reset out if it is the first record.
-            out.clear();
-        }
-        ++mRecordCount;
-
+    @Nullable
+    public Parcel getNextParcel() {
         // First iterate through all records in current parcel.
         if (mCurrentParcel != null) {
             if (mCurrentParcel.dataPosition() < mCurrentParcelEnd) {
@@ -1270,6 +1255,10 @@
             return;
         }
 
+        if (!mMutable) {
+            throw new ConcurrentModificationException("Battery history is not writable");
+        }
+
         final long timeDiffMs = (mHistoryBaseTimeMs + elapsedRealtimeMs) - mHistoryLastWritten.time;
         final int diffStates = mHistoryLastWritten.states ^ (cur.states & mActiveHistoryStates);
         final int diffStates2 = mHistoryLastWritten.states2 ^ (cur.states2 & mActiveHistoryStates2);
@@ -1390,8 +1379,8 @@
 
     private void writeHistoryItem(long elapsedRealtimeMs,
             @SuppressWarnings("UnusedVariable") long uptimeMs, HistoryItem cur, byte cmd) {
-        if (mBatteryStatsHistoryIterator != null) {
-            throw new IllegalStateException("Can't do this while iterating history!");
+        if (!mMutable) {
+            throw new ConcurrentModificationException("Battery history is not writable");
         }
         mHistoryBufferLastPos = mHistoryBuffer.dataPosition();
         mHistoryLastLastWritten.setTo(mHistoryLastWritten);
@@ -1687,7 +1676,10 @@
                     Slog.i(TAG, "WRITE DELTA: cpuUsageDetails=" + cur.cpuUsageDetails);
                 }
                 if (!mCpuUsageHeaderWritten) {
-                    dest.writeStringArray(cur.cpuUsageDetails.cpuBracketDescriptions);
+                    dest.writeInt(cur.cpuUsageDetails.cpuBracketDescriptions.length);
+                    for (String desc: cur.cpuUsageDetails.cpuBracketDescriptions) {
+                        dest.writeString(desc);
+                    }
                     mCpuUsageHeaderWritten = true;
                 }
                 dest.writeInt(cur.cpuUsageDetails.uid);
diff --git a/core/java/com/android/internal/os/BatteryStatsHistoryIterator.java b/core/java/com/android/internal/os/BatteryStatsHistoryIterator.java
index 09fe100..b88116d 100644
--- a/core/java/com/android/internal/os/BatteryStatsHistoryIterator.java
+++ b/core/java/com/android/internal/os/BatteryStatsHistoryIterator.java
@@ -23,10 +23,13 @@
 import android.util.Slog;
 import android.util.SparseArray;
 
+import java.util.Iterator;
+
 /**
  * An iterator for {@link BatteryStats.HistoryItem}'s.
  */
-public class BatteryStatsHistoryIterator {
+public class BatteryStatsHistoryIterator implements Iterator<BatteryStats.HistoryItem>,
+        AutoCloseable {
     private static final boolean DEBUG = false;
     private static final String TAG = "BatteryStatsHistoryItr";
     private final BatteryStatsHistory mBatteryStatsHistory;
@@ -38,29 +41,51 @@
     private final BatteryStatsHistory.VarintParceler mVarintParceler =
             new BatteryStatsHistory.VarintParceler();
 
+    private final BatteryStats.HistoryItem mHistoryItem = new BatteryStats.HistoryItem();
+
+    private static final int MAX_ENERGY_CONSUMER_COUNT = 100;
+    private static final int MAX_CPU_BRACKET_COUNT = 100;
+
     public BatteryStatsHistoryIterator(@NonNull BatteryStatsHistory history) {
         mBatteryStatsHistory = history;
+        mHistoryItem.clear();
+    }
+
+    @Override
+    public boolean hasNext() {
+        Parcel p = mBatteryStatsHistory.getNextParcel();
+        if (p == null) {
+            close();
+            return false;
+        }
+        return true;
     }
 
     /**
-     * Retrieves the next HistoryItem from battery history, if available. Returns false if there
+     * Retrieves the next HistoryItem from battery history, if available. Returns null if there
      * are no more items.
      */
-    public boolean next(BatteryStats.HistoryItem out) {
-        Parcel p = mBatteryStatsHistory.getNextParcel(out);
+    @Override
+    public BatteryStats.HistoryItem next() {
+        Parcel p = mBatteryStatsHistory.getNextParcel();
         if (p == null) {
-            mBatteryStatsHistory.finishIteratingHistory();
-            return false;
+            close();
+            return null;
         }
 
-        final long lastRealtimeMs = out.time;
-        final long lastWalltimeMs = out.currentTime;
-        readHistoryDelta(p, out);
-        if (out.cmd != BatteryStats.HistoryItem.CMD_CURRENT_TIME
-                && out.cmd != BatteryStats.HistoryItem.CMD_RESET && lastWalltimeMs != 0) {
-            out.currentTime = lastWalltimeMs + (out.time - lastRealtimeMs);
+        final long lastRealtimeMs = mHistoryItem.time;
+        final long lastWalltimeMs = mHistoryItem.currentTime;
+        try {
+            readHistoryDelta(p, mHistoryItem);
+        } catch (Throwable t) {
+            Slog.wtf(TAG, "Corrupted battery history", t);
+            return null;
         }
-        return true;
+        if (mHistoryItem.cmd != BatteryStats.HistoryItem.CMD_CURRENT_TIME
+                && mHistoryItem.cmd != BatteryStats.HistoryItem.CMD_RESET && lastWalltimeMs != 0) {
+            mHistoryItem.currentTime = lastWalltimeMs + (mHistoryItem.time - lastRealtimeMs);
+        }
+        return mHistoryItem;
     }
 
     private void readHistoryDelta(Parcel src, BatteryStats.HistoryItem cur) {
@@ -210,6 +235,12 @@
                 }
 
                 final int consumerCount = src.readInt();
+                if (consumerCount > MAX_ENERGY_CONSUMER_COUNT) {
+                    // Check to avoid a heap explosion in case the parcel is corrupted
+                    throw new IllegalStateException(
+                            "EnergyConsumer count too high: " + consumerCount
+                                    + ". Max = " + MAX_ENERGY_CONSUMER_COUNT);
+                }
                 mMeasuredEnergyDetails.consumers =
                         new BatteryStats.MeasuredEnergyDetails.EnergyConsumer[consumerCount];
                 mMeasuredEnergyDetails.chargeUC = new long[consumerCount];
@@ -236,7 +267,16 @@
 
             if ((extensionFlags & BatteryStatsHistory.EXTENSION_CPU_USAGE_HEADER_FLAG) != 0) {
                 mCpuUsageDetails = new BatteryStats.CpuUsageDetails();
-                mCpuUsageDetails.cpuBracketDescriptions = src.readStringArray();
+                final int cpuBracketCount = src.readInt();
+                if (cpuBracketCount > MAX_CPU_BRACKET_COUNT) {
+                    // Check to avoid a heap explosion in case the parcel is corrupted
+                    throw new IllegalStateException("Too many CPU brackets: " + cpuBracketCount
+                            + ". Max = " + MAX_CPU_BRACKET_COUNT);
+                }
+                mCpuUsageDetails.cpuBracketDescriptions = new String[cpuBracketCount];
+                for (int i = 0; i < cpuBracketCount; i++) {
+                    mCpuUsageDetails.cpuBracketDescriptions[i] = src.readString();
+                }
                 mCpuUsageDetails.cpuUsageMs =
                         new long[mCpuUsageDetails.cpuBracketDescriptions.length];
             } else if (mCpuUsageDetails != null) {
@@ -294,7 +334,8 @@
     /**
      * Should be called when iteration is complete.
      */
+    @Override
     public void close() {
-        mBatteryStatsHistory.finishIteratingHistory();
+        mBatteryStatsHistory.iteratorFinished();
     }
 }
diff --git a/core/java/com/android/internal/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java
index b5b27f52..145aeaf 100644
--- a/core/java/com/android/internal/policy/DecorView.java
+++ b/core/java/com/android/internal/policy/DecorView.java
@@ -274,6 +274,7 @@
     private boolean mApplyFloatingVerticalInsets = false;
     private boolean mApplyFloatingHorizontalInsets = false;
 
+    private int mResizeMode = RESIZE_MODE_INVALID;
     private final int mResizeShadowSize;
     private final Paint mVerticalResizeShadowPaint = new Paint();
     private final Paint mHorizontalResizeShadowPaint = new Paint();
@@ -807,7 +808,9 @@
         updateElevation();
         mAllowUpdateElevation = true;
 
-        if (changed && mDrawLegacyNavigationBarBackground) {
+        if (changed
+                && (mResizeMode == RESIZE_MODE_DOCKED_DIVIDER
+                    || mDrawLegacyNavigationBarBackground)) {
             getViewRootImpl().requestInvalidateRootRenderNode();
         }
     }
@@ -2389,7 +2392,7 @@
 
     @Override
     public void onWindowDragResizeStart(Rect initialBounds, boolean fullscreen, Rect systemInsets,
-            Rect stableInsets) {
+            Rect stableInsets, int resizeMode) {
         if (mWindow.isDestroyed()) {
             // If the owner's window is gone, we should not be able to come here anymore.
             releaseThreadedRenderer();
@@ -2415,6 +2418,7 @@
 
             updateColorViews(null /* insets */, false);
         }
+        mResizeMode = resizeMode;
         getViewRootImpl().requestInvalidateRootRenderNode();
     }
 
@@ -2422,6 +2426,7 @@
     public void onWindowDragResizeEnd() {
         releaseThreadedRenderer();
         updateColorViews(null /* insets */, false);
+        mResizeMode = RESIZE_MODE_INVALID;
         getViewRootImpl().requestInvalidateRootRenderNode();
     }
 
@@ -2466,7 +2471,9 @@
     }
 
     private void drawResizingShadowIfNeeded(RecordingCanvas canvas) {
-        if (mWindow.mIsFloating || mWindow.isTranslucent() || mWindow.isShowingWallpaper()) {
+        if (mResizeMode != RESIZE_MODE_DOCKED_DIVIDER || mWindow.mIsFloating
+                || mWindow.isTranslucent()
+                || mWindow.isShowingWallpaper()) {
             return;
         }
         canvas.save();
diff --git a/core/java/com/android/internal/security/TEST_MAPPING b/core/java/com/android/internal/security/TEST_MAPPING
index 803760c..b47ecec 100644
--- a/core/java/com/android/internal/security/TEST_MAPPING
+++ b/core/java/com/android/internal/security/TEST_MAPPING
@@ -14,6 +14,19 @@
     {
       "name": "ApkVerityTest",
       "file_patterns": ["VerityUtils\\.java"]
+    },
+    {
+      "name": "UpdatableSystemFontTest",
+      "file_patterns": ["VerityUtils\\.java"]
+    },
+    {
+      "name": "CtsAppSecurityHostTestCases",
+      "options": [
+        {
+          "include-filter": "android.appsecurity.cts.ApkVerityInstallTest"
+        }
+      ],
+      "file_patterns": ["VerityUtils\\.java"]
     }
   ]
 }
diff --git a/core/java/com/android/internal/telephony/ICarrierConfigChangeListener.aidl b/core/java/com/android/internal/telephony/ICarrierConfigChangeListener.aidl
new file mode 100644
index 0000000..0f7ab0a
--- /dev/null
+++ b/core/java/com/android/internal/telephony/ICarrierConfigChangeListener.aidl
@@ -0,0 +1,21 @@
+/*
+ * 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.internal.telephony;
+
+oneway interface ICarrierConfigChangeListener {
+    void onCarrierConfigChanged(int slotIndex, int subId, int carrierId, int specificCarrierId);
+}
\ No newline at end of file
diff --git a/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl b/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
index 7ba2686..54936c6 100644
--- a/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
+++ b/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
@@ -32,6 +32,7 @@
 import android.telephony.ServiceState;
 import android.telephony.SignalStrength;
 import android.telephony.emergency.EmergencyNumber;
+import com.android.internal.telephony.ICarrierConfigChangeListener;
 import com.android.internal.telephony.ICarrierPrivilegesCallback;
 import com.android.internal.telephony.IPhoneStateListener;
 import com.android.internal.telephony.IOnSubscriptionsChangedListener;
@@ -109,4 +110,8 @@
             int phoneId, in List<String> privilegedPackageNames, in int[] privilegedUids);
     void notifyCarrierServiceChanged(int phoneId, in String packageName, int uid);
 
+    void addCarrierConfigChangeListener(ICarrierConfigChangeListener listener,
+            String pkg, String featureId);
+    void removeCarrierConfigChangeListener(ICarrierConfigChangeListener listener, String pkg);
+    void notifyCarrierConfigChanged(int phoneId, int subId, int carrierId, int specificCarrierId);
 }
diff --git a/core/java/com/android/internal/view/BaseIWindow.java b/core/java/com/android/internal/view/BaseIWindow.java
index 4a5ed7e..2ac4309 100644
--- a/core/java/com/android/internal/view/BaseIWindow.java
+++ b/core/java/com/android/internal/view/BaseIWindow.java
@@ -53,7 +53,7 @@
     @Override
     public void resized(ClientWindowFrames frames, boolean reportDraw,
             MergedConfiguration mergedConfiguration, InsetsState insetsState, boolean forceLayout,
-            boolean alwaysConsumeSystemBars, int displayId, int seqId, boolean dragResizing) {
+            boolean alwaysConsumeSystemBars, int displayId, int seqId, int resizeMode) {
         if (reportDraw) {
             try {
                 mSession.finishDrawing(this, null /* postDrawTransaction */, seqId);
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index 953b36b..cc5de3e 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -1742,7 +1742,7 @@
     }
 
     public static boolean userOwnsFrpCredential(Context context, UserInfo info) {
-        return info != null && info.isPrimary() && info.isAdmin() && frpCredentialEnabled(context);
+        return info != null && info.isMain() && info.isAdmin() && frpCredentialEnabled(context);
     }
 
     public static boolean frpCredentialEnabled(Context context) {
diff --git a/core/jni/android_media_AudioFormat.h b/core/jni/android_media_AudioFormat.h
index 962b501..a9b1906 100644
--- a/core/jni/android_media_AudioFormat.h
+++ b/core/jni/android_media_AudioFormat.h
@@ -49,6 +49,7 @@
 #define ENCODING_DRA 28
 #define ENCODING_DTS_HD_MA 29
 #define ENCODING_DTS_UHD_P2 30
+#define ENCODING_DSD 31
 
 #define ENCODING_INVALID    0
 #define ENCODING_DEFAULT    1
@@ -122,6 +123,8 @@
         return AUDIO_FORMAT_DTS_HD_MA;
     case ENCODING_DTS_UHD_P2:
         return AUDIO_FORMAT_DTS_UHD_P2;
+    case ENCODING_DSD:
+        return AUDIO_FORMAT_DSD;
     default:
         return AUDIO_FORMAT_INVALID;
     }
@@ -201,6 +204,8 @@
         return ENCODING_DTS_UHD_P2;
     case AUDIO_FORMAT_DEFAULT:
         return ENCODING_DEFAULT;
+    case AUDIO_FORMAT_DSD:
+        return ENCODING_DSD;
     default:
         return ENCODING_INVALID;
     }
diff --git a/core/proto/OWNERS b/core/proto/OWNERS
index a4463e4..db391f7 100644
--- a/core/proto/OWNERS
+++ b/core/proto/OWNERS
@@ -16,6 +16,8 @@
 per-file package_item_info.proto = toddke@google.com,patb@google.com
 per-file usagestatsservice.proto, usagestatsservice_v2.proto = file:/core/java/android/app/usage/OWNERS
 per-file apphibernationservice.proto = file:/core/java/android/apphibernation/OWNERS
+per-file android/hardware/sensorprivacy.proto = ntmyren@google.com,evanseverson@google.com
+per-file background_install_control.proto = wenhaowang@google.com,georgechan@google.com,billylau@google.com
 
 # Biometrics
 jaggies@google.com
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 2cf241f..fd4d4f8 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -4975,6 +4975,15 @@
     <permission android:name="android.permission.ROTATE_SURFACE_FLINGER"
         android:protectionLevel="signature|recents" />
 
+    <!-- @SystemApi Allows an application to provide hints to SurfaceFlinger that can influence
+         its wakes up time to compose the next frame. This is a subset of the capabilities granted
+         by {@link #ACCESS_SURFACE_FLINGER}.
+         <p>Not for use by third-party applications.
+         @hide
+    -->
+    <permission android:name="android.permission.WAKEUP_SURFACE_FLINGER"
+        android:protectionLevel="signature|recents" />
+
     <!-- Allows an application to take screen shots and more generally
          get access to the frame buffer data.
          <p>Not for use by third-party applications.
diff --git a/core/res/OWNERS b/core/res/OWNERS
index a2ef400..b878189 100644
--- a/core/res/OWNERS
+++ b/core/res/OWNERS
@@ -21,6 +21,11 @@
 tsuji@google.com
 yamasani@google.com
 
+# WindowManager team
+# TODO(262451702): Move WindowManager configs out of config.xml in a separate file
+per-file core/res/res/values/config.xml = file:/services/core/java/com/android/server/wm/OWNERS
+per-file core/res/res/values/symbols.xml = file:/services/core/java/com/android/server/wm/OWNERS
+
 # Resources finalization
 per-file res/xml/public-staging.xml = file:/tools/aapt2/OWNERS
 per-file res/xml/public-final.xml = file:/tools/aapt2/OWNERS
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 2fb766e..f995a6e 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -5281,6 +5281,10 @@
     <!-- Whether using split screen aspect ratio as a default aspect ratio for unresizable apps. -->
     <bool name="config_letterboxIsSplitScreenAspectRatioForUnresizableAppsEnabled">false</bool>
 
+    <!-- Whether the specific behaviour for translucent activities letterboxing is enabled.
+         TODO(b/255532890) Enable when ignoreOrientationRequest is set -->
+    <bool name="config_letterboxIsEnabledForTranslucentActivities">false</bool>
+
     <!-- Whether a camera compat controller is enabled to allow the user to apply or revert
          treatment for stretched issues in camera viewfinder. -->
     <bool name="config_isCameraCompatControlForStretchedIssuesEnabled">false</bool>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index e8304d8..151530b 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -4397,6 +4397,9 @@
   <!-- Set to true to make assistant show in front of the dream/screensaver. -->
   <java-symbol type="bool" name="config_assistantOnTopOfDream"/>
 
+  <!-- Set to true to enable letterboxing on translucent activities. -->
+  <java-symbol type="bool" name="config_letterboxIsEnabledForTranslucentActivities" />
+
   <java-symbol type="string" name="config_overrideComponentUiPackage" />
 
   <java-symbol type="string" name="notification_channel_network_status" />
diff --git a/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/functional/RadioTunerTest.java b/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/functional/RadioTunerTest.java
index 3f35e99..cabeb13 100644
--- a/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/functional/RadioTunerTest.java
+++ b/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/functional/RadioTunerTest.java
@@ -17,6 +17,7 @@
 
 import static org.junit.Assert.*;
 import static org.junit.Assume.*;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.anyBoolean;
 import static org.mockito.Matchers.anyInt;
@@ -335,8 +336,10 @@
         assertEquals(RadioManager.STATUS_OK, scanRet);
         assertEquals(RadioManager.STATUS_OK, cancelRet);
 
-        verify(mCallback, after(kCancelTimeoutMs).atMost(1)).onError(RadioTuner.ERROR_CANCELLED);
+        verify(mCallback, after(kCancelTimeoutMs).atMost(1))
+                .onTuneFailed(eq(RadioTuner.TUNER_RESULT_CANCELED), any());
         verify(mCallback, atMost(1)).onProgramInfoChanged(any());
+        Mockito.reset(mCallback);
     }
 
     @Test
diff --git a/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/DefaultRadioTunerTest.java b/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/DefaultRadioTunerTest.java
index 2fa3f876..65e55a2 100644
--- a/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/DefaultRadioTunerTest.java
+++ b/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/DefaultRadioTunerTest.java
@@ -130,6 +130,18 @@
     };
 
     @Test
+    public void seek_forRadioTuner_throwsException() {
+        UnsupportedOperationException thrown = assertThrows(
+                UnsupportedOperationException.class, () -> {
+                    DEFAULT_RADIO_TUNER.seek(RadioTuner.DIRECTION_DOWN,
+                            /* skipSubChannel= */ false);
+                });
+
+        assertWithMessage("Exception for seeking on default radio tuner")
+                .that(thrown).hasMessageThat().contains("Seeking is not supported");
+    }
+
+    @Test
     public void getDynamicProgramList_forRadioTuner_returnsNull() {
         assertWithMessage("Dynamic program list obtained from default radio tuner")
                 .that(DEFAULT_RADIO_TUNER.getDynamicProgramList(new ProgramList.Filter())).isNull();
@@ -143,29 +155,45 @@
 
     @Test
     public void isConfigFlagSet_forRadioTuner_throwsException() {
-        assertThrows(UnsupportedOperationException.class, () -> {
-            DEFAULT_RADIO_TUNER.isConfigFlagSet(/* flag= */ 1);
-        });
+        UnsupportedOperationException thrown = assertThrows(
+                UnsupportedOperationException.class, () -> {
+                    DEFAULT_RADIO_TUNER.isConfigFlagSet(/* flag= */ 1);
+                });
+
+        assertWithMessage("Exception for isConfigFlagSet on default radio tuner")
+                .that(thrown).hasMessageThat().contains("isConfigFlagSet is not supported");
     }
 
     @Test
     public void setConfigFlag_forRadioTuner_throwsException() {
-        assertThrows(UnsupportedOperationException.class, () -> {
+        UnsupportedOperationException thrown = assertThrows(UnsupportedOperationException.class,
+                () -> {
             DEFAULT_RADIO_TUNER.setConfigFlag(/* flag= */ 1, /* value= */ false);
         });
+
+        assertWithMessage("Exception for setting config flag on default radio tuner")
+                .that(thrown).hasMessageThat().contains("Setting config flag is not supported");
     }
 
     @Test
     public void setParameters_forRadioTuner_throwsException() {
-        assertThrows(UnsupportedOperationException.class, () -> {
+        UnsupportedOperationException thrown = assertThrows(UnsupportedOperationException.class,
+                () -> {
             DEFAULT_RADIO_TUNER.setParameters(Map.of("testKey", "testValue"));
         });
+
+        assertWithMessage("Exception for setting parameters from default radio tuner")
+                .that(thrown).hasMessageThat().contains("Setting parameters is not supported");
     }
 
     @Test
     public void getParameters_forRadioTuner_throwsException() {
-        assertThrows(UnsupportedOperationException.class, () -> {
+        UnsupportedOperationException thrown = assertThrows(UnsupportedOperationException.class,
+                () -> {
             DEFAULT_RADIO_TUNER.getParameters(List.of("testKey"));
         });
+
+        assertWithMessage("Exception for getting parameters from default radio tuner")
+                .that(thrown).hasMessageThat().contains("Getting parameters is not supported");
     }
 }
diff --git a/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/ProgramListTest.java b/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/ProgramListTest.java
index 2b9de18..87f91fa 100644
--- a/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/ProgramListTest.java
+++ b/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/ProgramListTest.java
@@ -41,6 +41,8 @@
 import android.os.RemoteException;
 import android.util.ArraySet;
 
+import androidx.test.InstrumentationRegistry;
+
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
@@ -54,6 +56,8 @@
 @RunWith(MockitoJUnitRunner.class)
 public final class ProgramListTest {
 
+    public final Context mContext = InstrumentationRegistry.getContext();
+
     private static final int CREATOR_ARRAY_SIZE = 3;
     private static final VerificationWithTimeout CALLBACK_TIMEOUT = timeout(/* millis= */ 500);
 
@@ -109,8 +113,6 @@
     @Mock
     private IRadioService mRadioServiceMock;
     @Mock
-    private Context mContextMock;
-    @Mock
     private ITuner mTunerMock;
     @Mock
     private RadioTuner.Callback mTunerCallbackMock;
@@ -477,7 +479,7 @@
     }
 
     private void createRadioTuner() throws Exception {
-        RadioManager radioManager = new RadioManager(mContextMock, mRadioServiceMock);
+        RadioManager radioManager = new RadioManager(mContext, mRadioServiceMock);
         RadioManager.BandConfig band = new RadioManager.FmBandConfig(
                 new RadioManager.FmBandDescriptor(RadioManager.REGION_ITU_1, RadioManager.BAND_FM,
                         /* lowerLimit= */ 87500, /* upperLimit= */ 108000, /* spacing= */ 200,
@@ -487,7 +489,7 @@
         doAnswer(invocation -> {
             mTunerCallback = (ITunerCallback) invocation.getArguments()[3];
             return mTunerMock;
-        }).when(mRadioServiceMock).openTuner(anyInt(), any(), anyBoolean(), any());
+        }).when(mRadioServiceMock).openTuner(anyInt(), any(), anyBoolean(), any(), anyInt());
 
         mRadioTuner = radioManager.openTuner(/* moduleId= */ 0, band,
                 /* withAudio= */ true, mTunerCallbackMock, /* handler= */ null);
diff --git a/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/RadioManagerTest.java b/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/RadioManagerTest.java
index 44aa6d1..03742eb 100644
--- a/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/RadioManagerTest.java
+++ b/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/RadioManagerTest.java
@@ -18,14 +18,16 @@
 
 import static com.google.common.truth.Truth.assertWithMessage;
 
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.eq;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.annotation.Nullable;
 import android.content.Context;
+import android.content.pm.ApplicationInfo;
 import android.hardware.radio.Announcement;
 import android.hardware.radio.IAnnouncementListener;
 import android.hardware.radio.ICloseHandle;
@@ -34,6 +36,7 @@
 import android.hardware.radio.RadioManager;
 import android.hardware.radio.RadioMetadata;
 import android.hardware.radio.RadioTuner;
+import android.os.Build;
 import android.os.Parcel;
 import android.os.RemoteException;
 import android.util.ArrayMap;
@@ -53,6 +56,8 @@
 @RunWith(MockitoJUnitRunner.class)
 public final class RadioManagerTest {
 
+    private static final int TEST_TARGET_SDK_VERSION = Build.VERSION_CODES.CUR_DEVELOPMENT;
+
     private static final int REGION = RadioManager.REGION_ITU_2;
     private static final int FM_LOWER_LIMIT = 87500;
     private static final int FM_UPPER_LIMIT = 108000;
@@ -126,6 +131,7 @@
                     /* vendorInfo= */ new ArrayMap<>()));
 
     private RadioManager mRadioManager;
+    private final ApplicationInfo mApplicationInfo = new ApplicationInfo();
 
     @Mock
     private IRadioService mRadioServiceMock;
@@ -1008,7 +1014,8 @@
         mRadioManager.openTuner(moduleId, FM_BAND_CONFIG, withAudio, mCallbackMock,
                 /* handler= */ null);
 
-        verify(mRadioServiceMock).openTuner(eq(moduleId), eq(FM_BAND_CONFIG), eq(withAudio), any());
+        verify(mRadioServiceMock).openTuner(eq(moduleId), eq(FM_BAND_CONFIG), eq(withAudio), any(),
+                anyInt());
     }
 
     @Test
@@ -1103,6 +1110,8 @@
     }
 
     private void createRadioManager() throws RemoteException {
+        mApplicationInfo.targetSdkVersion = TEST_TARGET_SDK_VERSION;
+        when(mContextMock.getApplicationInfo()).thenReturn(mApplicationInfo);
         when(mRadioServiceMock.listModules()).thenReturn(Arrays.asList(AMFM_PROPERTIES));
         when(mRadioServiceMock.addAnnouncementListener(any(), any())).thenReturn(mCloseHandleMock);
 
diff --git a/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/TunerAdapterTest.java b/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/TunerAdapterTest.java
index bdba6a1..d851a7724 100644
--- a/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/TunerAdapterTest.java
+++ b/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/TunerAdapterTest.java
@@ -27,6 +27,7 @@
 import static org.mockito.Mockito.when;
 
 import android.content.Context;
+import android.content.pm.ApplicationInfo;
 import android.graphics.Bitmap;
 import android.hardware.radio.IRadioService;
 import android.hardware.radio.ITuner;
@@ -35,6 +36,7 @@
 import android.hardware.radio.RadioManager;
 import android.hardware.radio.RadioMetadata;
 import android.hardware.radio.RadioTuner;
+import android.os.Build;
 
 import org.junit.After;
 import org.junit.Before;
@@ -51,11 +53,12 @@
 @RunWith(MockitoJUnitRunner.class)
 public final class TunerAdapterTest {
 
+    private static final int TEST_TARGET_SDK_VERSION = Build.VERSION_CODES.CUR_DEVELOPMENT;
+
     private static final int CALLBACK_TIMEOUT_MS = 30_000;
     private static final int AM_LOWER_LIMIT_KHZ = 150;
 
     private static final RadioManager.BandConfig TEST_BAND_CONFIG = createBandConfig();
-
     private static final ProgramSelector.Identifier FM_IDENTIFIER =
             new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY,
                     /* value= */ 94300);
@@ -66,6 +69,7 @@
 
     private RadioTuner mRadioTuner;
     private ITunerCallback mTunerCallback;
+    private final ApplicationInfo mApplicationInfo = new ApplicationInfo();
 
     @Mock
     private IRadioService mRadioServiceMock;
@@ -78,12 +82,14 @@
 
     @Before
     public void setUp() throws Exception {
+        mApplicationInfo.targetSdkVersion = TEST_TARGET_SDK_VERSION;
+        when(mContextMock.getApplicationInfo()).thenReturn(mApplicationInfo);
         RadioManager radioManager = new RadioManager(mContextMock, mRadioServiceMock);
 
         doAnswer(invocation -> {
             mTunerCallback = (ITunerCallback) invocation.getArguments()[3];
             return mTunerMock;
-        }).when(mRadioServiceMock).openTuner(anyInt(), any(), anyBoolean(), any());
+        }).when(mRadioServiceMock).openTuner(anyInt(), any(), anyBoolean(), any(), anyInt());
 
         doAnswer(invocation -> {
             ProgramSelector program = (ProgramSelector) invocation.getArguments()[0];
@@ -92,7 +98,7 @@
                 throw new IllegalArgumentException();
             }
             if (program.getPrimaryId().getValue() < AM_LOWER_LIMIT_KHZ) {
-                mTunerCallback.onTuneFailed(RadioManager.STATUS_BAD_VALUE, program);
+                mTunerCallback.onTuneFailed(RadioTuner.TUNER_RESULT_INVALID_ARGUMENTS, program);
             } else {
                 mTunerCallback.onCurrentProgramInfoChanged(FM_PROGRAM_INFO);
             }
@@ -170,15 +176,30 @@
     }
 
     @Test
+    public void scan_forTunerAdapter_succeeds() throws Exception {
+        doAnswer(invocation -> {
+            mTunerCallback.onCurrentProgramInfoChanged(FM_PROGRAM_INFO);
+            return RadioManager.STATUS_OK;
+        }).when(mTunerMock).seek(anyBoolean(), anyBoolean());
+
+        int scanStatus = mRadioTuner.scan(RadioTuner.DIRECTION_DOWN, /* skipSubChannel= */ false);
+
+        verify(mTunerMock).seek(/* directionDown= */ true, /* skipSubChannel= */ false);
+        assertWithMessage("Status for scaning")
+                .that(scanStatus).isEqualTo(RadioManager.STATUS_OK);
+        verify(mCallbackMock, timeout(CALLBACK_TIMEOUT_MS)).onProgramInfoChanged(FM_PROGRAM_INFO);
+    }
+
+    @Test
     public void seek_forTunerAdapter_succeeds() throws Exception {
         doAnswer(invocation -> {
             mTunerCallback.onCurrentProgramInfoChanged(FM_PROGRAM_INFO);
             return RadioManager.STATUS_OK;
-        }).when(mTunerMock).scan(anyBoolean(), anyBoolean());
+        }).when(mTunerMock).seek(anyBoolean(), anyBoolean());
 
         int scanStatus = mRadioTuner.scan(RadioTuner.DIRECTION_DOWN, /* skipSubChannel= */ false);
 
-        verify(mTunerMock).scan(/* directionDown= */ true, /* skipSubChannel= */ false);
+        verify(mTunerMock).seek(/* directionDown= */ true, /* skipSubChannel= */ false);
         assertWithMessage("Status for seeking")
                 .that(scanStatus).isEqualTo(RadioManager.STATUS_OK);
         verify(mCallbackMock, timeout(CALLBACK_TIMEOUT_MS)).onProgramInfoChanged(FM_PROGRAM_INFO);
@@ -187,13 +208,14 @@
     @Test
     public void seek_forTunerAdapter_invokesOnErrorWhenTimeout() throws Exception {
         doAnswer(invocation -> {
-            mTunerCallback.onError(RadioTuner.ERROR_SCAN_TIMEOUT);
+            mTunerCallback.onTuneFailed(RadioTuner.TUNER_RESULT_TIMEOUT, FM_SELECTOR);
             return RadioManager.STATUS_OK;
-        }).when(mTunerMock).scan(anyBoolean(), anyBoolean());
+        }).when(mTunerMock).seek(anyBoolean(), anyBoolean());
 
         mRadioTuner.scan(RadioTuner.DIRECTION_UP, /* skipSubChannel*/ true);
 
-        verify(mCallbackMock, timeout(CALLBACK_TIMEOUT_MS)).onError(RadioTuner.ERROR_SCAN_TIMEOUT);
+        verify(mCallbackMock, timeout(CALLBACK_TIMEOUT_MS)).onTuneFailed(
+                RadioTuner.TUNER_RESULT_TIMEOUT, FM_SELECTOR);
     }
 
     @Test
@@ -224,7 +246,7 @@
         mRadioTuner.tune(invalidSelector);
 
         verify(mCallbackMock, timeout(CALLBACK_TIMEOUT_MS))
-                .onTuneFailed(RadioManager.STATUS_BAD_VALUE, invalidSelector);
+                .onTuneFailed(RadioTuner.TUNER_RESULT_INVALID_ARGUMENTS, invalidSelector);
     }
 
     @Test
@@ -415,6 +437,17 @@
     }
 
     @Test
+    public void onConfigFlagUpdated_forTunerCallbackAdapter() throws Exception {
+        int configFlag = RadioManager.CONFIG_RDS_AF;
+        boolean configFlagValue = true;
+
+        mTunerCallback.onConfigFlagUpdated(configFlag, configFlagValue);
+
+        verify(mCallbackMock, timeout(CALLBACK_TIMEOUT_MS))
+                .onConfigFlagUpdated(configFlag, configFlagValue);
+    }
+
+    @Test
     public void onParametersUpdated_forTunerCallbackAdapter() throws Exception {
         Map<String, String> parametersExpected = Map.of("ParameterKeyMock", "ParameterValueMock");
 
diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/IRadioServiceAidlImplTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/IRadioServiceAidlImplTest.java
index a2df426..9803474 100644
--- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/IRadioServiceAidlImplTest.java
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/IRadioServiceAidlImplTest.java
@@ -23,6 +23,7 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -33,6 +34,7 @@
 import android.hardware.radio.ITuner;
 import android.hardware.radio.ITunerCallback;
 import android.hardware.radio.RadioManager;
+import android.os.Build;
 import android.os.IBinder;
 import android.os.ServiceManager;
 
@@ -55,6 +57,7 @@
             "android.hardware.broadcastradio.IBroadcastRadio/amfm";
     private static final String DAB_SERVICE_NAME =
             "android.hardware.broadcastradio.IBroadcastRadio/dab";
+    private static final int TARGET_SDK_VERSION = Build.VERSION_CODES.CUR_DEVELOPMENT;
 
     private IRadioServiceAidlImpl mAidlImpl;
 
@@ -82,7 +85,7 @@
         doNothing().when(mServiceMock).enforcePolicyAccess();
 
         when(mHalMock.listModules()).thenReturn(List.of(mModuleMock));
-        when(mHalMock.openSession(anyInt(), any(), anyBoolean(), any()))
+        when(mHalMock.openSession(anyInt(), any(), anyBoolean(), any(), eq(TARGET_SDK_VERSION)))
                 .thenReturn(mTunerMock);
         when(mHalMock.addAnnouncementListener(any(), any())).thenReturn(mICloseHandle);
 
@@ -114,7 +117,7 @@
     @Test
     public void openTuner_forAidlImpl() throws Exception {
         ITuner tuner = mAidlImpl.openTuner(/* moduleId= */ 0, mBandConfigMock,
-                /* withAudio= */ true, mTunerCallbackMock);
+                /* withAudio= */ true, mTunerCallbackMock, TARGET_SDK_VERSION);
 
         assertWithMessage("Tuner opened in AIDL HAL")
                 .that(tuner).isEqualTo(mTunerMock);
diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/IRadioServiceHidlImplTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/IRadioServiceHidlImplTest.java
index 5ab9435..cfff477 100644
--- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/IRadioServiceHidlImplTest.java
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/IRadioServiceHidlImplTest.java
@@ -32,6 +32,7 @@
 import android.hardware.radio.ITuner;
 import android.hardware.radio.ITunerCallback;
 import android.hardware.radio.RadioManager;
+import android.os.Build;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -49,6 +50,7 @@
 
     private static final int HAL1_MODULE_ID = 0;
     private static final int[] ENABLE_TYPES = new int[]{Announcement.TYPE_TRAFFIC};
+    private static final int TARGET_SDK_VERSION = Build.VERSION_CODES.CUR_DEVELOPMENT;
 
     private IRadioServiceHidlImpl mHidlImpl;
 
@@ -103,7 +105,7 @@
     @Test
     public void openTuner_withHal1ModuleId_forHidlImpl() throws Exception {
         ITuner tuner = mHidlImpl.openTuner(HAL1_MODULE_ID, mBandConfigMock,
-                /* withAudio= */ true, mTunerCallbackMock);
+                /* withAudio= */ true, mTunerCallbackMock, TARGET_SDK_VERSION);
 
         assertWithMessage("Tuner opened in HAL 1")
                 .that(tuner).isEqualTo(mHal1TunerMock);
@@ -112,7 +114,7 @@
     @Test
     public void openTuner_withHal2ModuleId_forHidlImpl() throws Exception {
         ITuner tuner = mHidlImpl.openTuner(HAL1_MODULE_ID + 1, mBandConfigMock,
-                /* withAudio= */ true, mTunerCallbackMock);
+                /* withAudio= */ true, mTunerCallbackMock, TARGET_SDK_VERSION);
 
         assertWithMessage("Tuner opened in HAL 2")
                 .that(tuner).isEqualTo(mHal2TunerMock);
diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/BroadcastRadioServiceImplTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/BroadcastRadioServiceImplTest.java
index 1cc0a985..f404082 100644
--- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/BroadcastRadioServiceImplTest.java
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/BroadcastRadioServiceImplTest.java
@@ -36,6 +36,7 @@
 import android.hardware.radio.ITunerCallback;
 import android.hardware.radio.RadioManager;
 import android.hardware.radio.RadioTuner;
+import android.os.Build;
 import android.os.IBinder;
 import android.os.IServiceCallback;
 import android.os.RemoteException;
@@ -54,6 +55,8 @@
 
 public final class BroadcastRadioServiceImplTest extends ExtendedRadioMockitoTestCase {
 
+    private static final int TARGET_SDK_VERSION = Build.VERSION_CODES.CUR_DEVELOPMENT;
+
     private static final int FM_RADIO_MODULE_ID = 0;
     private static final int DAB_RADIO_MODULE_ID = 1;
     private static final ArrayList<String> SERVICE_LIST =
@@ -137,7 +140,8 @@
         createBroadcastRadioService();
 
         ITuner session = mBroadcastRadioService.openSession(FM_RADIO_MODULE_ID,
-                /* legacyConfig= */ null, /* withAudio= */ true, mTunerCallbackMock);
+                /* legacyConfig= */ null, /* withAudio= */ true, mTunerCallbackMock,
+                TARGET_SDK_VERSION);
 
         assertWithMessage("Session opened in FM radio module")
                 .that(session).isEqualTo(mFmTunerSessionMock);
@@ -148,7 +152,8 @@
         createBroadcastRadioService();
 
         ITuner session = mBroadcastRadioService.openSession(DAB_RADIO_MODULE_ID + 1,
-                /* legacyConfig= */ null, /* withAudio= */ true, mTunerCallbackMock);
+                /* legacyConfig= */ null, /* withAudio= */ true, mTunerCallbackMock,
+                TARGET_SDK_VERSION);
 
         assertWithMessage("Session opened with id not found").that(session).isNull();
     }
@@ -160,7 +165,8 @@
 
         IllegalStateException thrown = assertThrows(IllegalStateException.class,
                 () -> mBroadcastRadioService.openSession(FM_RADIO_MODULE_ID,
-                        /* legacyConfig= */ null, /* withAudio= */ true, mTunerCallbackMock));
+                        /* legacyConfig= */ null, /* withAudio= */ true, mTunerCallbackMock,
+                        TARGET_SDK_VERSION));
 
         assertWithMessage("Exception for opening session by non-current user")
                 .that(thrown).hasMessageThat().contains("Cannot open session for non-current user");
@@ -228,7 +234,7 @@
             return null;
         }).when(mFmBinderMock).linkToDeath(any(), anyInt());
 
-        when(mFmRadioModuleMock.openSession(eq(mTunerCallbackMock)))
+        when(mFmRadioModuleMock.openSession(eq(mTunerCallbackMock), eq(TARGET_SDK_VERSION)))
                 .thenReturn(mFmTunerSessionMock);
     }
 }
diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/ConversionUtilsResultTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/ConversionUtilsResultTest.java
new file mode 100644
index 0000000..df3ddfd
--- /dev/null
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/ConversionUtilsResultTest.java
@@ -0,0 +1,65 @@
+/*
+ * 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.broadcastradio.aidl;
+
+import android.hardware.broadcastradio.Result;
+import android.hardware.radio.RadioTuner;
+
+import com.google.common.truth.Expect;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.util.Arrays;
+import java.util.List;
+
+@RunWith(Parameterized.class)
+public final class ConversionUtilsResultTest {
+
+    private final int mHalResult;
+    private final int mTunerResult;
+
+    @Rule
+    public final Expect expect = Expect.create();
+
+    public ConversionUtilsResultTest(int halResult, int tunerResult) {
+        this.mHalResult = halResult;
+        this.mTunerResult = tunerResult;
+    }
+
+    @Parameterized.Parameters
+    public static List<Object[]> inputParameters() {
+        return Arrays.asList(new Object[][]{
+                {Result.OK, RadioTuner.TUNER_RESULT_OK},
+                {Result.INTERNAL_ERROR, RadioTuner.TUNER_RESULT_INTERNAL_ERROR},
+                {Result.INVALID_ARGUMENTS, RadioTuner.TUNER_RESULT_INVALID_ARGUMENTS},
+                {Result.INVALID_STATE, RadioTuner.TUNER_RESULT_INVALID_STATE},
+                {Result.NOT_SUPPORTED, RadioTuner.TUNER_RESULT_NOT_SUPPORTED},
+                {Result.TIMEOUT, RadioTuner.TUNER_RESULT_TIMEOUT},
+                {Result.UNKNOWN_ERROR, RadioTuner.TUNER_RESULT_UNKNOWN_ERROR}
+        });
+    }
+
+    @Test
+    public void halResultToTunerResult() {
+        expect.withMessage("Tuner result converted from AIDL HAL result %s", mHalResult)
+                .that(ConversionUtils.halResultToTunerResult(mHalResult))
+                .isEqualTo(mTunerResult);
+    }
+}
diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/ConversionUtilsTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/ConversionUtilsTest.java
index 3119554..a1cebb6 100644
--- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/ConversionUtilsTest.java
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/ConversionUtilsTest.java
@@ -25,6 +25,7 @@
 import android.hardware.radio.Announcement;
 import android.hardware.radio.ProgramSelector;
 import android.hardware.radio.RadioManager;
+import android.os.Build;
 
 import com.google.common.truth.Expect;
 
@@ -69,6 +70,18 @@
     public final Expect expect = Expect.create();
 
     @Test
+    public void isAtLeastU_withTSdkVersion_returnsFalse() {
+        expect.withMessage("Target SDK version of T")
+                .that(ConversionUtils.isAtLeastU(Build.VERSION_CODES.TIRAMISU)).isFalse();
+    }
+
+    @Test
+    public void isAtLeastU_withCurrentSdkVersion_returnsTrue() {
+        expect.withMessage("Target SDK version of U")
+                .that(ConversionUtils.isAtLeastU(Build.VERSION_CODES.CUR_DEVELOPMENT)).isTrue();
+    }
+
+    @Test
     public void propertiesFromHalProperties_idsMatch() {
         expect.withMessage("Properties id")
                 .that(MODULE_PROPERTIES.getId()).isEqualTo(TEST_ID);
diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java
index 993ca77..c5c6349 100644
--- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java
@@ -42,6 +42,7 @@
 import android.hardware.radio.ProgramSelector;
 import android.hardware.radio.RadioManager;
 import android.hardware.radio.RadioTuner;
+import android.os.Build;
 import android.os.ServiceSpecificException;
 import android.util.ArrayMap;
 import android.util.ArraySet;
@@ -65,6 +66,7 @@
  */
 public final class TunerSessionTest extends ExtendedRadioMockitoTestCase {
 
+    private static final int TARGET_SDK_VERSION = Build.VERSION_CODES.CUR_DEVELOPMENT;
     private static final VerificationWithTimeout CALLBACK_TIMEOUT =
             timeout(/* millis= */ 200);
     private static final int SIGNAL_QUALITY = 1;
@@ -299,6 +301,18 @@
     }
 
     @Test
+    public void tune_withLowerSdkVersion() throws Exception {
+        openAidlClients(/* numClients= */ 1, Build.VERSION_CODES.TIRAMISU);
+        ProgramSelector initialSel = AidlTestUtils.makeFmSelector(AM_FM_FREQUENCY_LIST[1]);
+        RadioManager.ProgramInfo tuneInfo =
+                AidlTestUtils.makeProgramInfo(initialSel, SIGNAL_QUALITY);
+
+        mTunerSessions[0].tune(initialSel);
+
+        verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT).onCurrentProgramInfoChanged(tuneInfo);
+    }
+
+    @Test
     public void tune_withMultipleSessions() throws Exception {
         int numSessions = 3;
         openAidlClients(numSessions);
@@ -377,49 +391,49 @@
     }
 
     @Test
-    public void scan_withDirectionUp() throws Exception {
+    public void seek_withDirectionUp() throws Exception {
         long initFreq = AM_FM_FREQUENCY_LIST[2];
         ProgramSelector initialSel = AidlTestUtils.makeFmSelector(initFreq);
-        RadioManager.ProgramInfo scanUpInfo = AidlTestUtils.makeProgramInfo(
+        RadioManager.ProgramInfo seekUpInfo = AidlTestUtils.makeProgramInfo(
                 AidlTestUtils.makeFmSelector(getSeekFrequency(initFreq, /* seekDown= */ false)),
                 SIGNAL_QUALITY);
         openAidlClients(/* numClients= */ 1);
         mHalCurrentInfo = AidlTestUtils.makeHalProgramInfo(
                 ConversionUtils.programSelectorToHalProgramSelector(initialSel), SIGNAL_QUALITY);
 
-        mTunerSessions[0].scan(/* directionDown= */ false, /* skipSubChannel= */ false);
+        mTunerSessions[0].seek(/* directionDown= */ false, /* skipSubChannel= */ false);
 
         verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT)
-                .onCurrentProgramInfoChanged(scanUpInfo);
+                .onCurrentProgramInfoChanged(seekUpInfo);
     }
 
     @Test
-    public void scan_callsOnTuneFailedWhenTimeout() throws Exception {
+    public void seek_callsOnTuneFailedWhenTimeout() throws Exception {
         int numSessions = 2;
         openAidlClients(numSessions);
 
-        mTunerSessions[0].scan(/* directionDown= */ false, /* skipSubChannel= */ false);
+        mTunerSessions[0].seek(/* directionDown= */ false, /* skipSubChannel= */ false);
 
         for (int index = 0; index < numSessions; index++) {
             verify(mAidlTunerCallbackMocks[index], CALLBACK_TIMEOUT)
-                    .onTuneFailed(eq(Result.TIMEOUT), any());
+                    .onTuneFailed(eq(RadioTuner.TUNER_RESULT_TIMEOUT), any());
         }
     }
 
     @Test
-    public void scan_withDirectionDown() throws Exception {
+    public void seek_withDirectionDown() throws Exception {
         long initFreq = AM_FM_FREQUENCY_LIST[2];
         ProgramSelector initialSel = AidlTestUtils.makeFmSelector(initFreq);
-        RadioManager.ProgramInfo scanUpInfo = AidlTestUtils.makeProgramInfo(
+        RadioManager.ProgramInfo seekUpInfo = AidlTestUtils.makeProgramInfo(
                 AidlTestUtils.makeFmSelector(getSeekFrequency(initFreq, /* seekDown= */ true)),
                 SIGNAL_QUALITY);
         openAidlClients(/* numClients= */ 1);
         mHalCurrentInfo = AidlTestUtils.makeHalProgramInfo(
                 ConversionUtils.programSelectorToHalProgramSelector(initialSel), SIGNAL_QUALITY);
 
-        mTunerSessions[0].scan(/* directionDown= */ true, /* skipSubChannel= */ false);
+        mTunerSessions[0].seek(/* directionDown= */ true, /* skipSubChannel= */ false);
         verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT)
-                .onCurrentProgramInfoChanged(scanUpInfo);
+                .onCurrentProgramInfoChanged(seekUpInfo);
     }
 
     @Test
@@ -585,7 +599,7 @@
     }
 
     @Test
-    public void onConfigFlagUpdated_forTunerCallback() throws Exception {
+    public void onAntennaStateChange_forTunerCallback() throws Exception {
         int numSessions = 3;
         openAidlClients(numSessions);
 
@@ -598,6 +612,21 @@
     }
 
     @Test
+    public void onConfigFlagUpdated_forTunerCallback() throws Exception {
+        int numSessions = 3;
+        openAidlClients(numSessions);
+        int flag = UNSUPPORTED_CONFIG_FLAG + 1;
+        boolean configFlagValue = true;
+
+        mHalTunerCallback.onConfigFlagUpdated(flag, configFlagValue);
+
+        for (int index = 0; index < numSessions; index++) {
+            verify(mAidlTunerCallbackMocks[index], CALLBACK_TIMEOUT)
+                    .onConfigFlagUpdated(flag, configFlagValue);
+        }
+    }
+
+    @Test
     public void onParametersUpdated_forTunerCallback() throws Exception {
         int numSessions = 3;
         openAidlClients(numSessions);
@@ -612,13 +641,17 @@
                     .onParametersUpdated(parametersExpected);
         }
     }
-
     private void openAidlClients(int numClients) throws Exception {
+        openAidlClients(numClients, TARGET_SDK_VERSION);
+    }
+
+    private void openAidlClients(int numClients, int targetSdkVersion) throws Exception {
         mAidlTunerCallbackMocks = new android.hardware.radio.ITunerCallback[numClients];
         mTunerSessions = new TunerSession[numClients];
         for (int index = 0; index < numClients; index++) {
             mAidlTunerCallbackMocks[index] = mock(android.hardware.radio.ITunerCallback.class);
-            mTunerSessions[index] = mRadioModule.openSession(mAidlTunerCallbackMocks[index]);
+            mTunerSessions[index] = mRadioModule.openSession(mAidlTunerCallbackMocks[index],
+                    targetSdkVersion);
         }
     }
 
diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/ConvertResultTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/ConvertResultTest.java
new file mode 100644
index 0000000..d106de9
--- /dev/null
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/ConvertResultTest.java
@@ -0,0 +1,66 @@
+/*
+ * 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.broadcastradio.hal2;
+
+import android.hardware.broadcastradio.V2_0.Result;
+import android.hardware.radio.RadioTuner;
+
+import com.google.common.truth.Expect;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.util.Arrays;
+import java.util.List;
+
+@RunWith(Parameterized.class)
+public final class ConvertResultTest {
+
+    private final int mHalResult;
+    private final int mTunerResult;
+
+    @Rule
+    public final Expect expect = Expect.create();
+
+    public ConvertResultTest(int halResult, int tunerResult) {
+        this.mHalResult = halResult;
+        this.mTunerResult = tunerResult;
+    }
+
+    @Parameterized.Parameters
+    public static List<Object[]> inputParameters() {
+        return Arrays.asList(new Object[][]{
+                {Result.OK, RadioTuner.TUNER_RESULT_OK},
+                {Result.INTERNAL_ERROR, RadioTuner.TUNER_RESULT_INTERNAL_ERROR},
+                {Result.INVALID_ARGUMENTS, RadioTuner.TUNER_RESULT_INVALID_ARGUMENTS},
+                {Result.INVALID_STATE, RadioTuner.TUNER_RESULT_INVALID_STATE},
+                {Result.NOT_SUPPORTED, RadioTuner.TUNER_RESULT_NOT_SUPPORTED},
+                {Result.TIMEOUT, RadioTuner.TUNER_RESULT_TIMEOUT},
+                {Result.UNKNOWN_ERROR, RadioTuner.TUNER_RESULT_UNKNOWN_ERROR}
+        });
+    }
+
+    @Test
+    public void halResultToTunerResult() {
+        expect.withMessage("Tuner result converted from HAL 2.0 result %s",
+                Result.toString(mHalResult))
+                .that(Convert.halResultToTunerResult(mHalResult))
+                .isEqualTo(mTunerResult);
+    }
+}
diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/TunerSessionHidlTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/TunerSessionHidlTest.java
index ff988a2..db16c03 100644
--- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/TunerSessionHidlTest.java
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/TunerSessionHidlTest.java
@@ -384,49 +384,49 @@
     }
 
     @Test
-    public void scan_withDirectionUp() throws Exception {
+    public void seek_withDirectionUp() throws Exception {
         long initFreq = AM_FM_FREQUENCY_LIST[2];
         ProgramSelector initialSel = TestUtils.makeFmSelector(initFreq);
-        RadioManager.ProgramInfo scanUpInfo = TestUtils.makeProgramInfo(
+        RadioManager.ProgramInfo seekUpInfo = TestUtils.makeProgramInfo(
                 TestUtils.makeFmSelector(getSeekFrequency(initFreq, /* seekDown= */ false)),
                 SIGNAL_QUALITY);
         openAidlClients(/* numClients= */ 1);
         mHalCurrentInfo = TestUtils.makeHalProgramInfo(
                 Convert.programSelectorToHal(initialSel), SIGNAL_QUALITY);
 
-        mTunerSessions[0].scan(/* directionDown= */ false, /* skipSubChannel= */ false);
+        mTunerSessions[0].seek(/* directionDown= */ false, /* skipSubChannel= */ false);
 
         verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT)
-                .onCurrentProgramInfoChanged(scanUpInfo);
+                .onCurrentProgramInfoChanged(seekUpInfo);
     }
 
     @Test
-    public void scan_callsOnTuneFailedWhenTimeout() throws Exception {
+    public void seek_callsOnTuneFailedWhenTimeout() throws Exception {
         int numSessions = 2;
         openAidlClients(numSessions);
 
-        mTunerSessions[0].scan(/* directionDown= */ false, /* skipSubChannel= */ false);
+        mTunerSessions[0].seek(/* directionDown= */ false, /* skipSubChannel= */ false);
 
         for (int index = 0; index < numSessions; index++) {
             verify(mAidlTunerCallbackMocks[index], CALLBACK_TIMEOUT)
-                    .onTuneFailed(eq(Result.TIMEOUT), any());
+                    .onTuneFailed(eq(RadioTuner.TUNER_RESULT_TIMEOUT), any());
         }
     }
 
     @Test
-    public void scan_withDirectionDown() throws Exception {
+    public void seek_withDirectionDown() throws Exception {
         long initFreq = AM_FM_FREQUENCY_LIST[2];
         ProgramSelector initialSel = TestUtils.makeFmSelector(initFreq);
-        RadioManager.ProgramInfo scanUpInfo = TestUtils.makeProgramInfo(
+        RadioManager.ProgramInfo seekUpInfo = TestUtils.makeProgramInfo(
                 TestUtils.makeFmSelector(getSeekFrequency(initFreq, /* seekDown= */ true)),
                 SIGNAL_QUALITY);
         openAidlClients(/* numClients= */ 1);
         mHalCurrentInfo = TestUtils.makeHalProgramInfo(
                 Convert.programSelectorToHal(initialSel), SIGNAL_QUALITY);
 
-        mTunerSessions[0].scan(/* directionDown= */ true, /* skipSubChannel= */ false);
+        mTunerSessions[0].seek(/* directionDown= */ true, /* skipSubChannel= */ false);
         verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT)
-                .onCurrentProgramInfoChanged(scanUpInfo);
+                .onCurrentProgramInfoChanged(seekUpInfo);
     }
 
     @Test
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index 73f25b1..d4ac531b 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -619,6 +619,12 @@
       "group": "WM_DEBUG_CONFIGURATION",
       "at": "com\/android\/server\/wm\/ActivityStarter.java"
     },
+    "-1484988952": {
+      "message": "Creating Pending Multiwindow Fullscreen Request: %s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+      "at": "com\/android\/server\/wm\/ActivityClientController.java"
+    },
     "-1483435730": {
       "message": "InsetsSource setWin %s for type %s",
       "level": "DEBUG",
diff --git a/libs/WindowManager/Shell/AndroidManifest.xml b/libs/WindowManager/Shell/AndroidManifest.xml
index 8881be7..36d3313 100644
--- a/libs/WindowManager/Shell/AndroidManifest.xml
+++ b/libs/WindowManager/Shell/AndroidManifest.xml
@@ -21,5 +21,6 @@
     <uses-permission android:name="android.permission.CAPTURE_BLACKOUT_CONTENT" />
     <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
     <uses-permission android:name="android.permission.ROTATE_SURFACE_FLINGER" />
+    <uses-permission android:name="android.permission.WAKEUP_SURFACE_FLINGER" />
     <uses-permission android:name="android.permission.READ_FRAME_BUFFER" />
 </manifest>
diff --git a/libs/WindowManager/Shell/res/layout/tv_pip_menu.xml b/libs/WindowManager/Shell/res/layout/tv_pip_menu.xml
index 70755e6..dcce4698 100644
--- a/libs/WindowManager/Shell/res/layout/tv_pip_menu.xml
+++ b/libs/WindowManager/Shell/res/layout/tv_pip_menu.xml
@@ -44,67 +44,15 @@
             android:background="@color/tv_pip_menu_dim_layer"
             android:alpha="0"/>
 
-        <ScrollView
-            android:id="@+id/tv_pip_menu_scroll"
-            android:layout_width="match_parent"
-            android:layout_height="match_parent"
-            android:scrollbars="none"
-            android:visibility="gone"/>
-
-        <HorizontalScrollView
-            android:id="@+id/tv_pip_menu_horizontal_scroll"
-            android:layout_width="match_parent"
-            android:layout_height="match_parent"
-            android:scrollbars="none">
-
-            <LinearLayout
-                android:id="@+id/tv_pip_menu_action_buttons"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:orientation="horizontal"
-                android:alpha="0">
-
-                <Space
-                    android:layout_width="@dimen/pip_menu_button_wrapper_margin"
-                    android:layout_height="@dimen/pip_menu_button_wrapper_margin"/>
-
-                <com.android.wm.shell.common.TvWindowMenuActionButton
-                    android:id="@+id/tv_pip_menu_fullscreen_button"
-                    android:layout_width="wrap_content"
-                    android:layout_height="wrap_content"
-                    android:src="@drawable/pip_ic_fullscreen_white"
-                    android:text="@string/pip_fullscreen" />
-
-                <com.android.wm.shell.common.TvWindowMenuActionButton
-                    android:id="@+id/tv_pip_menu_close_button"
-                    android:layout_width="wrap_content"
-                    android:layout_height="wrap_content"
-                    android:src="@drawable/pip_ic_close_white"
-                    android:text="@string/pip_close" />
-
-                <!-- More TvWindowMenuActionButtons may be added here at runtime. -->
-
-                <com.android.wm.shell.common.TvWindowMenuActionButton
-                    android:id="@+id/tv_pip_menu_move_button"
-                    android:layout_width="wrap_content"
-                    android:layout_height="wrap_content"
-                    android:src="@drawable/pip_ic_move_white"
-                    android:text="@string/pip_move" />
-
-                <com.android.wm.shell.common.TvWindowMenuActionButton
-                    android:id="@+id/tv_pip_menu_expand_button"
-                    android:layout_width="wrap_content"
-                    android:layout_height="wrap_content"
-                    android:src="@drawable/pip_ic_collapse"
-                    android:visibility="gone"
-                    android:text="@string/pip_collapse" />
-
-                <Space
-                    android:layout_width="@dimen/pip_menu_button_wrapper_margin"
-                    android:layout_height="@dimen/pip_menu_button_wrapper_margin"/>
-
-            </LinearLayout>
-        </HorizontalScrollView>
+        <com.android.internal.widget.RecyclerView
+            android:id="@+id/tv_pip_menu_action_buttons"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center"
+            android:padding="@dimen/pip_menu_button_start_end_offset"
+            android:clipToPadding="false"
+            android:alpha="0"
+            android:contentDescription="@string/a11y_pip_menu_entered"/>
     </FrameLayout>
 
     <!-- Frame around the content, just overlapping the corners to make them round -->
diff --git a/libs/WindowManager/Shell/res/layout/tv_window_menu_action_button.xml b/libs/WindowManager/Shell/res/layout/tv_window_menu_action_button.xml
index c4dbd39..b2ac85b 100644
--- a/libs/WindowManager/Shell/res/layout/tv_window_menu_action_button.xml
+++ b/libs/WindowManager/Shell/res/layout/tv_window_menu_action_button.xml
@@ -21,6 +21,7 @@
     android:layout_width="@dimen/tv_window_menu_button_size"
     android:layout_height="@dimen/tv_window_menu_button_size"
     android:padding="@dimen/tv_window_menu_button_margin"
+    android:duplicateParentState="true"
     android:stateListAnimator="@animator/tv_window_menu_action_button_animator"
     android:focusable="true">
 
diff --git a/libs/WindowManager/Shell/res/values-hi/strings.xml b/libs/WindowManager/Shell/res/values-hi/strings.xml
index 1a7cf3e..9a926d8 100644
--- a/libs/WindowManager/Shell/res/values-hi/strings.xml
+++ b/libs/WindowManager/Shell/res/values-hi/strings.xml
@@ -34,8 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"दिखाएं"</string>
     <string name="dock_forced_resizable" msgid="1749750436092293116">"ऐप्लिकेशन शायद स्प्लिट स्क्रीन मोड में काम न करे."</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"ऐप विभाजित स्‍क्रीन का समर्थन नहीं करता है."</string>
-    <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
-    <skip />
+    <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"इस ऐप्लिकेशन को सिर्फ़ एक विंडो में खोला जा सकता है."</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"हो सकता है कि ऐप प्राइमरी (मुख्य) डिस्प्ले के अलावा बाकी दूसरे डिस्प्ले पर काम न करे."</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"प्राइमरी (मुख्य) डिस्प्ले के अलावा बाकी दूसरे डिस्प्ले पर ऐप लॉन्च नहीं किया जा सकता."</string>
     <string name="accessibility_divider" msgid="703810061635792791">"विभाजित स्क्रीन विभाजक"</string>
diff --git a/libs/WindowManager/Shell/res/values-tvdpi/dimen.xml b/libs/WindowManager/Shell/res/values-tvdpi/dimen.xml
index 9833a88..0b61d7a 100644
--- a/libs/WindowManager/Shell/res/values-tvdpi/dimen.xml
+++ b/libs/WindowManager/Shell/res/values-tvdpi/dimen.xml
@@ -28,7 +28,7 @@
     <dimen name="pip_menu_background_corner_radius">6dp</dimen>
     <dimen name="pip_menu_border_width">4dp</dimen>
     <dimen name="pip_menu_outer_space">24dp</dimen>
-    <dimen name="pip_menu_button_wrapper_margin">26dp</dimen>
+    <dimen name="pip_menu_button_start_end_offset">30dp</dimen>
 
     <!-- outer space minus border width -->
     <dimen name="pip_menu_outer_space_frame">20dp</dimen>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java
index 94aeb2b..af13bf5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java
@@ -346,7 +346,7 @@
         public void resized(ClientWindowFrames frames, boolean reportDraw,
                 MergedConfiguration newMergedConfiguration, InsetsState insetsState,
                 boolean forceLayout, boolean alwaysConsumeSystemBars, int displayId, int syncSeqId,
-                boolean dragResizing) {}
+                int resizeMode) {}
 
         @Override
         public void insetsControlChanged(InsetsState insetsState,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/TvWindowMenuActionButton.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/TvWindowMenuActionButton.java
index 39b0b55..8ba785a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/TvWindowMenuActionButton.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/TvWindowMenuActionButton.java
@@ -19,6 +19,8 @@
 import android.content.Context;
 import android.content.res.TypedArray;
 import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Icon;
+import android.os.Handler;
 import android.util.AttributeSet;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -30,11 +32,11 @@
 /**
  * A common action button for TV window menu layouts.
  */
-public class TvWindowMenuActionButton extends RelativeLayout implements View.OnClickListener {
+public class TvWindowMenuActionButton extends RelativeLayout {
     private final ImageView mIconImageView;
     private final View mButtonBackgroundView;
-    private final View mButtonView;
-    private OnClickListener mOnClickListener;
+
+    private Icon mCurrentIcon;
 
     public TvWindowMenuActionButton(Context context) {
         this(context, null, 0, 0);
@@ -56,7 +58,6 @@
         inflater.inflate(R.layout.tv_window_menu_action_button, this);
 
         mIconImageView = findViewById(R.id.icon);
-        mButtonView = findViewById(R.id.button);
         mButtonBackgroundView = findViewById(R.id.background);
 
         final int[] values = new int[]{android.R.attr.src, android.R.attr.text};
@@ -71,23 +72,6 @@
         typedArray.recycle();
     }
 
-    @Override
-    public void setOnClickListener(OnClickListener listener) {
-        // We do not want to set an OnClickListener to the TvWindowMenuActionButton itself, but only
-        // to the ImageView. So let's "cash" the listener we've been passed here and set a "proxy"
-        // listener to the ImageView.
-        mOnClickListener = listener;
-        mButtonView.setOnClickListener(listener != null ? this : null);
-    }
-
-    @Override
-    public void onClick(View v) {
-        if (mOnClickListener != null) {
-            // Pass the correct view - this.
-            mOnClickListener.onClick(this);
-        }
-    }
-
     /**
      * Sets the drawable for the button with the given drawable.
      */
@@ -104,11 +88,24 @@
         }
     }
 
+    public void setImageIconAsync(Icon icon, Handler handler) {
+        mCurrentIcon = icon;
+        // Remove old image while waiting for the new one to load.
+        mIconImageView.setImageDrawable(null);
+        icon.loadDrawableAsync(mContext, d -> {
+            // The image hasn't been set any other way and the drawable belongs to the most
+            // recently set Icon.
+            if (mIconImageView.getDrawable() == null && mCurrentIcon == icon) {
+                mIconImageView.setImageDrawable(d);
+            }
+        }, handler);
+    }
+
     /**
      * Sets the text for description the with the given string.
      */
     public void setTextAndDescription(CharSequence text) {
-        mButtonView.setContentDescription(text);
+        setContentDescription(text);
     }
 
     /**
@@ -118,16 +115,6 @@
         setTextAndDescription(getContext().getString(resId));
     }
 
-    @Override
-    public void setEnabled(boolean enabled) {
-        mButtonView.setEnabled(enabled);
-    }
-
-    @Override
-    public boolean isEnabled() {
-        return mButtonView.isEnabled();
-    }
-
     /**
      * Marks this button as a custom close action button.
      * This changes the style of the action button to highlight that this action finishes the
@@ -147,10 +134,10 @@
 
     @Override
     public String toString() {
-        if (mButtonView.getContentDescription() == null) {
+        if (getContentDescription() == null) {
             return TvWindowMenuActionButton.class.getSimpleName();
         }
-        return mButtonView.getContentDescription().toString();
+        return getContentDescription().toString();
     }
 
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java
index 8022e9b..b144d22 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java
@@ -81,6 +81,7 @@
             PipParamsChangedForwarder pipParamsChangedForwarder,
             DisplayController displayController,
             WindowManagerShellWrapper windowManagerShellWrapper,
+            @ShellMainThread Handler mainHandler, // needed for registerReceiverForAllUsers()
             @ShellMainThread ShellExecutor mainExecutor) {
         return Optional.of(
                 TvPipController.create(
@@ -100,6 +101,7 @@
                         pipParamsChangedForwarder,
                         displayController,
                         windowManagerShellWrapper,
+                        mainHandler,
                         mainExecutor));
     }
 
@@ -157,22 +159,17 @@
             Context context,
             TvPipBoundsState tvPipBoundsState,
             SystemWindows systemWindows,
-            PipMediaController pipMediaController,
             @ShellMainThread Handler mainHandler) {
-        return new TvPipMenuController(context, tvPipBoundsState, systemWindows, pipMediaController,
-                mainHandler);
+        return new TvPipMenuController(context, tvPipBoundsState, systemWindows, mainHandler);
     }
 
-    // Handler needed for registerReceiverForAllUsers()
     @WMSingleton
     @Provides
     static TvPipNotificationController provideTvPipNotificationController(Context context,
             PipMediaController pipMediaController,
-            PipParamsChangedForwarder pipParamsChangedForwarder,
-            TvPipBoundsState tvPipBoundsState,
-            @ShellMainThread Handler mainHandler) {
+            PipParamsChangedForwarder pipParamsChangedForwarder) {
         return new TvPipNotificationController(context, pipMediaController,
-                pipParamsChangedForwarder, tvPipBoundsState, mainHandler);
+                pipParamsChangedForwarder);
     }
 
     @WMSingleton
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipAction.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipAction.java
new file mode 100644
index 0000000..222307f
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipAction.java
@@ -0,0 +1,87 @@
+/*
+ * 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.wm.shell.pip.tv;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.app.Notification;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.os.Handler;
+
+import com.android.wm.shell.common.TvWindowMenuActionButton;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
+
+abstract class TvPipAction {
+
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = {"ACTION_"}, value = {
+            ACTION_FULLSCREEN,
+            ACTION_CLOSE,
+            ACTION_MOVE,
+            ACTION_EXPAND_COLLAPSE,
+            ACTION_CUSTOM,
+            ACTION_CUSTOM_CLOSE
+    })
+    public @interface ActionType {
+    }
+
+    public static final int ACTION_FULLSCREEN = 0;
+    public static final int ACTION_CLOSE = 1;
+    public static final int ACTION_MOVE = 2;
+    public static final int ACTION_EXPAND_COLLAPSE = 3;
+    public static final int ACTION_CUSTOM = 4;
+    public static final int ACTION_CUSTOM_CLOSE = 5;
+
+    @ActionType
+    private final int mActionType;
+
+    @NonNull
+    private final SystemActionsHandler mSystemActionsHandler;
+
+    TvPipAction(@ActionType int actionType, @NonNull SystemActionsHandler systemActionsHandler) {
+        Objects.requireNonNull(systemActionsHandler);
+        mActionType = actionType;
+        mSystemActionsHandler = systemActionsHandler;
+    }
+
+    boolean isCloseAction() {
+        return mActionType == ACTION_CLOSE || mActionType == ACTION_CUSTOM_CLOSE;
+    }
+
+    @ActionType
+    int getActionType() {
+        return mActionType;
+    }
+
+    abstract void populateButton(@NonNull TvWindowMenuActionButton button, Handler mainHandler);
+
+    abstract PendingIntent getPendingIntent();
+
+    void executeAction() {
+        mSystemActionsHandler.executeAction(mActionType);
+    }
+
+    abstract Notification.Action toNotificationAction(Context context);
+
+    interface SystemActionsHandler {
+        void executeAction(@TvPipAction.ActionType int actionType);
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipActionsProvider.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipActionsProvider.java
new file mode 100644
index 0000000..fa62a73
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipActionsProvider.java
@@ -0,0 +1,245 @@
+/*
+ * 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.wm.shell.pip.tv;
+
+import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
+import static com.android.wm.shell.pip.tv.TvPipAction.ACTION_CLOSE;
+import static com.android.wm.shell.pip.tv.TvPipAction.ACTION_CUSTOM;
+import static com.android.wm.shell.pip.tv.TvPipAction.ACTION_CUSTOM_CLOSE;
+import static com.android.wm.shell.pip.tv.TvPipAction.ACTION_EXPAND_COLLAPSE;
+import static com.android.wm.shell.pip.tv.TvPipAction.ACTION_FULLSCREEN;
+import static com.android.wm.shell.pip.tv.TvPipAction.ACTION_MOVE;
+import static com.android.wm.shell.pip.tv.TvPipController.ACTION_CLOSE_PIP;
+import static com.android.wm.shell.pip.tv.TvPipController.ACTION_MOVE_PIP;
+import static com.android.wm.shell.pip.tv.TvPipController.ACTION_TOGGLE_EXPANDED_PIP;
+import static com.android.wm.shell.pip.tv.TvPipController.ACTION_TO_FULLSCREEN;
+
+import android.annotation.NonNull;
+import android.app.RemoteAction;
+import android.content.Context;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.R;
+import com.android.wm.shell.pip.PipMediaController;
+import com.android.wm.shell.pip.PipUtils;
+import com.android.wm.shell.protolog.ShellProtoLogGroup;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Creates the system TvPipActions (fullscreen, close, move, expand/collapse),  and handles all the
+ * changes to the actions, including the custom app actions and media actions. Other components can
+ * listen to those changes.
+ */
+public class TvPipActionsProvider implements TvPipAction.SystemActionsHandler {
+    private static final String TAG = TvPipActionsProvider.class.getSimpleName();
+
+    private static final int CLOSE_ACTION_INDEX = 1;
+    private static final int FIRST_CUSTOM_ACTION_INDEX = 2;
+
+    private final List<Listener> mListeners = new ArrayList<>();
+    private final TvPipAction.SystemActionsHandler mSystemActionsHandler;
+
+    private final List<TvPipAction> mActionsList;
+    private final TvPipSystemAction mDefaultCloseAction;
+    private final TvPipSystemAction mExpandCollapseAction;
+
+    private final List<RemoteAction> mMediaActions = new ArrayList<>();
+    private final List<RemoteAction> mAppActions = new ArrayList<>();
+
+    public TvPipActionsProvider(Context context, PipMediaController pipMediaController,
+            TvPipAction.SystemActionsHandler systemActionsHandler) {
+        mSystemActionsHandler = systemActionsHandler;
+
+        mActionsList = new ArrayList<>();
+        mActionsList.add(new TvPipSystemAction(ACTION_FULLSCREEN, R.string.pip_fullscreen,
+                R.drawable.pip_ic_fullscreen_white, ACTION_TO_FULLSCREEN, context,
+                mSystemActionsHandler));
+
+        mDefaultCloseAction = new TvPipSystemAction(ACTION_CLOSE, R.string.pip_close,
+                R.drawable.pip_ic_close_white, ACTION_CLOSE_PIP, context, mSystemActionsHandler);
+        mActionsList.add(mDefaultCloseAction);
+
+        mActionsList.add(new TvPipSystemAction(ACTION_MOVE, R.string.pip_move,
+                R.drawable.pip_ic_move_white, ACTION_MOVE_PIP, context, mSystemActionsHandler));
+
+        mExpandCollapseAction = new TvPipSystemAction(ACTION_EXPAND_COLLAPSE, R.string.pip_collapse,
+                R.drawable.pip_ic_collapse, ACTION_TOGGLE_EXPANDED_PIP, context,
+                mSystemActionsHandler);
+        mActionsList.add(mExpandCollapseAction);
+
+        pipMediaController.addActionListener(this::onMediaActionsChanged);
+    }
+
+    @Override
+    public void executeAction(@TvPipAction.ActionType int actionType) {
+        if (mSystemActionsHandler != null) {
+            mSystemActionsHandler.executeAction(actionType);
+        }
+    }
+
+    private void notifyActionsChanged(int added, int changed, int startIndex) {
+        for (Listener listener : mListeners) {
+            listener.onActionsChanged(added, changed, startIndex);
+        }
+    }
+
+    @VisibleForTesting(visibility = PACKAGE)
+    public void setAppActions(@NonNull List<RemoteAction> appActions, RemoteAction closeAction) {
+        // Update close action.
+        mActionsList.set(CLOSE_ACTION_INDEX,
+                closeAction == null ? mDefaultCloseAction
+                        : new TvPipCustomAction(ACTION_CUSTOM_CLOSE, closeAction,
+                                mSystemActionsHandler));
+        notifyActionsChanged(/* added= */ 0, /* updated= */ 1, CLOSE_ACTION_INDEX);
+
+        // Replace custom actions with new ones.
+        mAppActions.clear();
+        for (RemoteAction action : appActions) {
+            if (action != null && !PipUtils.remoteActionsMatch(action, closeAction)) {
+                // Only show actions that aren't duplicates of the custom close action.
+                mAppActions.add(action);
+            }
+        }
+
+        updateCustomActions(mAppActions);
+    }
+
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    public void onMediaActionsChanged(List<RemoteAction> actions) {
+        ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+                "%s: onMediaActionsChanged()", TAG);
+
+        mMediaActions.clear();
+        // Don't show disabled actions.
+        for (RemoteAction remoteAction : actions) {
+            if (remoteAction.isEnabled()) {
+                mMediaActions.add(remoteAction);
+            }
+        }
+
+        updateCustomActions(mMediaActions);
+    }
+
+    private void updateCustomActions(@NonNull List<RemoteAction> customActions) {
+        List<RemoteAction> newCustomActions = customActions;
+        if (newCustomActions == mMediaActions && !mAppActions.isEmpty()) {
+            // Don't show the media actions while there are app actions.
+            return;
+        } else if (newCustomActions == mAppActions && mAppActions.isEmpty()) {
+            // If all the app actions were removed, show the media actions.
+            newCustomActions = mMediaActions;
+        }
+
+        ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+                "%s: replaceCustomActions, count: %d", TAG, newCustomActions.size());
+        int oldCustomActionsCount = 0;
+        for (TvPipAction action : mActionsList) {
+            if (action.getActionType() == ACTION_CUSTOM) {
+                oldCustomActionsCount++;
+            }
+        }
+        mActionsList.removeIf(tvPipAction -> tvPipAction.getActionType() == ACTION_CUSTOM);
+
+        List<TvPipAction> actions = new ArrayList<>();
+        for (RemoteAction action : newCustomActions) {
+            actions.add(new TvPipCustomAction(ACTION_CUSTOM, action, mSystemActionsHandler));
+        }
+        mActionsList.addAll(FIRST_CUSTOM_ACTION_INDEX, actions);
+
+        int added = newCustomActions.size() - oldCustomActionsCount;
+        int changed = Math.min(newCustomActions.size(), oldCustomActionsCount);
+        notifyActionsChanged(added, changed, FIRST_CUSTOM_ACTION_INDEX);
+    }
+
+    @VisibleForTesting(visibility = PACKAGE)
+    public void updateExpansionEnabled(boolean enabled) {
+        ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+                "%s: updateExpansionState, enabled: %b", TAG, enabled);
+        int actionIndex = mActionsList.indexOf(mExpandCollapseAction);
+        boolean actionInList = actionIndex != -1;
+        if (enabled && !actionInList) {
+            mActionsList.add(mExpandCollapseAction);
+            actionIndex = mActionsList.size() - 1;
+        } else if (!enabled && actionInList) {
+            mActionsList.remove(actionIndex);
+        } else {
+            return;
+        }
+        notifyActionsChanged(/* added= */ enabled ? 1 : -1, /* updated= */ 0, actionIndex);
+    }
+
+    @VisibleForTesting(visibility = PACKAGE)
+    public void onPipExpansionToggled(boolean expanded) {
+        ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+                "%s: onPipExpansionToggled, expanded: %b", TAG, expanded);
+
+        mExpandCollapseAction.update(
+                expanded ? R.string.pip_collapse : R.string.pip_expand,
+                expanded ? R.drawable.pip_ic_collapse : R.drawable.pip_ic_expand);
+
+        notifyActionsChanged(/* added= */ 0, /* updated= */ 1,
+                mActionsList.indexOf(mExpandCollapseAction));
+    }
+
+    List<TvPipAction> getActionsList() {
+        return mActionsList;
+    }
+
+    @NonNull
+    TvPipAction getCloseAction() {
+        return mActionsList.get(CLOSE_ACTION_INDEX);
+    }
+
+    void addListener(Listener listener) {
+        if (!mListeners.contains(listener)) {
+            mListeners.add(listener);
+        }
+    }
+
+    /**
+     * Returns the index of the first action of the given action type or -1 if none can be found.
+     */
+    int getFirstIndexOfAction(@TvPipAction.ActionType int actionType) {
+        for (int i = 0; i < mActionsList.size(); i++) {
+            if (mActionsList.get(i).getActionType() == actionType) {
+                return i;
+            }
+        }
+        return -1;
+    }
+
+    /**
+     * Allow components to listen to updates to the actions list, including where they happen so
+     * that changes can be animated.
+     */
+    interface Listener {
+        /**
+         * Notifies the listener how many actions were added/removed or updated.
+         *
+         * @param added      can be positive (number of actions added), negative (number of actions
+         *                   removed) or zero (the number of actions stayed the same).
+         * @param updated    the number of actions that might have been updated and need to be
+         *                   refreshed.
+         * @param startIndex The index of the first updated action. The added/removed actions start
+         *                   at (startIndex + updated).
+         */
+        void onActionsChanged(int added, int updated, int startIndex);
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
index 3e8de45..7671081 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
@@ -22,13 +22,16 @@
 import android.annotation.IntDef;
 import android.app.ActivityManager;
 import android.app.ActivityTaskManager;
-import android.app.PendingIntent;
 import android.app.RemoteAction;
 import android.app.TaskInfo;
+import android.content.BroadcastReceiver;
 import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
 import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.graphics.Rect;
+import android.os.Handler;
 import android.os.RemoteException;
 import android.view.Gravity;
 
@@ -67,8 +70,8 @@
  */
 public class TvPipController implements PipTransitionController.PipTransitionCallback,
         TvPipBoundsController.PipBoundsListener, TvPipMenuController.Delegate,
-        TvPipNotificationController.Delegate, DisplayController.OnDisplaysChangedListener,
-        ConfigurationChangeListener, UserChangeListener {
+        DisplayController.OnDisplaysChangedListener, ConfigurationChangeListener,
+        UserChangeListener {
     private static final String TAG = "TvPipController";
 
     private static final int NONEXISTENT_TASK_ID = -1;
@@ -98,6 +101,17 @@
      */
     private static final int STATE_PIP_MENU = 2;
 
+    static final String ACTION_SHOW_PIP_MENU =
+            "com.android.wm.shell.pip.tv.notification.action.SHOW_PIP_MENU";
+    static final String ACTION_CLOSE_PIP =
+            "com.android.wm.shell.pip.tv.notification.action.CLOSE_PIP";
+    static final String ACTION_MOVE_PIP =
+            "com.android.wm.shell.pip.tv.notification.action.MOVE_PIP";
+    static final String ACTION_TOGGLE_EXPANDED_PIP =
+            "com.android.wm.shell.pip.tv.notification.action.TOGGLE_EXPANDED_PIP";
+    static final String ACTION_TO_FULLSCREEN =
+            "com.android.wm.shell.pip.tv.notification.action.FULLSCREEN";
+
     private final Context mContext;
 
     private final ShellController mShellController;
@@ -107,6 +121,7 @@
     private final PipAppOpsListener mAppOpsListener;
     private final PipTaskOrganizer mPipTaskOrganizer;
     private final PipMediaController mPipMediaController;
+    private final TvPipActionsProvider mTvPipActionsProvider;
     private final TvPipNotificationController mPipNotificationController;
     private final TvPipMenuController mTvPipMenuController;
     private final PipTransitionController mPipTransitionController;
@@ -115,14 +130,16 @@
     private final DisplayController mDisplayController;
     private final WindowManagerShellWrapper mWmShellWrapper;
     private final ShellExecutor mMainExecutor;
+    private final Handler mMainHandler; // For registering the broadcast receiver
     private final TvPipImpl mImpl = new TvPipImpl();
 
+    private final ActionBroadcastReceiver mActionBroadcastReceiver;
+
     @State
     private int mState = STATE_NO_PIP;
     private int mPreviousGravity = TvPipBoundsState.DEFAULT_TV_GRAVITY;
     private int mPinnedTaskId = NONEXISTENT_TASK_ID;
 
-    private RemoteAction mCloseAction;
     // How long the shell will wait for the app to close the PiP if a custom action is set.
     private int mPipForceCloseDelay;
 
@@ -146,6 +163,7 @@
             PipParamsChangedForwarder pipParamsChangedForwarder,
             DisplayController displayController,
             WindowManagerShellWrapper wmShell,
+            Handler mainHandler,
             ShellExecutor mainExecutor) {
         return new TvPipController(
                 context,
@@ -164,6 +182,7 @@
                 pipParamsChangedForwarder,
                 displayController,
                 wmShell,
+                mainHandler,
                 mainExecutor).mImpl;
     }
 
@@ -184,8 +203,10 @@
             PipParamsChangedForwarder pipParamsChangedForwarder,
             DisplayController displayController,
             WindowManagerShellWrapper wmShellWrapper,
+            Handler mainHandler,
             ShellExecutor mainExecutor) {
         mContext = context;
+        mMainHandler = mainHandler;
         mMainExecutor = mainExecutor;
         mShellController = shellController;
         mDisplayController = displayController;
@@ -198,12 +219,17 @@
         mTvPipBoundsController.setListener(this);
 
         mPipMediaController = pipMediaController;
+        mTvPipActionsProvider = new TvPipActionsProvider(context, pipMediaController,
+                this::executeAction);
 
         mPipNotificationController = pipNotificationController;
-        mPipNotificationController.setDelegate(this);
+        mPipNotificationController.setTvPipActionsProvider(mTvPipActionsProvider);
 
         mTvPipMenuController = tvPipMenuController;
         mTvPipMenuController.setDelegate(this);
+        mTvPipMenuController.setTvPipActionsProvider(mTvPipActionsProvider);
+
+        mActionBroadcastReceiver = new ActionBroadcastReceiver();
 
         mAppOpsListener = pipAppOpsListener;
         mPipTaskOrganizer = pipTaskOrganizer;
@@ -241,7 +267,7 @@
                 "%s: onConfigurationChanged(), state=%s", TAG, stateToName(mState));
 
         loadConfigurations();
-        mPipNotificationController.onConfigurationChanged(mContext);
+        mPipNotificationController.onConfigurationChanged();
         mTvPipBoundsAlgorithm.onConfigurationChanged(mContext);
     }
 
@@ -256,9 +282,10 @@
      * Starts the process if bringing up the Pip menu if by issuing a command to move Pip
      * task/window to the "Menu" position. We'll show the actual Menu UI (eg. actions) once the Pip
      * task/window is properly positioned in {@link #onPipTransitionFinished(int)}.
+     *
+     * @param moveMenu If true, show the moveMenu, otherwise show the regular menu.
      */
-    @Override
-    public void showPictureInPictureMenu() {
+    private void showPictureInPictureMenu(boolean moveMenu) {
         ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
                 "%s: showPictureInPictureMenu(), state=%s", TAG, stateToName(mState));
 
@@ -269,7 +296,11 @@
         }
 
         setState(STATE_PIP_MENU);
-        mTvPipMenuController.showMenu();
+        if (moveMenu) {
+            mTvPipMenuController.showMovementMenu();
+        } else {
+            mTvPipMenuController.showMenu();
+        }
         updatePinnedStackBounds();
     }
 
@@ -289,8 +320,7 @@
     /**
      * Opens the "Pip-ed" Activity fullscreen.
      */
-    @Override
-    public void movePipToFullscreen() {
+    private void movePipToFullscreen() {
         ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
                 "%s: movePipToFullscreen(), state=%s", TAG, stateToName(mState));
 
@@ -298,8 +328,7 @@
         onPipDisappeared();
     }
 
-    @Override
-    public void togglePipExpansion() {
+    private void togglePipExpansion() {
         ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
                 "%s: togglePipExpansion()", TAG);
         boolean expanding = !mTvPipBoundsState.isTvPipExpanded();
@@ -310,18 +339,11 @@
         }
         mTvPipBoundsState.setTvPipManuallyCollapsed(!expanding);
         mTvPipBoundsState.setTvPipExpanded(expanding);
-        mPipNotificationController.updateExpansionState();
 
         updatePinnedStackBounds();
     }
 
     @Override
-    public void enterPipMovementMenu() {
-        setState(STATE_PIP_MENU);
-        mTvPipMenuController.showMovementMenuOnly();
-    }
-
-    @Override
     public void movePip(int keycode) {
         if (mTvPipBoundsAlgorithm.updateGravity(keycode)) {
             mTvPipMenuController.updateGravity(mTvPipBoundsState.getTvPipGravity());
@@ -373,30 +395,23 @@
     @Override
     public void onPipTargetBoundsChange(Rect targetBounds, int animationDuration) {
         mPipTaskOrganizer.scheduleAnimateResizePip(targetBounds,
-                animationDuration, rect -> mTvPipMenuController.updateExpansionState());
+                animationDuration, null);
         mTvPipMenuController.onPipTransitionToTargetBoundsStarted(targetBounds);
     }
 
     /**
      * Closes Pip window.
      */
-    @Override
     public void closePip() {
-        ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
-                "%s: closePip(), state=%s, loseAction=%s", TAG, stateToName(mState),
-                mCloseAction);
+        closeCurrentPiP(mPinnedTaskId);
+    }
 
-        if (mCloseAction != null) {
-            try {
-                mCloseAction.getActionIntent().send();
-            } catch (PendingIntent.CanceledException e) {
-                ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
-                        "%s: Failed to send close action, %s", TAG, e);
-            }
-            mMainExecutor.executeDelayed(() -> closeCurrentPiP(mPinnedTaskId), mPipForceCloseDelay);
-        } else {
-            closeCurrentPiP(mPinnedTaskId);
-        }
+    /**
+     * Force close the current PiP after some time in case the custom action hasn't done it by
+     * itself.
+     */
+    public void customClosePip() {
+        mMainExecutor.executeDelayed(() -> closeCurrentPiP(mPinnedTaskId), mPipForceCloseDelay);
     }
 
     private void closeCurrentPiP(int pinnedTaskId) {
@@ -426,6 +441,7 @@
         mPinnedTaskId = pinnedTask.taskId;
 
         mPipMediaController.onActivityPinned();
+        mActionBroadcastReceiver.register();
         mPipNotificationController.show(pinnedTask.topActivity.getPackageName());
     }
 
@@ -445,6 +461,8 @@
                 "%s: onPipDisappeared() state=%s", TAG, stateToName(mState));
 
         mPipNotificationController.dismiss();
+        mActionBroadcastReceiver.unregister();
+
         mTvPipMenuController.closeMenu();
         mTvPipBoundsState.resetTvPipState();
         mTvPipBoundsController.onPipDismissed();
@@ -454,6 +472,11 @@
 
     @Override
     public void onPipTransitionStarted(int direction, Rect currentPipBounds) {
+        final boolean enterPipTransition = PipAnimationController.isInPipDirection(direction);
+        if (enterPipTransition && mState == STATE_NO_PIP) {
+            // Set the initial ability to expand the PiP when entering PiP.
+            updateExpansionState();
+        }
         ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
                 "%s: onPipTransition_Started(), state=%s, direction=%d",
                 TAG, stateToName(mState), direction);
@@ -465,6 +488,7 @@
                 "%s: onPipTransition_Canceled(), state=%s", TAG, stateToName(mState));
         mTvPipMenuController.onPipTransitionFinished(
                 PipAnimationController.isInPipDirection(direction));
+        mTvPipActionsProvider.onPipExpansionToggled(mTvPipBoundsState.isTvPipExpanded());
     }
 
     @Override
@@ -477,6 +501,12 @@
                 "%s: onPipTransition_Finished(), state=%s, direction=%d",
                 TAG, stateToName(mState), direction);
         mTvPipMenuController.onPipTransitionFinished(enterPipTransition);
+        mTvPipActionsProvider.onPipExpansionToggled(mTvPipBoundsState.isTvPipExpanded());
+    }
+
+    private void updateExpansionState() {
+        mTvPipActionsProvider.updateExpansionEnabled(mTvPipBoundsState.isTvExpandedPipSupported()
+                && mTvPipBoundsState.getDesiredTvExpandedAspectRatio() != 0);
     }
 
     private void setState(@State int state) {
@@ -534,8 +564,7 @@
                 ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
                         "%s: onActionsChanged()", TAG);
 
-                mTvPipMenuController.setAppActions(actions, closeAction);
-                mCloseAction = closeAction;
+                mTvPipActionsProvider.setAppActions(actions, closeAction);
             }
 
             @Override
@@ -555,7 +584,7 @@
                         "%s: onExpandedAspectRatioChanged: %f", TAG, ratio);
 
                 mTvPipBoundsState.setDesiredTvExpandedAspectRatio(ratio, false);
-                mTvPipMenuController.updateExpansionState();
+                updateExpansionState();
 
                 // 1) PiP is expanded and only aspect ratio changed, but wasn't disabled
                 // --> update bounds, but don't toggle
@@ -662,6 +691,90 @@
         }
     }
 
+    private void executeAction(@TvPipAction.ActionType int actionType) {
+        switch (actionType) {
+            case TvPipAction.ACTION_FULLSCREEN:
+                movePipToFullscreen();
+                break;
+            case TvPipAction.ACTION_CLOSE:
+                closePip();
+                break;
+            case TvPipAction.ACTION_MOVE:
+                showPictureInPictureMenu(/* moveMenu= */ true);
+                break;
+            case TvPipAction.ACTION_CUSTOM_CLOSE:
+                customClosePip();
+                break;
+            case TvPipAction.ACTION_EXPAND_COLLAPSE:
+                togglePipExpansion();
+                break;
+            default:
+                // NOOP
+                break;
+        }
+    }
+
+    private class ActionBroadcastReceiver extends BroadcastReceiver {
+        private static final String SYSTEMUI_PERMISSION = "com.android.systemui.permission.SELF";
+
+        final IntentFilter mIntentFilter;
+
+        {
+            mIntentFilter = new IntentFilter();
+            mIntentFilter.addAction(ACTION_CLOSE_PIP);
+            mIntentFilter.addAction(ACTION_SHOW_PIP_MENU);
+            mIntentFilter.addAction(ACTION_MOVE_PIP);
+            mIntentFilter.addAction(ACTION_TOGGLE_EXPANDED_PIP);
+            mIntentFilter.addAction(ACTION_TO_FULLSCREEN);
+        }
+
+        boolean mRegistered = false;
+
+        void register() {
+            if (mRegistered) return;
+
+            mContext.registerReceiverForAllUsers(this, mIntentFilter, SYSTEMUI_PERMISSION,
+                    mMainHandler);
+            mRegistered = true;
+        }
+
+        void unregister() {
+            if (!mRegistered) return;
+
+            mContext.unregisterReceiver(this);
+            mRegistered = false;
+        }
+
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            final String action = intent.getAction();
+            ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+                    "%s: on(Broadcast)Receive(), action=%s", TAG, action);
+
+            if (ACTION_SHOW_PIP_MENU.equals(action)) {
+                showPictureInPictureMenu(/* moveMenu= */ false);
+            } else {
+                executeAction(getCorrespondingActionType(action));
+            }
+        }
+
+        @TvPipAction.ActionType
+        private int getCorrespondingActionType(String broadcast) {
+            if (ACTION_CLOSE_PIP.equals(broadcast)) {
+                return TvPipAction.ACTION_CLOSE;
+            } else if (ACTION_MOVE_PIP.equals(broadcast)) {
+                return TvPipAction.ACTION_MOVE;
+            } else if (ACTION_TOGGLE_EXPANDED_PIP.equals(broadcast)) {
+                return TvPipAction.ACTION_EXPAND_COLLAPSE;
+            } else if (ACTION_TO_FULLSCREEN.equals(broadcast)) {
+                return TvPipAction.ACTION_FULLSCREEN;
+            }
+
+            // Default: handle it like an action we don't know the content of.
+            return TvPipAction.ACTION_CUSTOM;
+        }
+    }
+
     private class TvPipImpl implements Pip {
         // Not used
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipCustomAction.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipCustomAction.java
new file mode 100644
index 0000000..449a2bf
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipCustomAction.java
@@ -0,0 +1,96 @@
+/*
+ * 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.wm.shell.pip.tv;
+
+import static android.app.Notification.Action.SEMANTIC_ACTION_DELETE;
+import static android.app.Notification.Action.SEMANTIC_ACTION_NONE;
+
+import android.annotation.NonNull;
+import android.app.Notification;
+import android.app.PendingIntent;
+import android.app.RemoteAction;
+import android.content.Context;
+import android.os.Bundle;
+import android.os.Handler;
+
+import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.common.TvWindowMenuActionButton;
+import com.android.wm.shell.protolog.ShellProtoLogGroup;
+
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * A TvPipAction for actions that the app provides via {@link
+ * android.app.PictureInPictureParams.Builder#setCloseAction(RemoteAction)} or {@link
+ * android.app.PictureInPictureParams.Builder#setActions(List)}.
+ */
+public class TvPipCustomAction extends TvPipAction {
+    private static final String TAG = TvPipCustomAction.class.getSimpleName();
+
+    private final RemoteAction mRemoteAction;
+
+    TvPipCustomAction(@ActionType int actionType, @NonNull RemoteAction remoteAction,
+            SystemActionsHandler systemActionsHandler) {
+        super(actionType, systemActionsHandler);
+        Objects.requireNonNull(remoteAction);
+        mRemoteAction = remoteAction;
+    }
+
+    void populateButton(@NonNull TvWindowMenuActionButton button, Handler mainHandler) {
+        if (button == null || mainHandler == null) return;
+        if (mRemoteAction.getContentDescription().length() > 0) {
+            button.setTextAndDescription(mRemoteAction.getContentDescription());
+        } else {
+            button.setTextAndDescription(mRemoteAction.getTitle());
+        }
+        button.setImageIconAsync(mRemoteAction.getIcon(), mainHandler);
+        button.setEnabled(isCloseAction() || mRemoteAction.isEnabled());
+    }
+
+    PendingIntent getPendingIntent() {
+        return mRemoteAction.getActionIntent();
+    }
+
+    void executeAction() {
+        super.executeAction();
+        try {
+            mRemoteAction.getActionIntent().send();
+        } catch (PendingIntent.CanceledException e) {
+            ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+                    "%s: Failed to send action, %s", TAG, e);
+        }
+    }
+
+    @Override
+    Notification.Action toNotificationAction(Context context) {
+        Notification.Action.Builder builder = new Notification.Action.Builder(
+                mRemoteAction.getIcon(),
+                mRemoteAction.getTitle(),
+                mRemoteAction.getActionIntent());
+        Bundle extras = new Bundle();
+        extras.putCharSequence(Notification.EXTRA_PICTURE_CONTENT_DESCRIPTION,
+                mRemoteAction.getContentDescription());
+        builder.addExtras(extras);
+
+        builder.setSemanticAction(isCloseAction()
+                ? SEMANTIC_ACTION_DELETE : SEMANTIC_ACTION_NONE);
+        builder.setContextual(true);
+        return builder.build();
+    }
+
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java
index ab7edbf..00e4f47 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java
@@ -41,13 +41,10 @@
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.wm.shell.R;
 import com.android.wm.shell.common.SystemWindows;
-import com.android.wm.shell.pip.PipMediaController;
 import com.android.wm.shell.pip.PipMenuController;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
 
-import java.util.ArrayList;
 import java.util.List;
-import java.util.Objects;
 
 /**
  * Manages the visibility of the PiP Menu as user interacts with PiP.
@@ -60,22 +57,20 @@
     private final SystemWindows mSystemWindows;
     private final TvPipBoundsState mTvPipBoundsState;
     private final Handler mMainHandler;
+    private TvPipActionsProvider mTvPipActionsProvider;
 
     private Delegate mDelegate;
     private SurfaceControl mLeash;
     private TvPipMenuView mPipMenuView;
     private View mPipBackgroundView;
 
+    private boolean mMenuIsOpen;
     // User can actively move the PiP via the DPAD.
     private boolean mInMoveMode;
     // Used when only showing the move menu since we want to close the menu completely when
     // exiting the move menu instead of showing the regular button menu.
     private boolean mCloseAfterExitMoveMenu;
 
-    private final List<RemoteAction> mMediaActions = new ArrayList<>();
-    private final List<RemoteAction> mAppActions = new ArrayList<>();
-    private RemoteAction mCloseAction;
-
     private SyncRtSurfaceTransactionApplier mApplier;
     private SyncRtSurfaceTransactionApplier mBackgroundApplier;
     RectF mTmpSourceRectF = new RectF();
@@ -83,8 +78,7 @@
     Matrix mMoveTransform = new Matrix();
 
     public TvPipMenuController(Context context, TvPipBoundsState tvPipBoundsState,
-            SystemWindows systemWindows, PipMediaController pipMediaController,
-            Handler mainHandler) {
+            SystemWindows systemWindows, Handler mainHandler) {
         mContext = context;
         mTvPipBoundsState = tvPipBoundsState;
         mSystemWindows = systemWindows;
@@ -101,9 +95,6 @@
         context.registerReceiverForAllUsers(closeSystemDialogsBroadcastReceiver,
                 new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS), null /* permission */,
                 mainHandler, Context.RECEIVER_EXPORTED);
-
-        pipMediaController.addActionListener(this::onMediaActionsChanged);
-
     }
 
     void setDelegate(Delegate delegate) {
@@ -120,6 +111,10 @@
         mDelegate = delegate;
     }
 
+    void setTvPipActionsProvider(TvPipActionsProvider tvPipActionsProvider) {
+        mTvPipActionsProvider = tvPipActionsProvider;
+    }
+
     @Override
     public void attach(SurfaceControl leash) {
         if (mDelegate == null) {
@@ -146,15 +141,19 @@
         int pipMenuBorderWidth = mContext.getResources()
                 .getDimensionPixelSize(R.dimen.pip_menu_border_width);
         mTvPipBoundsState.setPipMenuPermanentDecorInsets(Insets.of(-pipMenuBorderWidth,
-                    -pipMenuBorderWidth, -pipMenuBorderWidth, -pipMenuBorderWidth));
+                -pipMenuBorderWidth, -pipMenuBorderWidth, -pipMenuBorderWidth));
         mTvPipBoundsState.setPipMenuTemporaryDecorInsets(Insets.of(0, 0, 0, -pipEduTextHeight));
     }
 
     private void attachPipMenuView() {
-        mPipMenuView = new TvPipMenuView(mContext, mMainHandler, this);
+        if (mTvPipActionsProvider == null) {
+            ProtoLog.e(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+                    "%s: Actions provider is not set", TAG);
+            return;
+        }
+        mPipMenuView = new TvPipMenuView(mContext, mMainHandler, this, mTvPipActionsProvider);
         setUpViewSurfaceZOrder(mPipMenuView, 1);
         addPipMenuViewToSystemWindows(mPipMenuView, MENU_WINDOW_TITLE);
-        maybeUpdateMenuViewActions();
     }
 
     private void attachPipBackgroundView() {
@@ -189,17 +188,22 @@
         // and the menu view has been fully remeasured and relaid out, we add a small delay here by
         // posting on the handler.
         mMainHandler.post(() -> {
-            mPipMenuView.onPipTransitionFinished(
-                    enterTransition, mTvPipBoundsState.isTvPipExpanded());
+            if (mPipMenuView != null) {
+                mPipMenuView.onPipTransitionFinished(enterTransition);
+            }
         });
     }
 
-    void showMovementMenuOnly() {
+    void showMovementMenu() {
         ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
                 "%s: showMovementMenuOnly()", TAG);
         setInMoveMode(true);
-        mCloseAfterExitMoveMenu = true;
-        showMenuInternal();
+        if (mMenuIsOpen) {
+            mPipMenuView.showMoveMenu(mDelegate.getPipGravity());
+        } else {
+            mCloseAfterExitMoveMenu = true;
+            showMenuInternal();
+        }
     }
 
     @Override
@@ -214,14 +218,13 @@
         if (mPipMenuView == null) {
             return;
         }
-        maybeUpdateMenuViewActions();
-        updateExpansionState();
 
+        mMenuIsOpen = true;
         grantPipMenuFocus(true);
         if (mInMoveMode) {
             mPipMenuView.showMoveMenu(mDelegate.getPipGravity());
         } else {
-            mPipMenuView.showButtonsMenu();
+            mPipMenuView.showButtonsMenu(/* exitingMoveMode= */ false);
         }
         mPipMenuView.updateBounds(mTvPipBoundsState.getBounds());
     }
@@ -236,11 +239,6 @@
         mPipMenuView.showMovementHints(gravity);
     }
 
-    void updateExpansionState() {
-        mPipMenuView.setExpandedModeEnabled(mTvPipBoundsState.isTvExpandedPipSupported()
-                && mTvPipBoundsState.getDesiredTvExpandedAspectRatio() != 0);
-    }
-
     private Rect calculateMenuSurfaceBounds(Rect pipBounds) {
         return mPipMenuView.getPipMenuContainerBounds(pipBounds);
     }
@@ -252,6 +250,8 @@
         if (mPipMenuView == null) {
             return;
         }
+
+        mMenuIsOpen = false;
         mPipMenuView.hideAllUserControls();
         grantPipMenuFocus(false);
         mDelegate.onMenuClosed();
@@ -272,29 +272,19 @@
     }
 
     @Override
-    public void onEnterMoveMode() {
-        ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
-                "%s: onEnterMoveMode - %b, close when exiting move menu: %b", TAG, mInMoveMode,
-                mCloseAfterExitMoveMenu);
-        setInMoveMode(true);
-        mPipMenuView.showMoveMenu(mDelegate.getPipGravity());
-    }
-
-    @Override
     public boolean onExitMoveMode() {
         ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
-                "%s: onExitMoveMode - %b, close when exiting move menu: %b", TAG, mInMoveMode,
-                mCloseAfterExitMoveMenu);
+                "%s: onExitMoveMode - %b, close when exiting move menu: %b",
+                TAG, mInMoveMode, mCloseAfterExitMoveMenu);
 
-        if (mCloseAfterExitMoveMenu) {
-            setInMoveMode(false);
-            mCloseAfterExitMoveMenu = false;
-            closeMenu();
-            return true;
-        }
         if (mInMoveMode) {
             setInMoveMode(false);
-            mPipMenuView.showButtonsMenu();
+            if (mCloseAfterExitMoveMenu) {
+                mCloseAfterExitMoveMenu = false;
+                closeMenu();
+            } else {
+                mPipMenuView.showButtonsMenu(/* exitingMoveMode= */ true);
+            }
             return true;
         }
         return false;
@@ -319,51 +309,7 @@
 
     @Override
     public void setAppActions(List<RemoteAction> actions, RemoteAction closeAction) {
-        ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
-                "%s: setAppActions()", TAG);
-        updateAdditionalActionsList(mAppActions, actions, closeAction);
-    }
-
-    private void onMediaActionsChanged(List<RemoteAction> actions) {
-        ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
-                "%s: onMediaActionsChanged()", TAG);
-
-        // Hide disabled actions.
-        List<RemoteAction> enabledActions = new ArrayList<>();
-        for (RemoteAction remoteAction : actions) {
-            if (remoteAction.isEnabled()) {
-                enabledActions.add(remoteAction);
-            }
-        }
-        updateAdditionalActionsList(mMediaActions, enabledActions, mCloseAction);
-    }
-
-    private void updateAdditionalActionsList(List<RemoteAction> destination,
-            @Nullable List<RemoteAction> source, RemoteAction closeAction) {
-        final int number = source != null ? source.size() : 0;
-        if (number == 0 && destination.isEmpty() && Objects.equals(closeAction, mCloseAction)) {
-            // Nothing changed.
-            return;
-        }
-
-        mCloseAction = closeAction;
-
-        destination.clear();
-        if (number > 0) {
-            destination.addAll(source);
-        }
-        maybeUpdateMenuViewActions();
-    }
-
-    private void maybeUpdateMenuViewActions() {
-        if (mPipMenuView == null) {
-            return;
-        }
-        if (!mAppActions.isEmpty()) {
-            mPipMenuView.setAdditionalActions(mAppActions, mCloseAction, mMainHandler);
-        } else {
-            mPipMenuView.setAdditionalActions(mMediaActions, mCloseAction, mMainHandler);
-        }
+        // NOOP - handled via the TvPipActionsProvider
     }
 
     @Override
@@ -544,42 +490,21 @@
     }
 
     @Override
-    public void onCloseButtonClick() {
-        mDelegate.closePip();
-    }
-
-    @Override
-    public void onFullscreenButtonClick() {
-        mDelegate.movePipToFullscreen();
-    }
-
-    @Override
-    public void onToggleExpandedMode() {
-        mDelegate.togglePipExpansion();
-    }
-
-    @Override
     public void onCloseEduText() {
         mTvPipBoundsState.setPipMenuTemporaryDecorInsets(Insets.NONE);
         mDelegate.closeEduText();
     }
 
     interface Delegate {
-        void movePipToFullscreen();
-
         void movePip(int keycode);
 
         void onInMoveModeChanged();
 
         int getPipGravity();
 
-        void togglePipExpansion();
-
         void onMenuClosed();
 
         void closeEduText();
-
-        void closePip();
     }
 
     private void grantPipMenuFocus(boolean grantFocus) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java
index 57e95c4..56c602a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java
@@ -25,119 +25,102 @@
 import static android.view.KeyEvent.KEYCODE_DPAD_UP;
 import static android.view.KeyEvent.KEYCODE_ENTER;
 
-import android.app.PendingIntent;
-import android.app.RemoteAction;
+import static com.android.wm.shell.pip.tv.TvPipAction.ACTION_MOVE;
+
 import android.content.Context;
 import android.graphics.Rect;
 import android.os.Handler;
 import android.view.Gravity;
 import android.view.KeyEvent;
-import android.view.SurfaceControl;
 import android.view.View;
 import android.view.ViewGroup;
-import android.view.ViewRootImpl;
 import android.view.accessibility.AccessibilityManager;
 import android.widget.FrameLayout;
-import android.widget.HorizontalScrollView;
 import android.widget.ImageView;
-import android.widget.LinearLayout;
-import android.widget.ScrollView;
 
 import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
 
 import com.android.internal.protolog.common.ProtoLog;
+import com.android.internal.widget.LinearLayoutManager;
+import com.android.internal.widget.RecyclerView;
 import com.android.wm.shell.R;
 import com.android.wm.shell.common.TvWindowMenuActionButton;
 import com.android.wm.shell.pip.PipUtils;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
 
-import java.util.ArrayList;
 import java.util.List;
 
 /**
- * A View that represents Pip Menu on TV. It's responsible for displaying 3 ever-present Pip Menu
- * actions: Fullscreen, Move and Close, but could also display "additional" actions, that may be set
- * via a {@link #setAdditionalActions(List, RemoteAction, Handler)} call.
+ * A View that represents Pip Menu on TV. It's responsible for displaying the Pip menu actions from
+ * the TvPipActionsProvider as well as the buttons for manually moving the PiP.
  */
-public class TvPipMenuView extends FrameLayout implements View.OnClickListener {
+public class TvPipMenuView extends FrameLayout implements TvPipActionsProvider.Listener {
     private static final String TAG = "TvPipMenuView";
 
-    private static final int FIRST_CUSTOM_ACTION_POSITION = 3;
+    private final TvPipMenuView.Listener mListener;
 
-    private final Listener mListener;
+    private final TvPipActionsProvider mTvPipActionsProvider;
 
-    private final LinearLayout mActionButtonsContainer;
-    private final View mMenuFrameView;
-    private final List<TvWindowMenuActionButton> mAdditionalButtons = new ArrayList<>();
+    private final RecyclerView mActionButtonsRecyclerView;
+    private final LinearLayoutManager mButtonLayoutManager;
+    private final RecyclerViewAdapter mRecyclerViewAdapter;
+
     private final View mPipFrameView;
+    private final View mMenuFrameView;
     private final View mPipView;
+
+    private final View mPipBackground;
+    private final View mDimLayer;
+
     private final TvPipMenuEduTextDrawer mEduTextDrawer;
+
     private final int mPipMenuOuterSpace;
     private final int mPipMenuBorderWidth;
 
+    private final int mPipMenuFadeAnimationDuration;
+    private final int mResizeAnimationDuration;
+
     private final ImageView mArrowUp;
     private final ImageView mArrowRight;
     private final ImageView mArrowDown;
     private final ImageView mArrowLeft;
     private final TvWindowMenuActionButton mA11yDoneButton;
 
-    private final View mPipBackground;
-    private final View mDimLayer;
-
-    private final ScrollView mScrollView;
-    private final HorizontalScrollView mHorizontalScrollView;
-    private View mFocusedButton;
-
     private Rect mCurrentPipBounds;
     private boolean mMoveMenuIsVisible;
     private boolean mButtonMenuIsVisible;
-
-    private final TvWindowMenuActionButton mExpandButton;
-    private final TvWindowMenuActionButton mCloseButton;
-
     private boolean mSwitchingOrientation;
 
-    private final int mPipMenuFadeAnimationDuration;
-    private final int mResizeAnimationDuration;
-
     private final AccessibilityManager mA11yManager;
     private final Handler mMainHandler;
 
     public TvPipMenuView(@NonNull Context context, @NonNull Handler mainHandler,
-            @NonNull Listener listener) {
+            @NonNull Listener listener, TvPipActionsProvider tvPipActionsProvider) {
         super(context, null, 0, 0);
-
         inflate(context, R.layout.tv_pip_menu, this);
 
         mMainHandler = mainHandler;
         mListener = listener;
-
         mA11yManager = context.getSystemService(AccessibilityManager.class);
 
-        mActionButtonsContainer = findViewById(R.id.tv_pip_menu_action_buttons);
-        mActionButtonsContainer.findViewById(R.id.tv_pip_menu_fullscreen_button)
-                .setOnClickListener(this);
+        mActionButtonsRecyclerView = findViewById(R.id.tv_pip_menu_action_buttons);
+        mButtonLayoutManager = new LinearLayoutManager(mContext);
+        mActionButtonsRecyclerView.setLayoutManager(mButtonLayoutManager);
+        mActionButtonsRecyclerView.setPreserveFocusAfterLayout(true);
 
-        mCloseButton = mActionButtonsContainer.findViewById(R.id.tv_pip_menu_close_button);
-        mCloseButton.setOnClickListener(this);
-        mCloseButton.setIsCustomCloseAction(true);
+        mTvPipActionsProvider = tvPipActionsProvider;
+        mRecyclerViewAdapter = new RecyclerViewAdapter(tvPipActionsProvider.getActionsList());
+        mActionButtonsRecyclerView.setAdapter(mRecyclerViewAdapter);
 
-        mActionButtonsContainer.findViewById(R.id.tv_pip_menu_move_button)
-                .setOnClickListener(this);
-        mExpandButton = findViewById(R.id.tv_pip_menu_expand_button);
-        mExpandButton.setOnClickListener(this);
-
-        mPipBackground = findViewById(R.id.tv_pip_menu_background);
-        mDimLayer = findViewById(R.id.tv_pip_menu_dim_layer);
-
-        mScrollView = findViewById(R.id.tv_pip_menu_scroll);
-        mHorizontalScrollView = findViewById(R.id.tv_pip_menu_horizontal_scroll);
+        tvPipActionsProvider.addListener(this);
 
         mMenuFrameView = findViewById(R.id.tv_pip_menu_frame);
         mPipFrameView = findViewById(R.id.tv_pip_border);
         mPipView = findViewById(R.id.tv_pip);
 
+        mPipBackground = findViewById(R.id.tv_pip_menu_background);
+        mDimLayer = findViewById(R.id.tv_pip_menu_dim_layer);
+
         mArrowUp = findViewById(R.id.tv_pip_menu_arrow_up);
         mArrowRight = findViewById(R.id.tv_pip_menu_arrow_right);
         mArrowDown = findViewById(R.id.tv_pip_menu_arrow_down);
@@ -160,8 +143,12 @@
     }
 
     void onPipTransitionToTargetBoundsStarted(Rect targetBounds) {
+        if (targetBounds == null) {
+            return;
+        }
+
         // Fade out content by fading in view on top.
-        if (mCurrentPipBounds != null && targetBounds != null) {
+        if (mCurrentPipBounds != null) {
             boolean ratioChanged = PipUtils.aspectRatioChanged(
                     mCurrentPipBounds.width() / (float) mCurrentPipBounds.height(),
                     targetBounds.width() / (float) targetBounds.height());
@@ -177,7 +164,7 @@
         // Update buttons.
         final boolean vertical = targetBounds.height() > targetBounds.width();
         final boolean orientationChanged =
-                vertical != (mActionButtonsContainer.getOrientation() == LinearLayout.VERTICAL);
+                vertical != (mButtonLayoutManager.getOrientation() == LinearLayoutManager.VERTICAL);
         ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
                 "%s: onPipTransitionToTargetBoundsStarted(), orientation changed %b",
                 TAG, orientationChanged);
@@ -187,27 +174,27 @@
 
         if (mButtonMenuIsVisible) {
             mSwitchingOrientation = true;
-            mActionButtonsContainer.animate()
+            mActionButtonsRecyclerView.animate()
                     .alpha(0)
                     .setInterpolator(TvPipInterpolators.EXIT)
                     .setDuration(mResizeAnimationDuration / 2)
                     .withEndAction(() -> {
-                        changeButtonScrollOrientation(targetBounds);
-                        updateButtonGravity(targetBounds);
+                        mButtonLayoutManager.setOrientation(vertical
+                                ? LinearLayoutManager.VERTICAL : LinearLayoutManager.HORIZONTAL);
                         // Only make buttons visible again in onPipTransitionFinished to keep in
                         // sync with PiP content alpha animation.
                     });
         } else {
-            changeButtonScrollOrientation(targetBounds);
-            updateButtonGravity(targetBounds);
+            mButtonLayoutManager.setOrientation(vertical
+                    ? LinearLayoutManager.VERTICAL : LinearLayoutManager.HORIZONTAL);
         }
     }
 
-    void onPipTransitionFinished(boolean enterTransition, boolean isTvPipExpanded) {
+    void onPipTransitionFinished(boolean enterTransition) {
         ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
                 "%s: onPipTransitionFinished()", TAG);
 
-        // Fade in content by fading out view on top.
+        // Fade in content by fading out view on top (faded out at every aspect ratio change).
         mPipBackground.animate()
                 .alpha(0f)
                 .setDuration(mResizeAnimationDuration / 2)
@@ -218,16 +205,11 @@
             mEduTextDrawer.init();
         }
 
-        setIsExpanded(isTvPipExpanded);
-
-        // Update buttons.
         if (mSwitchingOrientation) {
-            mActionButtonsContainer.animate()
+            mActionButtonsRecyclerView.animate()
                     .alpha(1)
                     .setInterpolator(TvPipInterpolators.ENTER)
                     .setDuration(mResizeAnimationDuration / 2);
-        } else {
-            refocusPreviousButton();
         }
         mSwitchingOrientation = false;
     }
@@ -240,107 +222,9 @@
                 "%s: updateLayout, width: %s, height: %s", TAG, updatedBounds.width(),
                 updatedBounds.height());
         mCurrentPipBounds = updatedBounds;
-        if (!mSwitchingOrientation) {
-            updateButtonGravity(mCurrentPipBounds);
-        }
-
         updatePipFrameBounds();
     }
 
-    private void changeButtonScrollOrientation(Rect bounds) {
-        final boolean vertical = bounds.height() > bounds.width();
-
-        final ViewGroup oldScrollView = vertical ? mHorizontalScrollView : mScrollView;
-        final ViewGroup newScrollView = vertical ? mScrollView : mHorizontalScrollView;
-
-        if (oldScrollView.getChildCount() == 1) {
-            ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
-                    "%s: orientation changed", TAG);
-            oldScrollView.removeView(mActionButtonsContainer);
-            oldScrollView.setVisibility(GONE);
-            mActionButtonsContainer.setOrientation(vertical ? LinearLayout.VERTICAL
-                    : LinearLayout.HORIZONTAL);
-            newScrollView.addView(mActionButtonsContainer);
-            newScrollView.setVisibility(VISIBLE);
-            if (mFocusedButton != null) {
-                mFocusedButton.requestFocus();
-            }
-        }
-    }
-
-    /**
-     * Change button gravity based on new dimensions
-     */
-    private void updateButtonGravity(Rect bounds) {
-        final boolean vertical = bounds.height() > bounds.width();
-        // Use Math.max since the possible orientation change might not have been applied yet.
-        final int buttonsSize = Math.max(mActionButtonsContainer.getHeight(),
-                mActionButtonsContainer.getWidth());
-
-        ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
-                "%s: buttons container width: %s, height: %s", TAG,
-                mActionButtonsContainer.getWidth(), mActionButtonsContainer.getHeight());
-
-        final boolean buttonsFit =
-                vertical ? buttonsSize < bounds.height()
-                        : buttonsSize < bounds.width();
-        final int buttonGravity = buttonsFit ? Gravity.CENTER
-                : (vertical ? Gravity.CENTER_HORIZONTAL : Gravity.CENTER_VERTICAL);
-
-        final LayoutParams params = (LayoutParams) mActionButtonsContainer.getLayoutParams();
-        params.gravity = buttonGravity;
-        mActionButtonsContainer.setLayoutParams(params);
-
-        ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
-                "%s: vertical: %b, buttonsFit: %b, gravity: %s", TAG, vertical, buttonsFit,
-                Gravity.toString(buttonGravity));
-    }
-
-    private void refocusPreviousButton() {
-        if (mMoveMenuIsVisible || mCurrentPipBounds == null || mFocusedButton == null) {
-            return;
-        }
-        final boolean vertical = mCurrentPipBounds.height() > mCurrentPipBounds.width();
-
-        if (!mFocusedButton.hasFocus()) {
-            ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
-                    "%s: request focus from: %s", TAG, mFocusedButton);
-            mFocusedButton.requestFocus();
-        } else {
-            ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
-                    "%s: already focused: %s", TAG, mFocusedButton);
-        }
-
-        // Do we need to scroll?
-        final Rect buttonBounds = new Rect();
-        final Rect scrollBounds = new Rect();
-        if (vertical) {
-            mScrollView.getDrawingRect(scrollBounds);
-        } else {
-            mHorizontalScrollView.getDrawingRect(scrollBounds);
-        }
-        mFocusedButton.getHitRect(buttonBounds);
-
-        if (scrollBounds.contains(buttonBounds)) {
-            // Button is already completely visible, don't scroll
-            ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
-                    "%s: not scrolling", TAG);
-            return;
-        }
-
-        // Scrolling so the button is visible to the user.
-        ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
-                "%s: scrolling to focused button", TAG);
-
-        if (vertical) {
-            mScrollView.smoothScrollTo((int) mFocusedButton.getX(),
-                    (int) mFocusedButton.getY());
-        } else {
-            mHorizontalScrollView.smoothScrollTo((int) mFocusedButton.getX(),
-                    (int) mFocusedButton.getY());
-        }
-    }
-
     Rect getPipMenuContainerBounds(Rect pipBounds) {
         final Rect menuUiBounds = new Rect(pipBounds);
         menuUiBounds.inset(-mPipMenuOuterSpace, -mPipMenuOuterSpace);
@@ -370,20 +254,14 @@
             mPipView.setLayoutParams(pipViewParams);
         }
 
-
-    }
-
-    void setExpandedModeEnabled(boolean enabled) {
-        mExpandButton.setVisibility(enabled ? VISIBLE : GONE);
-    }
-
-    void setIsExpanded(boolean expanded) {
-        ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
-                "%s: setIsExpanded, expanded: %b", TAG, expanded);
-        mExpandButton.setImageResource(
-                expanded ? R.drawable.pip_ic_collapse : R.drawable.pip_ic_expand);
-        mExpandButton.setTextAndDescription(
-                expanded ? R.string.pip_collapse : R.string.pip_expand);
+        // Keep focused button within the visible area while the PiP is changing size. Otherwise,
+        // the button would lose focus which would cause a need for scrolling and re-focusing after
+        // the animation finishes, which does not look good.
+        View focusedChild = mActionButtonsRecyclerView.getFocusedChild();
+        if (focusedChild != null) {
+            mActionButtonsRecyclerView.scrollToPosition(
+                    mActionButtonsRecyclerView.getChildLayoutPosition(focusedChild));
+        }
     }
 
     /**
@@ -391,48 +269,63 @@
      */
     void showMoveMenu(int gravity) {
         ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: showMoveMenu()", TAG);
-        showButtonsMenu(false);
         showMovementHints(gravity);
+        setMenuButtonsVisible(false);
         setFrameHighlighted(true);
 
-        mHorizontalScrollView.setFocusable(false);
-        mScrollView.setFocusable(false);
+        animateAlphaTo(mA11yManager.isEnabled() ? 1f : 0f, mDimLayer);
 
         mEduTextDrawer.closeIfNeeded();
     }
 
-    void showButtonsMenu() {
+
+    void showButtonsMenu(boolean exitingMoveMode) {
         ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
-                "%s: showButtonsMenu()", TAG);
-        showButtonsMenu(true);
+                "%s: showButtonsMenu(), exitingMoveMode %b", TAG, exitingMoveMode);
+        setMenuButtonsVisible(true);
         hideMovementHints();
         setFrameHighlighted(true);
+        animateAlphaTo(1f, mDimLayer);
+        mEduTextDrawer.closeIfNeeded();
 
-        mHorizontalScrollView.setFocusable(true);
-        mScrollView.setFocusable(true);
-
-        // Always focus on the first button when opening the menu, except directly after moving.
-        if (mFocusedButton == null) {
-            // Focus on first button (there is a Space at position 0)
-            mFocusedButton = mActionButtonsContainer.getChildAt(1);
-            // Reset scroll position.
-            mScrollView.scrollTo(0, 0);
-            mHorizontalScrollView.scrollTo(
-                    isLayoutRtl() ? mActionButtonsContainer.getWidth() : 0, 0);
+        if (exitingMoveMode) {
+            scrollAndRefocusButton(mTvPipActionsProvider.getFirstIndexOfAction(ACTION_MOVE),
+                    /* alwaysScroll= */ false);
+        } else {
+            scrollAndRefocusButton(0, /* alwaysScroll= */ true);
         }
-        refocusPreviousButton();
+    }
+
+    private void scrollAndRefocusButton(int position, boolean alwaysScroll) {
+        ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+                "%s: scrollAndRefocusButton, target: %d", TAG, position);
+
+        if (alwaysScroll || !refocusButton(position)) {
+            mButtonLayoutManager.scrollToPositionWithOffset(position, 0);
+            mActionButtonsRecyclerView.post(() -> refocusButton(position));
+        }
     }
 
     /**
-     * Hides all menu views, including the menu frame.
+     * @return true if focus was requested, false if focus request could not be carried out due to
+     * the view for the position not being available (scrolling beforehand will be necessary).
      */
+    private boolean refocusButton(int position) {
+        View itemToFocus = mButtonLayoutManager.findViewByPosition(position);
+        if (itemToFocus != null) {
+            itemToFocus.requestFocus();
+            itemToFocus.requestAccessibilityFocus();
+        }
+        return itemToFocus != null;
+    }
+
     void hideAllUserControls() {
         ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
                 "%s: hideAllUserControls()", TAG);
-        mFocusedButton = null;
-        showButtonsMenu(false);
+        setMenuButtonsVisible(false);
         hideMovementHints();
         setFrameHighlighted(false);
+        animateAlphaTo(0f, mDimLayer);
     }
 
     @Override
@@ -463,134 +356,19 @@
                 });
     }
 
-    /**
-     * Button order:
-     * - Fullscreen
-     * - Close
-     * - Custom actions (app or media actions)
-     * - System actions
-     */
-    void setAdditionalActions(List<RemoteAction> actions, RemoteAction closeAction,
-            Handler mainHandler) {
-        ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
-                "%s: setAdditionalActions()", TAG);
-
-        // Replace system close action with custom close action if available
-        if (closeAction != null) {
-            setActionForButton(closeAction, mCloseButton, mainHandler);
-        } else {
-            mCloseButton.setTextAndDescription(R.string.pip_close);
-            mCloseButton.setImageResource(R.drawable.pip_ic_close_white);
-        }
-        mCloseButton.setIsCustomCloseAction(closeAction != null);
-        // Make sure the close action is always enabled
-        mCloseButton.setEnabled(true);
-
-        // Make sure we exactly as many additional buttons as we have actions to display.
-        final int actionsNumber = actions.size();
-        int buttonsNumber = mAdditionalButtons.size();
-        if (actionsNumber > buttonsNumber) {
-            // Add buttons until we have enough to display all the actions.
-            while (actionsNumber > buttonsNumber) {
-                TvWindowMenuActionButton button = new TvWindowMenuActionButton(mContext);
-                button.setOnClickListener(this);
-
-                mActionButtonsContainer.addView(button,
-                        FIRST_CUSTOM_ACTION_POSITION + buttonsNumber);
-                mAdditionalButtons.add(button);
-
-                buttonsNumber++;
-            }
-        } else if (actionsNumber < buttonsNumber) {
-            // Hide buttons until we as many as the actions.
-            while (actionsNumber < buttonsNumber) {
-                final View button = mAdditionalButtons.get(buttonsNumber - 1);
-                button.setVisibility(View.GONE);
-                button.setTag(null);
-
-                buttonsNumber--;
-            }
-        }
-
-        // "Assign" actions to the buttons.
-        for (int index = 0; index < actionsNumber; index++) {
-            final RemoteAction action = actions.get(index);
-            final TvWindowMenuActionButton button = mAdditionalButtons.get(index);
-
-            // Remove action if it matches the custom close action.
-            if (PipUtils.remoteActionsMatch(action, closeAction)) {
-                button.setVisibility(GONE);
-                continue;
-            }
-            setActionForButton(action, button, mainHandler);
-        }
-
-        if (mCurrentPipBounds != null) {
-            updateButtonGravity(mCurrentPipBounds);
-            refocusPreviousButton();
-        }
-    }
-
-    private void setActionForButton(RemoteAction action, TvWindowMenuActionButton button,
-            Handler mainHandler) {
-        button.setVisibility(View.VISIBLE); // Ensure the button is visible.
-        if (action.getContentDescription().length() > 0) {
-            button.setTextAndDescription(action.getContentDescription());
-        } else {
-            button.setTextAndDescription(action.getTitle());
-        }
-        button.setEnabled(action.isEnabled());
-        button.setTag(action);
-        action.getIcon().loadDrawableAsync(mContext, button::setImageDrawable, mainHandler);
-    }
-
-    @Nullable
-    SurfaceControl getWindowSurfaceControl() {
-        final ViewRootImpl root = getViewRootImpl();
-        if (root == null) {
-            return null;
-        }
-        final SurfaceControl out = root.getSurfaceControl();
-        if (out != null && out.isValid()) {
-            return out;
-        }
-        return null;
-    }
-
     @Override
-    public void onClick(View v) {
-        final int id = v.getId();
-        if (id == R.id.tv_pip_menu_fullscreen_button) {
-            mListener.onFullscreenButtonClick();
-        } else if (id == R.id.tv_pip_menu_move_button) {
-            mListener.onEnterMoveMode();
-        } else if (id == R.id.tv_pip_menu_close_button) {
-            mListener.onCloseButtonClick();
-        } else if (id == R.id.tv_pip_menu_expand_button) {
-            mListener.onToggleExpandedMode();
-        } else {
-            // This should be an "additional action"
-            final RemoteAction action = (RemoteAction) v.getTag();
-            if (action != null) {
-                try {
-                    action.getActionIntent().send();
-                } catch (PendingIntent.CanceledException e) {
-                    ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
-                            "%s: Failed to send action, %s", TAG, e);
-                }
-            } else {
-                ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
-                        "%s: RemoteAction is null", TAG);
-            }
+    public void onActionsChanged(int added, int updated, int startIndex) {
+        mRecyclerViewAdapter.notifyItemRangeChanged(startIndex, updated);
+        if (added > 0) {
+            mRecyclerViewAdapter.notifyItemRangeInserted(startIndex + updated, added);
+        } else if (added < 0) {
+            mRecyclerViewAdapter.notifyItemRangeRemoved(startIndex + updated, -added);
         }
     }
 
     @Override
     public boolean dispatchKeyEvent(KeyEvent event) {
         if (event.getAction() == ACTION_UP) {
-            if (!mMoveMenuIsVisible) {
-                mFocusedButton = mActionButtonsContainer.getFocusedChild();
-            }
 
             if (event.getKeyCode() == KEYCODE_BACK) {
                 mListener.onBackPress();
@@ -624,10 +402,6 @@
     public void showMovementHints(int gravity) {
         ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
                 "%s: showMovementHints(), position: %s", TAG, Gravity.toString(gravity));
-
-        if (mMoveMenuIsVisible) {
-            return;
-        }
         mMoveMenuIsVisible = true;
 
         animateAlphaTo(checkGravity(gravity, Gravity.BOTTOM) ? 1f : 0f, mArrowUp);
@@ -643,9 +417,12 @@
 
         animateAlphaTo(a11yEnabled ? 1f : 0f, mA11yDoneButton);
         if (a11yEnabled) {
+            mA11yDoneButton.setVisibility(VISIBLE);
             mA11yDoneButton.setOnClickListener(v -> {
                 mListener.onExitMoveMode();
             });
+            mA11yDoneButton.requestFocus();
+            mA11yDoneButton.requestAccessibilityFocus();
         }
     }
 
@@ -684,33 +461,67 @@
     /**
      * Show or hide the pip buttons menu.
      */
-    public void showButtonsMenu(boolean show) {
+    private void setMenuButtonsVisible(boolean visible) {
         ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
-                "%s: showUserActions: %b", TAG, show);
-        if (mButtonMenuIsVisible == show) {
-            return;
-        }
-        mButtonMenuIsVisible = show;
-
-        if (show) {
-            mActionButtonsContainer.setVisibility(VISIBLE);
-            refocusPreviousButton();
-        }
-        animateAlphaTo(show ? 1 : 0, mActionButtonsContainer);
-        animateAlphaTo(show ? 1 : 0, mDimLayer);
-        mEduTextDrawer.closeIfNeeded();
+                "%s: showUserActions: %b", TAG, visible);
+        mButtonMenuIsVisible = visible;
+        animateAlphaTo(visible ? 1 : 0, mActionButtonsRecyclerView);
     }
 
     private void setFrameHighlighted(boolean highlighted) {
         mMenuFrameView.setActivated(highlighted);
     }
 
+    private class RecyclerViewAdapter extends
+            RecyclerView.Adapter<RecyclerViewAdapter.ButtonViewHolder> {
+
+        private final List<TvPipAction> mActionList;
+
+        RecyclerViewAdapter(List<TvPipAction> actionList) {
+            this.mActionList = actionList;
+        }
+
+        @NonNull
+        @Override
+        public ButtonViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
+            return new ButtonViewHolder(new TvWindowMenuActionButton(mContext));
+        }
+
+        @Override
+        public void onBindViewHolder(@NonNull ButtonViewHolder holder, int position) {
+            TvPipAction action = mActionList.get(position);
+            action.populateButton(holder.mButton, mMainHandler);
+        }
+
+        @Override
+        public int getItemCount() {
+            return mActionList.size();
+        }
+
+        private class ButtonViewHolder extends RecyclerView.ViewHolder implements OnClickListener {
+            TvWindowMenuActionButton mButton;
+
+            ButtonViewHolder(@NonNull View itemView) {
+                super(itemView);
+                mButton = (TvWindowMenuActionButton) itemView;
+                mButton.setOnClickListener(this);
+            }
+
+            @Override
+            public void onClick(View v) {
+                TvPipAction action = mActionList.get(
+                        mActionButtonsRecyclerView.getChildLayoutPosition(v));
+                if (action != null) {
+                    action.executeAction();
+                }
+            }
+        }
+    }
+
     interface Listener extends TvPipMenuEduTextDrawer.Listener {
 
         void onBackPress();
 
-        void onEnterMoveMode();
-
         /**
          * Called when a button for exiting move mode was pressed.
          *
@@ -723,11 +534,5 @@
          * @return whether pip movement was handled.
          */
         boolean onPipMovement(int keycode);
-
-        void onCloseButtonClick();
-
-        void onFullscreenButtonClick();
-
-        void onToggleExpandedMode();
     }
-}
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipNotificationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipNotificationController.java
index e3308f0..f22ee59 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipNotificationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipNotificationController.java
@@ -16,18 +16,13 @@
 
 package com.android.wm.shell.pip.tv;
 
-import static android.app.Notification.Action.SEMANTIC_ACTION_DELETE;
-import static android.app.Notification.Action.SEMANTIC_ACTION_NONE;
-
+import android.annotation.NonNull;
 import android.app.Notification;
 import android.app.NotificationManager;
 import android.app.PendingIntent;
-import android.app.RemoteAction;
-import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
-import android.content.IntentFilter;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.graphics.Bitmap;
@@ -35,7 +30,6 @@
 import android.graphics.drawable.Icon;
 import android.media.session.MediaSession;
 import android.os.Bundle;
-import android.os.Handler;
 import android.text.TextUtils;
 
 import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
@@ -47,7 +41,6 @@
 import com.android.wm.shell.pip.PipUtils;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
 
-import java.util.ArrayList;
 import java.util.List;
 
 /**
@@ -55,39 +48,18 @@
  * <p>Once it's created, it will manage the PiP notification UI by itself except for handling
  * configuration changes and user initiated expanded PiP toggling.
  */
-public class TvPipNotificationController {
-    private static final String TAG = "TvPipNotification";
+public class TvPipNotificationController implements TvPipActionsProvider.Listener {
+    private static final String TAG = TvPipNotificationController.class.getSimpleName();
 
     // Referenced in com.android.systemui.util.NotificationChannels.
     public static final String NOTIFICATION_CHANNEL = "TVPIP";
     private static final String NOTIFICATION_TAG = "TvPip";
-    private static final String SYSTEMUI_PERMISSION = "com.android.systemui.permission.SELF";
-
-    private static final String ACTION_SHOW_PIP_MENU =
-            "com.android.wm.shell.pip.tv.notification.action.SHOW_PIP_MENU";
-    private static final String ACTION_CLOSE_PIP =
-            "com.android.wm.shell.pip.tv.notification.action.CLOSE_PIP";
-    private static final String ACTION_MOVE_PIP =
-            "com.android.wm.shell.pip.tv.notification.action.MOVE_PIP";
-    private static final String ACTION_TOGGLE_EXPANDED_PIP =
-            "com.android.wm.shell.pip.tv.notification.action.TOGGLE_EXPANDED_PIP";
-    private static final String ACTION_FULLSCREEN =
-            "com.android.wm.shell.pip.tv.notification.action.FULLSCREEN";
 
     private final Context mContext;
     private final PackageManager mPackageManager;
     private final NotificationManager mNotificationManager;
     private final Notification.Builder mNotificationBuilder;
-    private final ActionBroadcastReceiver mActionBroadcastReceiver;
-    private final Handler mMainHandler;
-    private Delegate mDelegate;
-    private final TvPipBoundsState mTvPipBoundsState;
-
-    private String mDefaultTitle;
-
-    private final List<RemoteAction> mCustomActions = new ArrayList<>();
-    private final List<RemoteAction> mMediaActions = new ArrayList<>();
-    private RemoteAction mCustomCloseAction;
+    private TvPipActionsProvider mTvPipActionsProvider;
 
     private MediaSession.Token mMediaSessionToken;
 
@@ -95,19 +67,23 @@
     private String mPackageName;
 
     private boolean mIsNotificationShown;
+    private String mDefaultTitle;
     private String mPipTitle;
     private String mPipSubtitle;
 
+    // Saving the actions, so they don't have to be regenerated when e.g. the PiP title changes.
+    @NonNull
+    private Notification.Action[] mPipActions;
+
     private Bitmap mActivityIcon;
 
     public TvPipNotificationController(Context context, PipMediaController pipMediaController,
-            PipParamsChangedForwarder pipParamsChangedForwarder, TvPipBoundsState tvPipBoundsState,
-            Handler mainHandler) {
+            PipParamsChangedForwarder pipParamsChangedForwarder) {
         mContext = context;
         mPackageManager = context.getPackageManager();
         mNotificationManager = context.getSystemService(NotificationManager.class);
-        mMainHandler = mainHandler;
-        mTvPipBoundsState = tvPipBoundsState;
+
+        mPipActions = new Notification.Action[0];
 
         mNotificationBuilder = new Notification.Builder(context, NOTIFICATION_CHANNEL)
                 .setLocalOnly(true)
@@ -117,34 +93,15 @@
                 .setOnlyAlertOnce(true)
                 .setSmallIcon(R.drawable.pip_icon)
                 .setAllowSystemGeneratedContextualActions(false)
-                .setContentIntent(createPendingIntent(context, ACTION_FULLSCREEN))
-                .setDeleteIntent(getCloseAction().actionIntent)
-                .extend(new Notification.TvExtender()
-                        .setContentIntent(createPendingIntent(context, ACTION_SHOW_PIP_MENU))
-                        .setDeleteIntent(createPendingIntent(context, ACTION_CLOSE_PIP)));
+                .setContentIntent(
+                        createPendingIntent(context, TvPipController.ACTION_TO_FULLSCREEN));
+        // TvExtender and DeleteIntent set later since they might change.
 
-        mActionBroadcastReceiver = new ActionBroadcastReceiver();
-
-        pipMediaController.addActionListener(this::onMediaActionsChanged);
         pipMediaController.addTokenListener(this::onMediaSessionTokenChanged);
 
         pipParamsChangedForwarder.addListener(
                 new PipParamsChangedForwarder.PipParamsChangedCallback() {
                     @Override
-                    public void onExpandedAspectRatioChanged(float ratio) {
-                        updateExpansionState();
-                    }
-
-                    @Override
-                    public void onActionsChanged(List<RemoteAction> actions,
-                            RemoteAction closeAction) {
-                        mCustomActions.clear();
-                        mCustomActions.addAll(actions);
-                        mCustomCloseAction = closeAction;
-                        updateNotificationContent();
-                    }
-
-                    @Override
                     public void onTitleChanged(String title) {
                         mPipTitle = title;
                         updateNotificationContent();
@@ -157,34 +114,33 @@
                     }
                 });
 
-        onConfigurationChanged(context);
+        onConfigurationChanged();
     }
 
-    void setDelegate(Delegate delegate) {
-        ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: setDelegate(), delegate=%s",
-                TAG, delegate);
+    /**
+     * Call before showing any notification.
+     */
+    void setTvPipActionsProvider(@NonNull TvPipActionsProvider tvPipActionsProvider) {
+        mTvPipActionsProvider = tvPipActionsProvider;
+        mTvPipActionsProvider.addListener(this);
+    }
 
-        if (mDelegate != null) {
-            throw new IllegalStateException(
-                    "The delegate has already been set and should not change.");
-        }
-        if (delegate == null) {
-            throw new IllegalArgumentException("The delegate must not be null.");
-        }
-
-        mDelegate = delegate;
+    void onConfigurationChanged() {
+        mDefaultTitle = mContext.getResources().getString(R.string.pip_notification_unknown_title);
+        updateNotificationContent();
     }
 
     void show(String packageName) {
         ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: show %s", TAG, packageName);
-        if (mDelegate == null) {
-            throw new IllegalStateException("Delegate is not set.");
+        if (mTvPipActionsProvider == null) {
+            ProtoLog.e(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+                    "%s: Missing TvPipActionsProvider", TAG);
+            return;
         }
 
         mIsNotificationShown = true;
         mPackageName = packageName;
         mActivityIcon = getActivityIcon();
-        mActionBroadcastReceiver.register();
 
         updateNotificationContent();
     }
@@ -194,151 +150,42 @@
 
         mIsNotificationShown = false;
         mPackageName = null;
-        mActionBroadcastReceiver.unregister();
-
         mNotificationManager.cancel(NOTIFICATION_TAG, SystemMessage.NOTE_TV_PIP);
     }
 
-    private Notification.Action getToggleAction(boolean expanded) {
-        if (expanded) {
-            return createSystemAction(R.drawable.pip_ic_collapse,
-                    R.string.pip_collapse, ACTION_TOGGLE_EXPANDED_PIP);
-        } else {
-            return createSystemAction(R.drawable.pip_ic_expand, R.string.pip_expand,
-                    ACTION_TOGGLE_EXPANDED_PIP);
-        }
-    }
-
-    private Notification.Action createSystemAction(int iconRes, int titleRes, String action) {
-        Notification.Action.Builder builder = new Notification.Action.Builder(
-                Icon.createWithResource(mContext, iconRes),
-                mContext.getString(titleRes),
-                createPendingIntent(mContext, action));
-        builder.setContextual(true);
-        return builder.build();
-    }
-
-    private void onMediaActionsChanged(List<RemoteAction> actions) {
-        mMediaActions.clear();
-        mMediaActions.addAll(actions);
-        if (mCustomActions.isEmpty()) {
-            updateNotificationContent();
-        }
-    }
-
     private void onMediaSessionTokenChanged(MediaSession.Token token) {
         mMediaSessionToken = token;
         updateNotificationContent();
     }
 
-    private Notification.Action remoteToNotificationAction(RemoteAction action) {
-        return remoteToNotificationAction(action, SEMANTIC_ACTION_NONE);
-    }
-
-    private Notification.Action remoteToNotificationAction(RemoteAction action,
-            int semanticAction) {
-        Notification.Action.Builder builder = new Notification.Action.Builder(action.getIcon(),
-                action.getTitle(),
-                action.getActionIntent());
-        if (action.getContentDescription() != null) {
-            Bundle extras = new Bundle();
-            extras.putCharSequence(Notification.EXTRA_PICTURE_CONTENT_DESCRIPTION,
-                    action.getContentDescription());
-            builder.addExtras(extras);
-        }
-        builder.setSemanticAction(semanticAction);
-        builder.setContextual(true);
-        return builder.build();
-    }
-
-    private Notification.Action[] getNotificationActions() {
-        final List<Notification.Action> actions = new ArrayList<>();
-
-        // 1. Fullscreen
-        actions.add(getFullscreenAction());
-        // 2. Close
-        actions.add(getCloseAction());
-        // 3. App actions
-        final List<RemoteAction> appActions =
-                mCustomActions.isEmpty() ? mMediaActions : mCustomActions;
-        for (RemoteAction appAction : appActions) {
-            if (PipUtils.remoteActionsMatch(mCustomCloseAction, appAction)
-                    || !appAction.isEnabled()) {
-                continue;
-            }
-            actions.add(remoteToNotificationAction(appAction));
-        }
-        // 4. Move
-        actions.add(getMoveAction());
-        // 5. Toggle expansion (if expanded PiP enabled)
-        if (mTvPipBoundsState.getDesiredTvExpandedAspectRatio() > 0
-                && mTvPipBoundsState.isTvExpandedPipSupported()) {
-            actions.add(getToggleAction(mTvPipBoundsState.isTvPipExpanded()));
-        }
-        return actions.toArray(new Notification.Action[0]);
-    }
-
-    private Notification.Action getCloseAction() {
-        if (mCustomCloseAction == null) {
-            return createSystemAction(R.drawable.pip_ic_close_white, R.string.pip_close,
-                    ACTION_CLOSE_PIP);
-        } else {
-            return remoteToNotificationAction(mCustomCloseAction, SEMANTIC_ACTION_DELETE);
-        }
-    }
-
-    private Notification.Action getFullscreenAction() {
-        return createSystemAction(R.drawable.pip_ic_fullscreen_white,
-                R.string.pip_fullscreen, ACTION_FULLSCREEN);
-    }
-
-    private Notification.Action getMoveAction() {
-        return createSystemAction(R.drawable.pip_ic_move_white, R.string.pip_move,
-                ACTION_MOVE_PIP);
-    }
-
-    /**
-     * Called by {@link TvPipController} when the configuration is changed.
-     */
-    void onConfigurationChanged(Context context) {
-        mDefaultTitle = context.getResources().getString(R.string.pip_notification_unknown_title);
-        updateNotificationContent();
-    }
-
-    void updateExpansionState() {
-        updateNotificationContent();
-    }
-
     private void updateNotificationContent() {
         if (mPackageManager == null || !mIsNotificationShown) {
             return;
         }
 
-        Notification.Action[] actions = getNotificationActions();
         ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
                 "%s: update(), title: %s, subtitle: %s, mediaSessionToken: %s, #actions: %s", TAG,
-                getNotificationTitle(), mPipSubtitle, mMediaSessionToken, actions.length);
-        for (Notification.Action action : actions) {
-            ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: action: %s", TAG,
-                    action.toString());
-        }
-
+                getNotificationTitle(), mPipSubtitle, mMediaSessionToken, mPipActions.length);
         mNotificationBuilder
                 .setWhen(System.currentTimeMillis())
                 .setContentTitle(getNotificationTitle())
                 .setContentText(mPipSubtitle)
                 .setSubText(getApplicationLabel(mPackageName))
-                .setActions(actions);
+                .setActions(mPipActions);
         setPipIcon();
 
         Bundle extras = new Bundle();
         extras.putParcelable(Notification.EXTRA_MEDIA_SESSION, mMediaSessionToken);
         mNotificationBuilder.setExtras(extras);
 
+        PendingIntent closeIntent = mTvPipActionsProvider.getCloseAction().getPendingIntent();
+        mNotificationBuilder.setDeleteIntent(closeIntent);
         // TvExtender not recognized if not set last.
         mNotificationBuilder.extend(new Notification.TvExtender()
-                .setContentIntent(createPendingIntent(mContext, ACTION_SHOW_PIP_MENU))
-                .setDeleteIntent(createPendingIntent(mContext, ACTION_CLOSE_PIP)));
+                .setContentIntent(
+                        createPendingIntent(mContext, TvPipController.ACTION_SHOW_PIP_MENU))
+                .setDeleteIntent(closeIntent));
+
         mNotificationManager.notify(NOTIFICATION_TAG, SystemMessage.NOTE_TV_PIP,
                 mNotificationBuilder.build());
     }
@@ -390,68 +237,20 @@
         return ImageUtils.buildScaledBitmap(drawable, width, height, /* allowUpscaling */ true);
     }
 
-    private static PendingIntent createPendingIntent(Context context, String action) {
+    static PendingIntent createPendingIntent(Context context, String action) {
         return PendingIntent.getBroadcast(context, 0,
                 new Intent(action).setPackage(context.getPackageName()),
                 PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
     }
 
-    private class ActionBroadcastReceiver extends BroadcastReceiver {
-        final IntentFilter mIntentFilter;
-        {
-            mIntentFilter = new IntentFilter();
-            mIntentFilter.addAction(ACTION_CLOSE_PIP);
-            mIntentFilter.addAction(ACTION_SHOW_PIP_MENU);
-            mIntentFilter.addAction(ACTION_MOVE_PIP);
-            mIntentFilter.addAction(ACTION_TOGGLE_EXPANDED_PIP);
-            mIntentFilter.addAction(ACTION_FULLSCREEN);
+    @Override
+    public void onActionsChanged(int added, int updated, int startIndex) {
+        List<TvPipAction> actions = mTvPipActionsProvider.getActionsList();
+        mPipActions = new Notification.Action[actions.size()];
+        for (int i = 0; i < mPipActions.length; i++) {
+            mPipActions[i] = actions.get(i).toNotificationAction(mContext);
         }
-        boolean mRegistered = false;
-
-        void register() {
-            if (mRegistered) return;
-
-            mContext.registerReceiverForAllUsers(this, mIntentFilter, SYSTEMUI_PERMISSION,
-                    mMainHandler);
-            mRegistered = true;
-        }
-
-        void unregister() {
-            if (!mRegistered) return;
-
-            mContext.unregisterReceiver(this);
-            mRegistered = false;
-        }
-
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            final String action = intent.getAction();
-            ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
-                    "%s: on(Broadcast)Receive(), action=%s", TAG, action);
-
-            if (ACTION_SHOW_PIP_MENU.equals(action)) {
-                mDelegate.showPictureInPictureMenu();
-            } else if (ACTION_CLOSE_PIP.equals(action)) {
-                mDelegate.closePip();
-            } else if (ACTION_MOVE_PIP.equals(action)) {
-                mDelegate.enterPipMovementMenu();
-            } else if (ACTION_TOGGLE_EXPANDED_PIP.equals(action)) {
-                mDelegate.togglePipExpansion();
-            } else if (ACTION_FULLSCREEN.equals(action)) {
-                mDelegate.movePipToFullscreen();
-            }
-        }
+        updateNotificationContent();
     }
 
-    interface Delegate {
-        void showPictureInPictureMenu();
-
-        void closePip();
-
-        void enterPipMovementMenu();
-
-        void togglePipExpansion();
-
-        void movePipToFullscreen();
-    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipSystemAction.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipSystemAction.java
new file mode 100644
index 0000000..93b6a90
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipSystemAction.java
@@ -0,0 +1,83 @@
+/*
+ * 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.wm.shell.pip.tv;
+
+import static android.app.Notification.Action.SEMANTIC_ACTION_DELETE;
+import static android.app.Notification.Action.SEMANTIC_ACTION_NONE;
+
+import android.annotation.DrawableRes;
+import android.annotation.NonNull;
+import android.annotation.StringRes;
+import android.app.Notification;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.graphics.drawable.Icon;
+import android.os.Handler;
+
+import com.android.wm.shell.common.TvWindowMenuActionButton;
+
+/**
+ * A TvPipAction for actions that the system provides, i.e. fullscreen, default close, move,
+ * expand/collapse.
+ */
+public class TvPipSystemAction extends TvPipAction {
+
+    @StringRes
+    private int mTitleResource;
+    @DrawableRes
+    private int mIconResource;
+
+    private final PendingIntent mBroadcastIntent;
+
+    TvPipSystemAction(@ActionType int actionType, @StringRes int title, @DrawableRes int icon,
+            String broadcastAction, @NonNull Context context,
+            SystemActionsHandler systemActionsHandler) {
+        super(actionType, systemActionsHandler);
+        update(title, icon);
+        mBroadcastIntent = TvPipNotificationController.createPendingIntent(context,
+                broadcastAction);
+    }
+
+    void update(@StringRes int title, @DrawableRes int icon) {
+        mTitleResource = title;
+        mIconResource = icon;
+    }
+
+    void populateButton(@NonNull TvWindowMenuActionButton button, Handler mainHandler) {
+        button.setTextAndDescription(mTitleResource);
+        button.setImageResource(mIconResource);
+        button.setEnabled(true);
+    }
+
+    PendingIntent getPendingIntent() {
+        return mBroadcastIntent;
+    }
+
+    @Override
+    Notification.Action toNotificationAction(Context context) {
+        Notification.Action.Builder builder = new Notification.Action.Builder(
+                Icon.createWithResource(context, mIconResource),
+                context.getString(mTitleResource),
+                mBroadcastIntent);
+
+        builder.setSemanticAction(isCloseAction()
+                ? SEMANTIC_ACTION_DELETE : SEMANTIC_ACTION_NONE);
+        builder.setContextual(true);
+        return builder.build();
+    }
+
+}
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 602d0e6..5be29db 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
@@ -357,11 +357,6 @@
         return mMainStage.isActive();
     }
 
-    boolean isSplitScreenRunningBackground() {
-        return !isSplitScreenVisible() && mMainStageListener.mHasChildren
-                && mSideStageListener.mHasChildren;
-    }
-
     @StageType
     int getStageOfTask(int taskId) {
         if (mMainStage.containsTask(taskId)) {
@@ -389,7 +384,9 @@
                 targetStage = stagePosition == mSideStagePosition ? mSideStage : mMainStage;
                 sideStagePosition = mSideStagePosition;
             } else {
-                exitSplitIfBackground();
+                // Exit split if it running background.
+                exitSplitScreen(null /* childrenToTop */, EXIT_REASON_RECREATE_SPLIT);
+
                 targetStage = mSideStage;
                 sideStagePosition = stagePosition;
             }
@@ -685,7 +682,10 @@
             @Nullable PendingIntent mainPendingIntent, @Nullable Intent mainFillInIntent,
             @Nullable Bundle mainOptions, @SplitPosition int sidePosition, float splitRatio,
             RemoteAnimationAdapter adapter, InstanceId instanceId) {
-        exitSplitIfBackground();
+        if (!isSplitScreenVisible()) {
+            exitSplitScreen(null /* childrenToTop */, EXIT_REASON_RECREATE_SPLIT);
+        }
+
         // Init divider first to make divider leash for remote animation target.
         mSplitLayout.init();
         mSplitLayout.setDivideRatio(splitRatio);
@@ -1070,7 +1070,6 @@
             mSideStage.removeAllTasks(wct, false /* toTop */);
             mMainStage.deactivate(wct, false /* toTop */);
             wct.reorder(mRootTaskInfo.token, false /* onTop */);
-            wct.setForceTranslucent(mRootTaskInfo.token, true);
             wct.setBounds(mSideStage.mRootTaskInfo.token, mTempRect1);
             onTransitionAnimationComplete();
         } else {
@@ -1102,7 +1101,6 @@
                     mMainStage.deactivate(finishedWCT, childrenToTop == mMainStage /* toTop */);
                     mSideStage.removeAllTasks(finishedWCT, childrenToTop == mSideStage /* toTop */);
                     finishedWCT.reorder(mRootTaskInfo.token, false /* toTop */);
-                    finishedWCT.setForceTranslucent(mRootTaskInfo.token, true);
                     finishedWCT.setBounds(mSideStage.mRootTaskInfo.token, mTempRect1);
                     mSyncQueue.queue(finishedWCT);
                     mSyncQueue.runInSync(at -> {
@@ -1122,13 +1120,6 @@
         }
     }
 
-    /** Exit split screen if it still running background */
-    public void exitSplitIfBackground() {
-        if (!isSplitScreenRunningBackground()) return;
-
-        exitSplitScreen(null /* childrenToTop */, EXIT_REASON_RECREATE_SPLIT);
-    }
-
     /**
      * Overridden by child classes.
      */
@@ -1451,7 +1442,8 @@
     }
 
     void onChildTaskAppeared(StageListenerImpl stageListener, int taskId) {
-        if (stageListener == mSideStageListener && isSplitScreenRunningBackground()) {
+        if (stageListener == mSideStageListener && !isSplitScreenVisible() && isSplitActive()
+                && !mIsSplitEntering) {
             // Handle entring split case here if split already running background.
             if (mIsDropEntering) {
                 mSplitLayout.resetDividerPosition();
@@ -1459,11 +1451,12 @@
                 mSplitLayout.setDividerAtBorder(mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT);
             }
             final WindowContainerTransaction wct = new WindowContainerTransaction();
+            mMainStage.reparentTopTask(wct);
             mMainStage.evictAllChildren(wct);
             mSideStage.evictOtherChildren(wct, taskId);
-            mMainStage.reparentTopTask(wct);
             updateWindowBounds(mSplitLayout, wct);
             wct.reorder(mRootTaskInfo.token, true);
+            wct.setForceTranslucent(mRootTaskInfo.token, false);
 
             mSyncQueue.queue(wct);
             mSyncQueue.runInSync(t -> {
@@ -1471,6 +1464,7 @@
                     updateSurfaceBounds(mSplitLayout, t, false /* applyResizingOffset */);
                     mIsDropEntering = false;
                 } else {
+                    mShowDecorImmediately = true;
                     mSplitLayout.flingDividerToCenter();
                 }
             });
@@ -1499,6 +1493,7 @@
         if (!mainStageVisible) {
             wct.setReparentLeafTaskIfRelaunch(mRootTaskInfo.token,
                     true /* setReparentLeafTaskIfRelaunch */);
+            wct.setForceTranslucent(mRootTaskInfo.token, true);
             // Both stages are not visible, check if it needs to dismiss split screen.
             if (mExitSplitScreenOnHide) {
                 exitSplitScreen(null /* childrenToTop */, EXIT_REASON_RETURN_HOME);
@@ -1506,6 +1501,7 @@
         } else {
             wct.setReparentLeafTaskIfRelaunch(mRootTaskInfo.token,
                     false /* setReparentLeafTaskIfRelaunch */);
+            wct.setForceTranslucent(mRootTaskInfo.token, false);
         }
         mSyncQueue.queue(wct);
         mSyncQueue.runInSync(t -> {
@@ -1617,8 +1613,7 @@
                 mSplitLayout.flingDividerToDismiss(
                         mSideStagePosition != SPLIT_POSITION_BOTTOM_OR_RIGHT,
                         EXIT_REASON_APP_FINISHED);
-            } else if (isSplitScreenRunningBackground()) {
-                // Do not exit to any stage due to running background.
+            } else if (!isSplitScreenVisible()) {
                 exitSplitScreen(null /* childrenToTop */, EXIT_REASON_APP_FINISHED);
             }
         } else if (isSideStage && hasChildren && !mMainStage.isActive()) {
@@ -2355,9 +2350,9 @@
         if (!isSplitScreenVisible()) {
             mIsDropEntering = true;
         }
-        if (isSplitScreenRunningBackground()) {
-            // Split running background, log exit first and start new enter request.
-            logExit(EXIT_REASON_RECREATE_SPLIT);
+        if (!isSplitScreenVisible()) {
+            // If split running background, exit split first.
+            exitSplitScreen(null /* childrenToTop */, EXIT_REASON_RECREATE_SPLIT);
         }
         mLogger.enterRequestedByDrag(position, dragSessionId);
     }
@@ -2366,9 +2361,9 @@
      * Sets info to be logged when splitscreen is next entered.
      */
     public void onRequestToSplit(InstanceId sessionId, int enterReason) {
-        if (isSplitScreenRunningBackground()) {
-            // Split running background, log exit first and start new enter request.
-            logExit(EXIT_REASON_RECREATE_SPLIT);
+        if (!isSplitScreenVisible()) {
+            // If split running background, exit split first.
+            exitSplitScreen(null /* childrenToTop */, EXIT_REASON_RECREATE_SPLIT);
         }
         mLogger.enterRequested(sessionId, enterReason);
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java
index 65da757b..9d6711f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java
@@ -239,7 +239,7 @@
         public void resized(ClientWindowFrames frames, boolean reportDraw,
                 MergedConfiguration mergedConfiguration, InsetsState insetsState,
                 boolean forceLayout, boolean alwaysConsumeSystemBars, int displayId, int seqId,
-                boolean dragResizing) {
+                int resizeMode) {
             final TaskSnapshotWindow snapshot = mOuter.get();
             if (snapshot == null) {
                 return;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
index afefd5d..42e2b3f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
@@ -46,6 +46,7 @@
 
 import androidx.annotation.Nullable;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.wm.shell.R;
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.common.DisplayController;
@@ -56,7 +57,6 @@
 import com.android.wm.shell.transition.Transitions;
 
 import java.util.Optional;
-import java.util.function.Supplier;
 
 /**
  * View model for the window decoration with a caption and shadows. Works with
@@ -66,7 +66,6 @@
 public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
     private static final String TAG = "CaptionViewModel";
     private final CaptionWindowDecoration.Factory mCaptionWindowDecorFactory;
-    private final Supplier<InputManager> mInputManagerSupplier;
     private final ActivityTaskManager mActivityTaskManager;
     private final ShellTaskOrganizer mTaskOrganizer;
     private final Context mContext;
@@ -82,7 +81,7 @@
 
     private final SparseArray<CaptionWindowDecoration> mWindowDecorByTaskId = new SparseArray<>();
     private final DragStartListenerImpl mDragStartListener = new DragStartListenerImpl();
-    private EventReceiverFactory mEventReceiverFactory = new EventReceiverFactory();
+    private InputMonitorFactory mInputMonitorFactory;
 
     public CaptionWindowDecorViewModel(
             Context context,
@@ -101,10 +100,11 @@
                 syncQueue,
                 desktopModeController,
                 new CaptionWindowDecoration.Factory(),
-                InputManager::getInstance);
+                new InputMonitorFactory());
     }
 
-    public CaptionWindowDecorViewModel(
+    @VisibleForTesting
+    CaptionWindowDecorViewModel(
             Context context,
             Handler mainHandler,
             Choreographer mainChoreographer,
@@ -113,8 +113,7 @@
             SyncTransactionQueue syncQueue,
             Optional<DesktopModeController> desktopModeController,
             CaptionWindowDecoration.Factory captionWindowDecorFactory,
-            Supplier<InputManager> inputManagerSupplier) {
-
+            InputMonitorFactory inputMonitorFactory) {
         mContext = context;
         mMainHandler = mainHandler;
         mMainChoreographer = mainChoreographer;
@@ -125,11 +124,7 @@
         mDesktopModeController = desktopModeController;
 
         mCaptionWindowDecorFactory = captionWindowDecorFactory;
-        mInputManagerSupplier = inputManagerSupplier;
-    }
-
-    void setEventReceiverFactory(EventReceiverFactory eventReceiverFactory) {
-        mEventReceiverFactory = eventReceiverFactory;
+        mInputMonitorFactory = inputMonitorFactory;
     }
 
     @Override
@@ -205,7 +200,6 @@
         decoration.close();
         int displayId = taskInfo.displayId;
         if (mEventReceiversByDisplay.contains(displayId)) {
-            EventReceiver eventReceiver = mEventReceiversByDisplay.get(displayId);
             removeTaskFromEventReceiver(displayId);
         }
     }
@@ -408,12 +402,6 @@
         }
     }
 
-    class EventReceiverFactory {
-        EventReceiver create(InputMonitor inputMonitor, InputChannel channel, Looper looper) {
-            return new EventReceiver(inputMonitor, channel, looper);
-        }
-    }
-
     /**
      * Handle MotionEvents relevant to focused task's caption that don't directly touch it
      *
@@ -500,11 +488,11 @@
     }
 
     private void createInputChannel(int displayId) {
-        InputManager inputManager = mInputManagerSupplier.get();
+        InputManager inputManager = InputManager.getInstance();
         InputMonitor inputMonitor =
-                inputManager.monitorGestureInput("caption-touch", mContext.getDisplayId());
-        EventReceiver eventReceiver = mEventReceiverFactory.create(
-                inputMonitor, inputMonitor.getInputChannel(), Looper.myLooper());
+                mInputMonitorFactory.create(inputManager, mContext);
+        EventReceiver eventReceiver = new EventReceiver(inputMonitor,
+                inputMonitor.getInputChannel(), Looper.myLooper());
         mEventReceiversByDisplay.put(displayId, eventReceiver);
     }
 
@@ -562,4 +550,12 @@
             mWindowDecorByTaskId.get(taskId).closeHandleMenu();
         }
     }
+
+    static class InputMonitorFactory {
+        InputMonitor create(InputManager inputManager, Context context) {
+            return inputManager.monitorGestureInput("caption-touch", context.getDisplayId());
+        }
+    }
 }
+
+
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipActionProviderTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipActionProviderTest.java
new file mode 100644
index 0000000..91040e9
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipActionProviderTest.java
@@ -0,0 +1,309 @@
+/*
+ * 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.wm.shell.pip.tv;
+
+import static com.android.wm.shell.pip.tv.TvPipAction.ACTION_CLOSE;
+import static com.android.wm.shell.pip.tv.TvPipAction.ACTION_CUSTOM;
+import static com.android.wm.shell.pip.tv.TvPipAction.ACTION_CUSTOM_CLOSE;
+import static com.android.wm.shell.pip.tv.TvPipAction.ACTION_EXPAND_COLLAPSE;
+import static com.android.wm.shell.pip.tv.TvPipAction.ACTION_FULLSCREEN;
+import static com.android.wm.shell.pip.tv.TvPipAction.ACTION_MOVE;
+
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.app.PendingIntent;
+import android.app.RemoteAction;
+import android.graphics.drawable.Icon;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.util.Log;
+
+import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.pip.PipMediaController;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Unit tests for {@link TvPipActionsProvider}
+ */
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class TvPipActionProviderTest extends ShellTestCase {
+    private static final String TAG = TvPipActionProviderTest.class.getSimpleName();
+    private TvPipActionsProvider mActionsProvider;
+
+    @Mock
+    private PipMediaController mMockPipMediaController;
+    @Mock
+    private TvPipActionsProvider.Listener mMockListener;
+    @Mock
+    private TvPipAction.SystemActionsHandler mMockSystemActionsHandler;
+    @Mock
+    private Icon mMockIcon;
+    @Mock
+    private PendingIntent mMockPendingIntent;
+
+    private RemoteAction createRemoteAction(int identifier) {
+        return new RemoteAction(mMockIcon, "" + identifier, "" + identifier, mMockPendingIntent);
+    }
+
+    private List<RemoteAction> createRemoteActions(int numberOfActions) {
+        List<RemoteAction> actions = new ArrayList<>();
+        for (int i = 0; i < numberOfActions; i++) {
+            actions.add(createRemoteAction(i));
+        }
+        return actions;
+    }
+
+    private boolean checkActionsMatch(List<TvPipAction> actions, int[] actionTypes) {
+        for (int i = 0; i < actions.size(); i++) {
+            int type = actions.get(i).getActionType();
+            if (type != actionTypes[i]) {
+                Log.e(TAG, "Action at index " + i + ": found " + type
+                        + ", expected " + actionTypes[i]);
+                return false;
+            }
+        }
+        return true;
+    }
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mActionsProvider = new TvPipActionsProvider(mContext, mMockPipMediaController,
+                mMockSystemActionsHandler);
+    }
+
+    @Test
+    public void defaultSystemActions_regularPip() {
+        mActionsProvider.updateExpansionEnabled(false);
+        assertTrue(checkActionsMatch(mActionsProvider.getActionsList(),
+                new int[]{ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_MOVE}));
+    }
+
+    @Test
+    public void defaultSystemActions_expandedPip() {
+        mActionsProvider.updateExpansionEnabled(true);
+        assertTrue(checkActionsMatch(mActionsProvider.getActionsList(),
+                new int[]{ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_MOVE, ACTION_EXPAND_COLLAPSE}));
+    }
+
+    @Test
+    public void expandedPip_enableExpansion_enable() {
+        // PiP has expanded PiP disabled.
+        mActionsProvider.updateExpansionEnabled(false);
+
+        mActionsProvider.addListener(mMockListener);
+        mActionsProvider.updateExpansionEnabled(true);
+
+        assertTrue(checkActionsMatch(mActionsProvider.getActionsList(),
+                new int[]{ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_MOVE, ACTION_EXPAND_COLLAPSE}));
+        verify(mMockListener).onActionsChanged(/* added= */ 1, /* updated= */ 0, /* index= */ 3);
+    }
+
+    @Test
+    public void expandedPip_enableExpansion_disable() {
+        mActionsProvider.updateExpansionEnabled(true);
+
+        mActionsProvider.addListener(mMockListener);
+        mActionsProvider.updateExpansionEnabled(false);
+
+        assertTrue(checkActionsMatch(mActionsProvider.getActionsList(),
+                new int[]{ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_MOVE}));
+        verify(mMockListener).onActionsChanged(/* added= */ -1, /* updated= */ 0, /* index= */ 3);
+    }
+
+    @Test
+    public void expandedPip_enableExpansion_AlreadyEnabled() {
+        mActionsProvider.updateExpansionEnabled(true);
+
+        mActionsProvider.addListener(mMockListener);
+        mActionsProvider.updateExpansionEnabled(true);
+
+        assertTrue(checkActionsMatch(mActionsProvider.getActionsList(),
+                new int[]{ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_MOVE, ACTION_EXPAND_COLLAPSE}));
+    }
+
+    @Test
+    public void expandedPip_toggleExpansion() {
+        // PiP has expanded PiP enabled, but is in a collapsed state
+        mActionsProvider.updateExpansionEnabled(true);
+        mActionsProvider.onPipExpansionToggled(/* expanded= */ false);
+
+        mActionsProvider.addListener(mMockListener);
+        mActionsProvider.onPipExpansionToggled(/* expanded= */ true);
+
+        assertTrue(checkActionsMatch(mActionsProvider.getActionsList(),
+                new int[]{ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_MOVE, ACTION_EXPAND_COLLAPSE}));
+        verify(mMockListener).onActionsChanged(0, 1, 3);
+    }
+
+    @Test
+    public void customActions_added() {
+        mActionsProvider.updateExpansionEnabled(false);
+        mActionsProvider.addListener(mMockListener);
+
+        mActionsProvider.setAppActions(createRemoteActions(2), null);
+
+        assertTrue(checkActionsMatch(mActionsProvider.getActionsList(),
+                new int[]{ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_CUSTOM, ACTION_CUSTOM,
+                        ACTION_MOVE}));
+        verify(mMockListener).onActionsChanged(/* added= */ 2, /* updated= */ 0, /* index= */ 2);
+    }
+
+    @Test
+    public void customActions_replacedMore() {
+        mActionsProvider.updateExpansionEnabled(false);
+        mActionsProvider.setAppActions(createRemoteActions(2), null);
+
+        mActionsProvider.addListener(mMockListener);
+        mActionsProvider.setAppActions(createRemoteActions(3), null);
+
+        assertTrue(checkActionsMatch(mActionsProvider.getActionsList(),
+                new int[]{ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_CUSTOM, ACTION_CUSTOM,
+                        ACTION_CUSTOM, ACTION_MOVE}));
+        verify(mMockListener).onActionsChanged(/* added= */ 1, /* updated= */ 2, /* index= */ 2);
+    }
+
+    @Test
+    public void customActions_replacedLess() {
+        mActionsProvider.updateExpansionEnabled(false);
+        mActionsProvider.setAppActions(createRemoteActions(2), null);
+
+        mActionsProvider.addListener(mMockListener);
+        mActionsProvider.setAppActions(createRemoteActions(0), null);
+
+        assertTrue(checkActionsMatch(mActionsProvider.getActionsList(),
+                new int[]{ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_MOVE}));
+        verify(mMockListener).onActionsChanged(/* added= */ -2, /* updated= */ 0, /* index= */ 2);
+    }
+
+    @Test
+    public void customCloseAdded() {
+        mActionsProvider.updateExpansionEnabled(false);
+
+        List<RemoteAction> customActions = new ArrayList<>();
+        mActionsProvider.setAppActions(customActions, null);
+
+        mActionsProvider.addListener(mMockListener);
+        mActionsProvider.setAppActions(customActions, createRemoteAction(0));
+
+        assertTrue(checkActionsMatch(mActionsProvider.getActionsList(),
+                new int[]{ACTION_FULLSCREEN, ACTION_CUSTOM_CLOSE, ACTION_MOVE}));
+        verify(mMockListener).onActionsChanged(/* added= */ 0, /* updated= */ 1, /* index= */ 1);
+    }
+
+    @Test
+    public void customClose_matchesOtherCustomAction() {
+        mActionsProvider.updateExpansionEnabled(false);
+
+        List<RemoteAction> customActions = createRemoteActions(2);
+        RemoteAction customClose = createRemoteAction(/* id= */ 10);
+        customActions.add(customClose);
+
+        mActionsProvider.addListener(mMockListener);
+        mActionsProvider.setAppActions(customActions, customClose);
+
+        assertTrue(checkActionsMatch(mActionsProvider.getActionsList(),
+                new int[]{ACTION_FULLSCREEN, ACTION_CUSTOM_CLOSE, ACTION_CUSTOM, ACTION_CUSTOM,
+                        ACTION_MOVE}));
+        verify(mMockListener).onActionsChanged(/* added= */ 0, /* updated= */ 1, /* index= */ 1);
+        verify(mMockListener).onActionsChanged(/* added= */ 2, /* updated= */ 0, /* index= */ 2);
+    }
+
+    @Test
+    public void mediaActions_added_whileCustomActionsExist() {
+        mActionsProvider.updateExpansionEnabled(false);
+        mActionsProvider.setAppActions(createRemoteActions(2), null);
+
+        mActionsProvider.addListener(mMockListener);
+        mActionsProvider.onMediaActionsChanged(createRemoteActions(3));
+
+        assertTrue(checkActionsMatch(mActionsProvider.getActionsList(),
+                new int[]{ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_CUSTOM, ACTION_CUSTOM,
+                        ACTION_MOVE}));
+        verify(mMockListener, times(0)).onActionsChanged(anyInt(), anyInt(), anyInt());
+    }
+
+    @Test
+    public void customActions_removed_whileMediaActionsExist() {
+        mActionsProvider.updateExpansionEnabled(false);
+        mActionsProvider.onMediaActionsChanged(createRemoteActions(2));
+        mActionsProvider.setAppActions(createRemoteActions(3), null);
+
+        mActionsProvider.addListener(mMockListener);
+        mActionsProvider.setAppActions(createRemoteActions(0), null);
+
+        assertTrue(checkActionsMatch(mActionsProvider.getActionsList(),
+                new int[]{ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_CUSTOM, ACTION_CUSTOM,
+                        ACTION_MOVE}));
+        verify(mMockListener).onActionsChanged(/* added= */ -1, /* updated= */ 2, /* index= */ 2);
+    }
+
+    @Test
+    public void customCloseOnly_mediaActionsShowing() {
+        mActionsProvider.updateExpansionEnabled(false);
+        mActionsProvider.onMediaActionsChanged(createRemoteActions(2));
+
+        mActionsProvider.addListener(mMockListener);
+        mActionsProvider.setAppActions(createRemoteActions(0), createRemoteAction(5));
+
+        assertTrue(checkActionsMatch(mActionsProvider.getActionsList(),
+                new int[]{ACTION_FULLSCREEN, ACTION_CUSTOM_CLOSE, ACTION_CUSTOM, ACTION_CUSTOM,
+                        ACTION_MOVE}));
+        verify(mMockListener).onActionsChanged(/* added= */ 0, /* updated= */ 1, /* index= */ 1);
+    }
+
+    @Test
+    public void customActions_showDisabledActions() {
+        mActionsProvider.updateExpansionEnabled(false);
+
+        List<RemoteAction> customActions = createRemoteActions(2);
+        customActions.get(0).setEnabled(false);
+        mActionsProvider.setAppActions(customActions, null);
+
+        assertTrue(checkActionsMatch(mActionsProvider.getActionsList(),
+                new int[]{ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_CUSTOM, ACTION_CUSTOM,
+                        ACTION_MOVE}));
+    }
+
+    @Test
+    public void mediaActions_hideDisabledActions() {
+        mActionsProvider.updateExpansionEnabled(false);
+
+        List<RemoteAction> customActions = createRemoteActions(2);
+        customActions.get(0).setEnabled(false);
+        mActionsProvider.onMediaActionsChanged(customActions);
+
+        assertTrue(checkActionsMatch(mActionsProvider.getActionsList(),
+                new int[]{ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_CUSTOM, ACTION_MOVE}));
+    }
+
+}
+
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModelTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModelTests.java
index ad6fced..0dbf30d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModelTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModelTests.java
@@ -21,14 +21,15 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 
 import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.anyInt;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.app.ActivityManager;
+import android.hardware.display.DisplayManager;
+import android.hardware.display.VirtualDisplay;
 import android.hardware.input.InputManager;
 import android.os.Handler;
 import android.os.Looper;
@@ -37,9 +38,9 @@
 import android.view.InputChannel;
 import android.view.InputMonitor;
 import android.view.SurfaceControl;
+import android.view.SurfaceView;
 
 import androidx.test.filters.SmallTest;
-import androidx.test.rule.GrantPermissionRule;
 
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.ShellTestCase;
@@ -55,37 +56,28 @@
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Optional;
-import java.util.function.Supplier;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
 
 /** Tests of {@link CaptionWindowDecorViewModel} */
 @SmallTest
 public class CaptionWindowDecorViewModelTests extends ShellTestCase {
-    @Mock private CaptionWindowDecoration mCaptionWindowDecoration;
 
+    private static final String TAG = "CaptionWindowDecorViewModelTests";
+
+    @Mock private CaptionWindowDecoration mCaptionWindowDecoration;
     @Mock private CaptionWindowDecoration.Factory mCaptionWindowDecorFactory;
 
     @Mock private Handler mMainHandler;
-
     @Mock private Choreographer mMainChoreographer;
-
     @Mock private ShellTaskOrganizer mTaskOrganizer;
-
     @Mock private DisplayController mDisplayController;
-
     @Mock private SyncTransactionQueue mSyncQueue;
-
     @Mock private DesktopModeController mDesktopModeController;
-
     @Mock private InputMonitor mInputMonitor;
-
-    @Mock private InputChannel mInputChannel;
-
-    @Mock private CaptionWindowDecorViewModel.EventReceiverFactory mEventReceiverFactory;
-
-    @Mock private CaptionWindowDecorViewModel.EventReceiver mEventReceiver;
-
     @Mock private InputManager mInputManager;
 
+    @Mock private CaptionWindowDecorViewModel.InputMonitorFactory mMockInputMonitorFactory;
     private final List<InputManager> mMockInputManagers = new ArrayList<>();
 
     private CaptionWindowDecorViewModel mCaptionWindowDecorViewModel;
@@ -104,44 +96,46 @@
                 mSyncQueue,
                 Optional.of(mDesktopModeController),
                 mCaptionWindowDecorFactory,
-                new MockObjectSupplier<>(mMockInputManagers, () -> mock(InputManager.class)));
-        mCaptionWindowDecorViewModel.setEventReceiverFactory(mEventReceiverFactory);
+                mMockInputMonitorFactory
+            );
 
         doReturn(mCaptionWindowDecoration)
             .when(mCaptionWindowDecorFactory)
             .create(any(), any(), any(), any(), any(), any(), any(), any());
 
-        when(mInputManager.monitorGestureInput(any(), anyInt())).thenReturn(mInputMonitor);
-        when(mEventReceiverFactory.create(any(), any(), any())).thenReturn(mEventReceiver);
-        when(mInputMonitor.getInputChannel()).thenReturn(mInputChannel);
+        when(mMockInputMonitorFactory.create(any(), any())).thenReturn(mInputMonitor);
+        // InputChannel cannot be mocked because it passes to InputEventReceiver.
+        final InputChannel[] inputChannels = InputChannel.openInputChannelPair(TAG);
+        inputChannels[0].dispose();
+        when(mInputMonitor.getInputChannel()).thenReturn(inputChannels[1]);
     }
 
     @Test
     public void testDeleteCaptionOnChangeTransitionWhenNecessary() throws Exception {
-        Looper.prepare();
         final int taskId = 1;
         final ActivityManager.RunningTaskInfo taskInfo =
-                createTaskInfo(taskId, WINDOWING_MODE_FREEFORM);
+                createTaskInfo(taskId, Display.DEFAULT_DISPLAY, WINDOWING_MODE_FREEFORM);
         SurfaceControl surfaceControl = mock(SurfaceControl.class);
-        final SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class);
-        final SurfaceControl.Transaction finishT = mock(SurfaceControl.Transaction.class);
-        GrantPermissionRule.grant(android.Manifest.permission.MONITOR_INPUT);
+        runOnMainThread(() -> {
+            final SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class);
+            final SurfaceControl.Transaction finishT = mock(SurfaceControl.Transaction.class);
 
-        mCaptionWindowDecorViewModel.onTaskOpening(taskInfo, surfaceControl, startT, finishT);
+            mCaptionWindowDecorViewModel.onTaskOpening(taskInfo, surfaceControl, startT, finishT);
+
+            taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_UNDEFINED);
+            taskInfo.configuration.windowConfiguration.setActivityType(ACTIVITY_TYPE_UNDEFINED);
+            mCaptionWindowDecorViewModel.onTaskChanging(taskInfo, surfaceControl, startT, finishT);
+        });
         verify(mCaptionWindowDecorFactory)
                 .create(
-                    mContext,
-                    mDisplayController,
-                    mTaskOrganizer,
-                    taskInfo,
-                    surfaceControl,
-                    mMainHandler,
-                    mMainChoreographer,
-                    mSyncQueue);
-
-        taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_UNDEFINED);
-        taskInfo.configuration.windowConfiguration.setActivityType(ACTIVITY_TYPE_UNDEFINED);
-        mCaptionWindowDecorViewModel.onTaskChanging(taskInfo, surfaceControl, startT, finishT);
+                        mContext,
+                        mDisplayController,
+                        mTaskOrganizer,
+                        taskInfo,
+                        surfaceControl,
+                        mMainHandler,
+                        mMainChoreographer,
+                        mSyncQueue);
         verify(mCaptionWindowDecoration).close();
     }
 
@@ -149,70 +143,105 @@
     public void testCreateCaptionOnChangeTransitionWhenNecessary() throws Exception {
         final int taskId = 1;
         final ActivityManager.RunningTaskInfo taskInfo =
-                createTaskInfo(taskId, WINDOWING_MODE_UNDEFINED);
+                createTaskInfo(taskId, Display.DEFAULT_DISPLAY, WINDOWING_MODE_UNDEFINED);
         SurfaceControl surfaceControl = mock(SurfaceControl.class);
-        final SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class);
-        final SurfaceControl.Transaction finishT = mock(SurfaceControl.Transaction.class);
-        taskInfo.configuration.windowConfiguration.setActivityType(ACTIVITY_TYPE_UNDEFINED);
+        runOnMainThread(() -> {
+            final SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class);
+            final SurfaceControl.Transaction finishT = mock(SurfaceControl.Transaction.class);
+            taskInfo.configuration.windowConfiguration.setActivityType(ACTIVITY_TYPE_UNDEFINED);
 
-        mCaptionWindowDecorViewModel.onTaskChanging(taskInfo, surfaceControl, startT, finishT);
+            mCaptionWindowDecorViewModel.onTaskChanging(taskInfo, surfaceControl, startT, finishT);
 
-        verify(mCaptionWindowDecorFactory, never())
+            taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM);
+            taskInfo.configuration.windowConfiguration.setActivityType(ACTIVITY_TYPE_STANDARD);
+
+            mCaptionWindowDecorViewModel.onTaskChanging(taskInfo, surfaceControl, startT, finishT);
+        });
+        verify(mCaptionWindowDecorFactory, times(1))
                 .create(
-                    mContext,
-                    mDisplayController,
-                    mTaskOrganizer,
-                    taskInfo,
-                    surfaceControl,
-                    mMainHandler,
-                    mMainChoreographer,
-                    mSyncQueue);
-
-        taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM);
-        taskInfo.configuration.windowConfiguration.setActivityType(ACTIVITY_TYPE_STANDARD);
-
-        mCaptionWindowDecorViewModel.onTaskChanging(taskInfo, surfaceControl, startT, finishT);
-
-        verify(mCaptionWindowDecorFactory)
-                .create(
-                    mContext,
-                    mDisplayController,
-                    mTaskOrganizer,
-                    taskInfo,
-                    surfaceControl,
-                    mMainHandler,
-                    mMainChoreographer,
-                    mSyncQueue);
+                        mContext,
+                        mDisplayController,
+                        mTaskOrganizer,
+                        taskInfo,
+                        surfaceControl,
+                        mMainHandler,
+                        mMainChoreographer,
+                        mSyncQueue);
     }
 
-    private static ActivityManager.RunningTaskInfo createTaskInfo(int taskId, int windowingMode) {
+    @Test
+    public void testCreateAndDisposeEventReceiver() throws Exception {
+        final int taskId = 1;
+        final ActivityManager.RunningTaskInfo taskInfo =
+                createTaskInfo(taskId, Display.DEFAULT_DISPLAY, WINDOWING_MODE_FREEFORM);
+        taskInfo.configuration.windowConfiguration.setActivityType(ACTIVITY_TYPE_STANDARD);
+        runOnMainThread(() -> {
+            SurfaceControl surfaceControl = mock(SurfaceControl.class);
+            final SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class);
+            final SurfaceControl.Transaction finishT = mock(SurfaceControl.Transaction.class);
+
+            mCaptionWindowDecorViewModel.onTaskOpening(taskInfo, surfaceControl, startT, finishT);
+
+            mCaptionWindowDecorViewModel.destroyWindowDecoration(taskInfo);
+        });
+        verify(mMockInputMonitorFactory).create(any(), any());
+        verify(mInputMonitor).dispose();
+    }
+
+    @Test
+    public void testEventReceiversOnMultipleDisplays() throws Exception {
+        runOnMainThread(() -> {
+            SurfaceView surfaceView = new SurfaceView(mContext);
+            final DisplayManager mDm = mContext.getSystemService(DisplayManager.class);
+            final VirtualDisplay secondaryDisplay = mDm.createVirtualDisplay(
+                    "testEventReceiversOnMultipleDisplays", /*width=*/ 400, /*height=*/ 400,
+                    /*densityDpi=*/ 320, surfaceView.getHolder().getSurface(),
+                    DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY);
+            int secondaryDisplayId = secondaryDisplay.getDisplay().getDisplayId();
+
+            final int taskId = 1;
+            final ActivityManager.RunningTaskInfo taskInfo =
+                    createTaskInfo(taskId, Display.DEFAULT_DISPLAY, WINDOWING_MODE_FREEFORM);
+            final ActivityManager.RunningTaskInfo secondTaskInfo =
+                    createTaskInfo(taskId + 1, secondaryDisplayId, WINDOWING_MODE_FREEFORM);
+            final ActivityManager.RunningTaskInfo thirdTaskInfo =
+                    createTaskInfo(taskId + 2, secondaryDisplayId, WINDOWING_MODE_FREEFORM);
+
+            SurfaceControl surfaceControl = mock(SurfaceControl.class);
+            final SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class);
+            final SurfaceControl.Transaction finishT = mock(SurfaceControl.Transaction.class);
+
+            mCaptionWindowDecorViewModel.onTaskOpening(taskInfo, surfaceControl, startT, finishT);
+            mCaptionWindowDecorViewModel.onTaskOpening(secondTaskInfo, surfaceControl,
+                    startT, finishT);
+            mCaptionWindowDecorViewModel.onTaskOpening(thirdTaskInfo, surfaceControl,
+                    startT, finishT);
+            mCaptionWindowDecorViewModel.destroyWindowDecoration(thirdTaskInfo);
+            mCaptionWindowDecorViewModel.destroyWindowDecoration(taskInfo);
+        });
+        verify(mMockInputMonitorFactory, times(2)).create(any(), any());
+        verify(mInputMonitor, times(1)).dispose();
+    }
+
+    private void runOnMainThread(Runnable r) throws Exception {
+        final Handler mainHandler = new Handler(Looper.getMainLooper());
+        final CountDownLatch latch = new CountDownLatch(1);
+        mainHandler.post(() -> {
+            r.run();
+            latch.countDown();
+        });
+        latch.await(20, TimeUnit.MILLISECONDS);
+    }
+
+    private static ActivityManager.RunningTaskInfo createTaskInfo(int taskId,
+            int displayId, int windowingMode) {
         ActivityManager.RunningTaskInfo taskInfo =
                  new TestRunningTaskInfoBuilder()
-                .setDisplayId(Display.DEFAULT_DISPLAY)
+                .setDisplayId(displayId)
                 .setVisible(true)
                 .build();
         taskInfo.taskId = taskId;
         taskInfo.configuration.windowConfiguration.setWindowingMode(windowingMode);
         return taskInfo;
     }
-
-    private static class MockObjectSupplier<T> implements Supplier<T> {
-        private final List<T> mObjects;
-        private final Supplier<T> mDefaultSupplier;
-        private int mNumOfCalls = 0;
-
-        private MockObjectSupplier(List<T> objects, Supplier<T> defaultSupplier) {
-            mObjects = objects;
-            mDefaultSupplier = defaultSupplier;
-        }
-
-        @Override
-        public T get() {
-            final T mock =
-                    mNumOfCalls < mObjects.size() ? mObjects.get(mNumOfCalls)
-                        : mDefaultSupplier.get();
-            ++mNumOfCalls;
-            return mock;
-        }
-    }
 }
diff --git a/media/java/android/media/AudioFormat.java b/media/java/android/media/AudioFormat.java
index ccd4ed0..4d3f05b 100644
--- a/media/java/android/media/AudioFormat.java
+++ b/media/java/android/media/AudioFormat.java
@@ -373,6 +373,8 @@
      * Use {@link #ENCODING_DTS_UHD_P2} to transmit DTS UHD Profile 2 (aka DTS:X Profile 2)
      * bitstream. */
     public static final int ENCODING_DTS_UHD_P2 = 30;
+    /** Audio data format: Direct Stream Digital */
+    public static final int ENCODING_DSD = 31;
 
     /** @hide */
     public static String toLogFriendlyEncoding(int enc) {
@@ -437,6 +439,8 @@
                 return "ENCODING_DTS_HD_MA";
             case ENCODING_DTS_UHD_P2:
                 return "ENCODING_DTS_UHD_P2";
+            case ENCODING_DSD:
+                return "ENCODING_DSD";
             default :
                 return "invalid encoding " + enc;
         }
@@ -798,6 +802,7 @@
             case ENCODING_DRA:
             case ENCODING_DTS_HD_MA:
             case ENCODING_DTS_UHD_P2:
+            case ENCODING_DSD:
                 return true;
             default:
                 return false;
@@ -837,6 +842,7 @@
             case ENCODING_DRA:
             case ENCODING_DTS_HD_MA:
             case ENCODING_DTS_UHD_P2:
+            case ENCODING_DSD:
                 return true;
             default:
                 return false;
@@ -1211,6 +1217,7 @@
                 case ENCODING_DRA:
                 case ENCODING_DTS_HD_MA:
                 case ENCODING_DTS_UHD_P2:
+                case ENCODING_DSD:
                     mEncoding = encoding;
                     break;
                 case ENCODING_INVALID:
@@ -1441,7 +1448,8 @@
         ENCODING_DTS_UHD_P1,
         ENCODING_DRA,
         ENCODING_DTS_HD_MA,
-        ENCODING_DTS_UHD_P2 }
+        ENCODING_DTS_UHD_P2,
+        ENCODING_DSD }
     )
     @Retention(RetentionPolicy.SOURCE)
     public @interface Encoding {}
diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppService.java b/media/java/android/media/tv/interactive/TvInteractiveAppService.java
index 90eed9e..00150d5 100755
--- a/media/java/android/media/tv/interactive/TvInteractiveAppService.java
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppService.java
@@ -457,7 +457,9 @@
         /**
          * Receives started recording's ID.
          *
-         * @param recordingId The ID of the recording started
+         * @param recordingId The ID of the recording started. The TV app should provide and
+         *                    maintain this ID to identify the recording in the future.
+         * @see #onRecordingStopped(String)
          */
         public void onRecordingStarted(@NonNull String recordingId) {
         }
@@ -465,13 +467,13 @@
         /**
          * Receives stopped recording's ID.
          *
-         * @param recordingId The ID of the recording stopped
-         * @hide
+         * @param recordingId The ID of the recording stopped. This ID is created and maintained by
+         *                    the TV app when the recording was started.
+         * @see #onRecordingStarted(String)
          */
         public void onRecordingStopped(@NonNull String recordingId) {
         }
 
-
         /**
          * Receives signing result.
          * @param signingId the ID to identify the request. It's the same as the corresponding ID in
@@ -952,13 +954,14 @@
         }
 
         /**
-         * Requests starting of recording
+         * Requests the recording associated with the recordingId to stop.
          *
-         * <p> This is used to request the active {@link android.media.tv.TvRecordingClient} to
+         * <p> This is used to request the associated {@link android.media.tv.TvRecordingClient} to
          * call {@link android.media.tv.TvRecordingClient#stopRecording()}.
-         * @see android.media.tv.TvRecordingClient#stopRecording()
          *
-         * @hide
+         * @param recordingId The ID of the recording to stop. This is provided by the TV app in
+         *                    {@link TvInteractiveAppView#notifyRecordingStarted(String)}
+         * @see android.media.tv.TvRecordingClient#stopRecording()
          */
         @CallSuper
         public void requestStopRecording(@NonNull String recordingId) {
@@ -976,8 +979,6 @@
             });
         }
 
-
-
         /**
          * Requests signing of the given data.
          *
diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppView.java b/media/java/android/media/tv/interactive/TvInteractiveAppView.java
index fcd781b..1177688 100755
--- a/media/java/android/media/tv/interactive/TvInteractiveAppView.java
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppView.java
@@ -585,6 +585,7 @@
      *
      * @param recordingId The ID of the recording started. This ID is created and maintained by the
      *                    TV app and is used to identify the recording in the future.
+     * @see TvInteractiveAppView#notifyRecordingStopped(String)
      */
     public void notifyRecordingStarted(@NonNull String recordingId) {
         if (DEBUG) {
@@ -601,7 +602,6 @@
      * @param recordingId The ID of the recording stopped. This ID is created and maintained
      *                    by the TV app when a recording is started.
      * @see TvInteractiveAppView#notifyRecordingStarted(String)
-     * @hide
      */
     public void notifyRecordingStopped(@NonNull String recordingId) {
         if (DEBUG) {
@@ -877,7 +877,8 @@
          * is called.
          *
          * @param iAppServiceId The ID of the TV interactive app service bound to this view.
-         * @param programUri The program URI to record
+         * @param programUri The URI of the program to record
+         *
          */
         public void onRequestStartRecording(
                 @NonNull String iAppServiceId,
@@ -885,12 +886,14 @@
         }
 
         /**
-         * This is called when {@link TvInteractiveAppService.Session#requestStopRecording()}
+         * This is called when {@link TvInteractiveAppService.Session#requestStopRecording(String)}
          * is called.
          *
          * @param iAppServiceId The ID of the TV interactive app service bound to this view.
-         * @param recordingId The ID of the recording to stop.
-         * @hide
+         * @param recordingId The ID of the recording to stop. This is provided by the TV app in
+         *                    {@link #notifyRecordingStarted(String)}
+         * @see #notifyRecordingStarted(String)
+         * @see #notifyRecordingStopped(String)
          */
         public void onRequestStopRecording(
                 @NonNull String iAppServiceId,
diff --git a/packages/CompanionDeviceManager/res/drawable-night/ic_permission_microphone.xml b/packages/CompanionDeviceManager/res/drawable-night/ic_permission_microphone.xml
new file mode 100644
index 0000000..161e4e6e
--- /dev/null
+++ b/packages/CompanionDeviceManager/res/drawable-night/ic_permission_microphone.xml
@@ -0,0 +1,26 @@
+<!--
+  ~ 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.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24"
+    android:tint="@android:color/system_accent1_200">
+  <path
+      android:fillColor="@android:color/white"
+      android:pathData="M12,14Q10.75,14 9.875,13.125Q9,12.25 9,11V5Q9,3.75 9.875,2.875Q10.75,2 12,2Q13.25,2 14.125,2.875Q15,3.75 15,5V11Q15,12.25 14.125,13.125Q13.25,14 12,14ZM12,8Q12,8 12,8Q12,8 12,8Q12,8 12,8Q12,8 12,8Q12,8 12,8Q12,8 12,8Q12,8 12,8Q12,8 12,8ZM11,21V17.925Q8.4,17.575 6.7,15.6Q5,13.625 5,11H7Q7,13.075 8.463,14.537Q9.925,16 12,16Q14.075,16 15.538,14.537Q17,13.075 17,11H19Q19,13.625 17.3,15.6Q15.6,17.575 13,17.925V21ZM12,12Q12.425,12 12.713,11.712Q13,11.425 13,11V5Q13,4.575 12.713,4.287Q12.425,4 12,4Q11.575,4 11.288,4.287Q11,4.575 11,5V11Q11,11.425 11.288,11.712Q11.575,12 12,12Z"/>
+</vector>
diff --git a/packages/CompanionDeviceManager/res/drawable/ic_permission_microphone.xml b/packages/CompanionDeviceManager/res/drawable/ic_permission_microphone.xml
new file mode 100644
index 0000000..eca625d
--- /dev/null
+++ b/packages/CompanionDeviceManager/res/drawable/ic_permission_microphone.xml
@@ -0,0 +1,24 @@
+<!--
+  ~ 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.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24"
+    android:tint="@android:color/system_accent1_600">
+  <path android:fillColor="@android:color/white" android:pathData="M12,14Q10.75,14 9.875,13.125Q9,12.25 9,11V5Q9,3.75 9.875,2.875Q10.75,2 12,2Q13.25,2 14.125,2.875Q15,3.75 15,5V11Q15,12.25 14.125,13.125Q13.25,14 12,14ZM12,8Q12,8 12,8Q12,8 12,8Q12,8 12,8Q12,8 12,8Q12,8 12,8Q12,8 12,8Q12,8 12,8Q12,8 12,8ZM11,21V17.925Q8.4,17.575 6.7,15.6Q5,13.625 5,11H7Q7,13.075 8.463,14.537Q9.925,16 12,16Q14.075,16 15.538,14.537Q17,13.075 17,11H19Q19,13.625 17.3,15.6Q15.6,17.575 13,17.925V21ZM12,12Q12.425,12 12.713,11.712Q13,11.425 13,11V5Q13,4.575 12.713,4.287Q12.425,4 12,4Q11.575,4 11.288,4.287Q11,4.575 11,5V11Q11,11.425 11.288,11.712Q11.575,12 12,12Z"/>
+</vector>
diff --git a/packages/CompanionDeviceManager/res/values/strings.xml b/packages/CompanionDeviceManager/res/values/strings.xml
index ecc5f81..3b21541 100644
--- a/packages/CompanionDeviceManager/res/values/strings.xml
+++ b/packages/CompanionDeviceManager/res/values/strings.xml
@@ -140,6 +140,9 @@
     <!-- Calendar permission will be granted of corresponding profile [CHAR LIMIT=30] -->
     <string name="permission_calendar">Calendar</string>
 
+    <!-- Microphone permission will be granted to corresponding profile [CHAR LIMIT=30] -->
+    <string name="permission_microphone">Microphone</string>
+
     <!-- Nearby devices permission will be granted of corresponding profile [CHAR LIMIT=30] -->
     <string name="permission_nearby_devices">Nearby devices</string>
 
@@ -169,6 +172,10 @@
     <!-- TODO(b/253644212) Need the description for calendar permission  -->
     <string name="permission_calendar_summary"></string>
 
+    <!-- Description of microphone permission of corresponding profile [CHAR LIMIT=NONE] -->
+    <!-- TODO(b/256140614) Need the description for microphone permission  -->
+    <string name="permission_microphone_summary">Can record audio using the microphone</string>
+
     <!-- Description of nearby devices' permission of corresponding profile [CHAR LIMIT=NONE] -->
     <!-- TODO(b/253644212) Need the description for nearby devices' permission  -->
     <string name="permission_nearby_devices_summary"></string>
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
index c249f55..49a63345 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
@@ -36,6 +36,7 @@
 import static com.android.companiondevicemanager.PermissionListAdapter.PERMISSION_APP_STREAMING;
 import static com.android.companiondevicemanager.PermissionListAdapter.PERMISSION_CALENDAR;
 import static com.android.companiondevicemanager.PermissionListAdapter.PERMISSION_CONTACTS;
+import static com.android.companiondevicemanager.PermissionListAdapter.PERMISSION_MICROPHONE;
 import static com.android.companiondevicemanager.PermissionListAdapter.PERMISSION_NEARBY_DEVICES;
 import static com.android.companiondevicemanager.PermissionListAdapter.PERMISSION_NEARBY_DEVICE_STREAMING;
 import static com.android.companiondevicemanager.PermissionListAdapter.PERMISSION_NOTIFICATION;
@@ -596,10 +597,9 @@
                     this, R.string.summary_glasses_single_device, profileName, appLabel);
             profileIcon = getIcon(this, R.drawable.ic_glasses);
 
-            // TODO (b/256140614): add PERMISSION_MICROPHONE
             mPermissionTypes.addAll(Arrays.asList(
                     PERMISSION_PHONE, PERMISSION_SMS, PERMISSION_CONTACTS,
-                    PERMISSION_NEARBY_DEVICES));
+                    PERMISSION_MICROPHONE, PERMISSION_NEARBY_DEVICES));
 
             setupPermissionList();
         } else {
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/PermissionListAdapter.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/PermissionListAdapter.java
index 90b94fb..00c44d6 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/PermissionListAdapter.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/PermissionListAdapter.java
@@ -51,6 +51,7 @@
     static final int PERMISSION_CALENDAR = 6;
     static final int PERMISSION_NEARBY_DEVICES = 7;
     static final int PERMISSION_NEARBY_DEVICE_STREAMING = 8;
+    static final int PERMISSION_MICROPHONE = 9;
 
     private static final Map<Integer, Integer> sTitleMap;
     static {
@@ -64,6 +65,7 @@
         map.put(PERMISSION_CALENDAR, R.string.permission_calendar);
         map.put(PERMISSION_NEARBY_DEVICES, R.string.permission_nearby_devices);
         map.put(PERMISSION_NEARBY_DEVICE_STREAMING, R.string.permission_nearby_device_streaming);
+        map.put(PERMISSION_MICROPHONE, R.string.permission_microphone);
         sTitleMap = unmodifiableMap(map);
     }
 
@@ -80,6 +82,7 @@
         map.put(PERMISSION_NEARBY_DEVICES, R.string.permission_nearby_devices_summary);
         map.put(PERMISSION_NEARBY_DEVICE_STREAMING,
                 R.string.permission_nearby_device_streaming_summary);
+        map.put(PERMISSION_MICROPHONE, R.string.permission_microphone_summary);
         sSummaryMap = unmodifiableMap(map);
     }
 
@@ -96,6 +99,7 @@
         map.put(PERMISSION_NEARBY_DEVICES, R.drawable.ic_permission_nearby_devices);
         map.put(PERMISSION_NEARBY_DEVICE_STREAMING,
                 R.drawable.ic_permission_nearby_device_streaming);
+        map.put(PERMISSION_MICROPHONE, R.drawable.ic_permission_microphone);
         sIconMap = unmodifiableMap(map);
     }
 
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
index 4faf00c..d2b2924 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
@@ -41,8 +41,6 @@
 import android.os.ResultReceiver
 import android.service.credentials.CredentialProviderService
 import com.android.credentialmanager.createflow.CreateCredentialUiState
-import com.android.credentialmanager.createflow.EnabledProviderInfo
-import com.android.credentialmanager.createflow.RemoteInfo
 import com.android.credentialmanager.getflow.GetCredentialUiState
 import com.android.credentialmanager.jetpack.developer.CreatePasswordRequest.Companion.toBundle
 import com.android.credentialmanager.jetpack.developer.CreatePublicKeyCredentialRequest
@@ -63,7 +61,7 @@
     requestInfo = intent.extras?.getParcelable(
       RequestInfo.EXTRA_REQUEST_INFO,
       RequestInfo::class.java
-    ) ?: testGetRequestInfo()
+    ) ?: testCreatePasskeyRequestInfo()
 
     providerEnabledList = when (requestInfo.type) {
       RequestInfo.TYPE_CREATE ->
@@ -101,7 +99,7 @@
   }
 
   fun onOptionSelected(
-    providerPackageName: String,
+    providerId: String,
     entryKey: String,
     entrySubkey: String,
     resultCode: Int? = null,
@@ -109,7 +107,7 @@
   ) {
     val userSelectionDialogResult = UserSelectionDialogResult(
       requestInfo.token,
-      providerPackageName,
+      providerId,
       entryKey,
       entrySubkey,
       if (resultCode != null) ProviderPendingIntentResponse(resultCode, resultData) else null
@@ -138,36 +136,15 @@
     val providerDisabledList = CreateFlowUtils.toDisabledProviderList(
       // Handle runtime cast error
       providerDisabledList, context)
-    var defaultProvider: EnabledProviderInfo? = null
-    var remoteEntry: RemoteInfo? = null
-    var createOptionSize = 0
-    var lastSeenProviderWithNonEmptyCreateOptions: EnabledProviderInfo? = null
     providerEnabledList.forEach{providerInfo -> providerInfo.createOptions =
       providerInfo.createOptions.sortedWith(compareBy { it.lastUsedTimeMillis }).reversed()
-      if (providerInfo.isDefault) {defaultProvider = providerInfo}
-      if (providerInfo.remoteEntry != null) {
-        remoteEntry = providerInfo.remoteEntry!!
-      }
-      if (providerInfo.createOptions.isNotEmpty()) {
-        createOptionSize += providerInfo.createOptions.size
-        lastSeenProviderWithNonEmptyCreateOptions = providerInfo
-      }
     }
-    return CreateCredentialUiState(
-      enabledProviders = providerEnabledList,
-      disabledProviders = providerDisabledList,
-      CreateFlowUtils.toCreateScreenState(
-        createOptionSize, false,
-        requestDisplayInfo, defaultProvider, remoteEntry),
-      requestDisplayInfo,
-      false,
-      CreateFlowUtils.toActiveEntry(
-        /*defaultProvider=*/defaultProvider, createOptionSize,
-        lastSeenProviderWithNonEmptyCreateOptions, remoteEntry),
-    )
+    return CreateFlowUtils.toCreateCredentialUiState(
+      providerEnabledList, providerDisabledList, requestDisplayInfo, false)
   }
 
   companion object {
+    // TODO: find a way to resolve this static field leak problem
     lateinit var repo: CredentialManagerRepo
 
     fun setup(
@@ -198,7 +175,6 @@
         .setRemoteEntry(
           newRemoteEntry("key2", "subkey-1")
         )
-        .setIsDefaultProvider(true)
         .build(),
       CreateCredentialProviderData
         .Builder("com.dashlane")
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt
index 6a4c599..cdff2d4 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt
@@ -43,6 +43,7 @@
   override fun onCreate(savedInstanceState: Bundle?) {
     super.onCreate(savedInstanceState)
     CredentialManagerRepo.setup(this, intent)
+    UserConfigRepo.setup(this)
     val requestInfo = CredentialManagerRepo.getInstance().requestInfo
     setContent {
       CredentialSelectorTheme {
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
index 56fbf66..db676b2 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
@@ -31,6 +31,8 @@
 import com.android.credentialmanager.createflow.EnabledProviderInfo
 import com.android.credentialmanager.createflow.CreateScreenState
 import com.android.credentialmanager.createflow.ActiveEntry
+import com.android.credentialmanager.createflow.DisabledProviderInfo
+import com.android.credentialmanager.createflow.CreateCredentialUiState
 import com.android.credentialmanager.getflow.ActionEntryInfo
 import com.android.credentialmanager.getflow.AuthenticationEntryInfo
 import com.android.credentialmanager.getflow.CredentialEntryInfo
@@ -208,14 +210,13 @@
         val pkgInfo = packageManager
           .getPackageInfo(packageName!!,
             PackageManager.PackageInfoFlags.of(0))
-        com.android.credentialmanager.createflow.EnabledProviderInfo(
+        EnabledProviderInfo(
           // TODO: decide what to do when failed to load a provider icon
           icon = pkgInfo.applicationInfo.loadIcon(packageManager)!!,
           name = it.providerFlattenedComponentName,
           displayName = pkgInfo.applicationInfo.loadLabel(packageManager).toString(),
           createOptions = toCreationOptionInfoList(
             it.providerFlattenedComponentName, it.saveEntries, requestDisplayInfo, context),
-          isDefault = it.isDefaultProvider,
           remoteEntry = toRemoteInfo(it.providerFlattenedComponentName, it.remoteEntry),
         )
       }
@@ -256,8 +257,7 @@
             createCredentialRequestJetpack.password,
             createCredentialRequestJetpack.type,
             requestInfo.appPackageName,
-            context.getDrawable(R.drawable.ic_password)!!,
-            requestInfo.isFirstUsage
+            context.getDrawable(R.drawable.ic_password)!!
           )
         }
         is CreatePublicKeyCredentialRequest -> {
@@ -275,8 +275,7 @@
             displayName,
             createCredentialRequestJetpack.type,
             requestInfo.appPackageName,
-            context.getDrawable(R.drawable.ic_passkey)!!,
-            requestInfo.isFirstUsage)
+            context.getDrawable(R.drawable.ic_passkey)!!)
         }
         // TODO: correctly parsing for other sign-ins
         else -> {
@@ -285,20 +284,60 @@
             "Elisa Beckett",
             "other-sign-ins",
             requestInfo.appPackageName,
-            context.getDrawable(R.drawable.ic_other_sign_in)!!,
-            requestInfo.isFirstUsage)
+            context.getDrawable(R.drawable.ic_other_sign_in)!!)
         }
       }
     }
 
-    fun toCreateScreenState(
+    fun toCreateCredentialUiState(
+      enabledProviders: List<EnabledProviderInfo>,
+      disabledProviders: List<DisabledProviderInfo>?,
+      requestDisplayInfo: RequestDisplayInfo,
+      isOnPasskeyIntroStateAlready: Boolean,
+    ): CreateCredentialUiState {
+      var createOptionSize = 0
+      var lastSeenProviderWithNonEmptyCreateOptions: EnabledProviderInfo? = null
+      var remoteEntry: RemoteInfo? = null
+      var defaultProvider: EnabledProviderInfo? = null
+      val defaultProviderId = UserConfigRepo.getInstance().getDefaultProviderId()
+      enabledProviders.forEach {
+          enabledProvider ->
+        if (defaultProviderId != null) {
+          if (enabledProvider.name == defaultProviderId) {
+            defaultProvider = enabledProvider
+          }
+        }
+        if (enabledProvider.createOptions.isNotEmpty()) {
+          createOptionSize += enabledProvider.createOptions.size
+          lastSeenProviderWithNonEmptyCreateOptions = enabledProvider
+        }
+        if (enabledProvider.remoteEntry != null) {
+          remoteEntry = enabledProvider.remoteEntry!!
+        }
+      }
+      return CreateCredentialUiState(
+        enabledProviders = enabledProviders,
+        disabledProviders = disabledProviders,
+        toCreateScreenState(
+          createOptionSize, isOnPasskeyIntroStateAlready,
+          requestDisplayInfo, defaultProvider, remoteEntry),
+        requestDisplayInfo,
+        isOnPasskeyIntroStateAlready,
+        toActiveEntry(
+          /*defaultProvider=*/defaultProvider, createOptionSize,
+          lastSeenProviderWithNonEmptyCreateOptions, remoteEntry),
+      )
+    }
+
+    private fun toCreateScreenState(
       createOptionSize: Int,
       isOnPasskeyIntroStateAlready: Boolean,
       requestDisplayInfo: RequestDisplayInfo,
       defaultProvider: EnabledProviderInfo?,
       remoteEntry: RemoteInfo?,
     ): CreateScreenState {
-      return if (requestDisplayInfo.isFirstUsage && requestDisplayInfo
+      return if (
+        UserConfigRepo.getInstance().getIsFirstUse() && requestDisplayInfo
           .type == TYPE_PUBLIC_KEY_CREDENTIAL && !isOnPasskeyIntroStateAlready) {
         CreateScreenState.PASSKEY_INTRO
       } else if (
@@ -313,12 +352,12 @@
       } else if (createOptionSize == 0 && remoteEntry != null) {
         CreateScreenState.EXTERNAL_ONLY_SELECTION
       } else {
-          // TODO: properly handle error and gracefully finish itself
-          throw java.lang.IllegalStateException("Empty provider list.")
+        // TODO: properly handle error and gracefully finish itself
+        throw java.lang.IllegalStateException("Empty provider list.")
       }
     }
 
-   fun toActiveEntry(
+    private fun toActiveEntry(
       defaultProvider: EnabledProviderInfo?,
       createOptionSize: Int,
       lastSeenProviderWithNonEmptyCreateOptions: EnabledProviderInfo?,
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/UserConfigRepo.kt b/packages/CredentialManager/src/com/android/credentialmanager/UserConfigRepo.kt
new file mode 100644
index 0000000..5e77663
--- /dev/null
+++ b/packages/CredentialManager/src/com/android/credentialmanager/UserConfigRepo.kt
@@ -0,0 +1,70 @@
+/*
+ * 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.credentialmanager
+
+import android.content.Context
+import android.content.SharedPreferences
+
+class UserConfigRepo(context: Context) {
+    val sharedPreferences: SharedPreferences = context.getSharedPreferences(
+        context.packageName, Context.MODE_PRIVATE)
+
+    fun setDefaultProvider(
+        providerId: String
+    ) {
+        sharedPreferences.edit().apply {
+            putString(DEFAULT_PROVIDER, providerId)
+            apply()
+        }
+    }
+
+    fun setIsFirstUse(
+        isFirstUse: Boolean
+    ) {
+        sharedPreferences.edit().apply {
+            putBoolean(IS_PASSKEY_FIRST_USE, isFirstUse)
+            apply()
+        }
+    }
+
+    fun getDefaultProviderId(): String? {
+        return sharedPreferences.getString(DEFAULT_PROVIDER, null)
+    }
+
+    fun getIsFirstUse(): Boolean {
+        return sharedPreferences.getBoolean(IS_PASSKEY_FIRST_USE, true)
+    }
+
+    companion object {
+        lateinit var repo: UserConfigRepo
+
+        const val DEFAULT_PROVIDER = "default_provider"
+        // This first use value only applies to passkeys, not related with if generally
+        // credential manager is first use or not
+        const val IS_PASSKEY_FIRST_USE = "is_passkey_first_use"
+
+        fun setup(
+            context: Context,
+        ) {
+            repo = UserConfigRepo(context)
+        }
+
+        fun getInstance(): UserConfigRepo {
+            return repo
+        }
+    }
+}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
index c26fac8..38e2caa 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
@@ -108,7 +108,8 @@
                     )
                     CreateScreenState.MORE_OPTIONS_ROW_INTRO -> MoreOptionsRowIntroCard(
                         providerInfo = uiState.activeEntry?.activeProvider!!,
-                        onDefaultOrNotSelected = viewModel::onDefaultOrNotSelected
+                        onChangeDefaultSelected = viewModel::onChangeDefaultSelected,
+                        onUseOnceSelected = viewModel::onUseOnceSelected,
                     )
                     CreateScreenState.EXTERNAL_ONLY_SELECTION -> ExternalOnlySelectionCard(
                         requestDisplayInfo = uiState.requestDisplayInfo,
@@ -464,7 +465,8 @@
 @Composable
 fun MoreOptionsRowIntroCard(
     providerInfo: EnabledProviderInfo,
-    onDefaultOrNotSelected: () -> Unit,
+    onChangeDefaultSelected: () -> Unit,
+    onUseOnceSelected: () -> Unit,
 ) {
     ContainerCard() {
         Column() {
@@ -496,11 +498,11 @@
             ) {
                 CancelButton(
                     stringResource(R.string.use_once),
-                    onClick = onDefaultOrNotSelected
+                    onClick = onUseOnceSelected
                 )
                 ConfirmButton(
                     stringResource(R.string.set_as_default),
-                    onClick = onDefaultOrNotSelected
+                    onClick = onChangeDefaultSelected
                 )
             }
             Divider(
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialViewModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialViewModel.kt
index 518aaee..9d029dff 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialViewModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialViewModel.kt
@@ -29,6 +29,7 @@
 import androidx.lifecycle.ViewModel
 import com.android.credentialmanager.CreateFlowUtils
 import com.android.credentialmanager.CredentialManagerRepo
+import com.android.credentialmanager.UserConfigRepo
 import com.android.credentialmanager.common.DialogResult
 import com.android.credentialmanager.common.ProviderActivityResult
 import com.android.credentialmanager.common.ResultState
@@ -61,32 +62,15 @@
   }
 
   fun onConfirmIntro() {
-    var createOptionSize = 0
-    var lastSeenProviderWithNonEmptyCreateOptions: EnabledProviderInfo? = null
-    var remoteEntry: RemoteInfo? = null
-    uiState.enabledProviders.forEach {
-      enabledProvider ->
-      if (enabledProvider.createOptions.isNotEmpty()) {
-        createOptionSize += enabledProvider.createOptions.size
-        lastSeenProviderWithNonEmptyCreateOptions = enabledProvider
-      }
-      if (enabledProvider.remoteEntry != null) {
-        remoteEntry = enabledProvider.remoteEntry!!
-      }
-    }
-    uiState = uiState.copy(
-      currentScreenState = CreateFlowUtils.toCreateScreenState(
-        createOptionSize, true,
-        uiState.requestDisplayInfo, null, remoteEntry),
-      showActiveEntryOnly = createOptionSize > 1,
-      activeEntry = CreateFlowUtils.toActiveEntry(
-        null, createOptionSize, lastSeenProviderWithNonEmptyCreateOptions, remoteEntry),
-    )
+    uiState = CreateFlowUtils.toCreateCredentialUiState(
+      uiState.enabledProviders, uiState.disabledProviders,
+      uiState.requestDisplayInfo, true)
+    UserConfigRepo.getInstance().setIsFirstUse(false)
   }
 
   fun getProviderInfoByName(providerName: String): EnabledProviderInfo {
     return uiState.enabledProviders.single {
-      it.name.equals(providerName)
+      it.name == providerName
     }
   }
 
@@ -116,6 +100,8 @@
       showActiveEntryOnly = true,
       activeEntry = activeEntry
     )
+    val providerId = uiState.activeEntry?.activeProvider?.name
+    onDefaultChanged(providerId)
   }
 
   fun onDisabledPasswordManagerSelected() {
@@ -127,11 +113,29 @@
     dialogResult.value = DialogResult(ResultState.CANCELED)
   }
 
-  fun onDefaultOrNotSelected() {
+  fun onChangeDefaultSelected() {
     uiState = uiState.copy(
       currentScreenState = CreateScreenState.CREATION_OPTION_SELECTION,
     )
-    // TODO: implement the if choose as default or not logic later
+    val providerId = uiState.activeEntry?.activeProvider?.name
+    onDefaultChanged(providerId)
+  }
+
+  fun onUseOnceSelected() {
+    uiState = uiState.copy(
+      currentScreenState = CreateScreenState.CREATION_OPTION_SELECTION,
+    )
+  }
+
+  fun onDefaultChanged(providerId: String?) {
+    if (providerId != null) {
+      Log.d(
+        "Account Selector", "Default provider changed to: " +
+                " {provider=$providerId")
+      UserConfigRepo.getInstance().setDefaultProvider(providerId)
+    } else {
+      Log.w("Account Selector", "Null provider is being changed")
+    }
   }
 
   fun onEntrySelected(selectedEntry: EntryInfo) {
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
index 21abe08..fda0b97 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
@@ -31,7 +31,6 @@
   name: String,
   displayName: String,
   var createOptions: List<CreateOptionInfo>,
-  val isDefault: Boolean,
   var remoteEntry: RemoteInfo?,
 ) : ProviderInfo(icon, name, displayName)
 
@@ -77,7 +76,6 @@
   val type: String,
   val appDomainName: String,
   val typeIcon: Drawable,
-  val isFirstUsage: Boolean,
 )
 
 /**
diff --git a/packages/PackageInstaller/res/values/strings.xml b/packages/PackageInstaller/res/values/strings.xml
index 6a3b239..b713c14 100644
--- a/packages/PackageInstaller/res/values/strings.xml
+++ b/packages/PackageInstaller/res/values/strings.xml
@@ -119,6 +119,10 @@
     <string name="uninstall_update_text_multiuser">Replace this app with the factory version? All data will be removed. This affects all users of this device, including those with work profiles.</string>
     <!-- Label of a checkbox that allows to keep the data (e.g. files, settings) of the app on uninstall [CHAR LIMIT=none] -->
     <string name="uninstall_keep_data">Keep <xliff:g id="size" example="1.5MB">%1$s</xliff:g> of app data.</string>
+    <!--  [CHAR LIMIT=none] -->
+    <string name="uninstall_application_text_current_user_clone_profile">Do you want to delete this app?</string>
+    <!--  [CHAR LIMIT=none] -->
+    <string name="uninstall_application_text_with_clone_instance">Do you want to uninstall this app? <xliff:g id="package_label">%1$s</xliff:g> clone will also be deleted.</string>
 
     <!-- Label for the notification channel containing notifications for current uninstall operations [CHAR LIMIT=40] -->
     <string name="uninstalling_notification_channel">Running uninstalls</string>
@@ -137,6 +141,8 @@
     <string name="uninstall_failed">Uninstall unsuccessful.</string>
     <!-- [CHAR LIMIT=100] -->
     <string name="uninstall_failed_app">Uninstalling <xliff:g id="package_label">%1$s</xliff:g> unsuccessful.</string>
+    <!-- [CHAR LIMIT=100] -->
+    <string name="uninstalling_cloned_app">Deleting <xliff:g id="package_label">%1$s</xliff:g> clone\u2026</string>
     <!-- String presented to the user when uninstalling a package failed because the target package
         is a current device administrator [CHAR LIMIT=80] -->
     <string name="uninstall_failed_device_policy_manager">Can\'t uninstall active device admin
@@ -219,6 +225,9 @@
         TV or loss of data that may result from its use.
     </string>
 
+    <!-- Label for cloned app in uninstall dialogue [CHAR LIMIT=40] -->
+    <string name="cloned_app_label"><xliff:g id="package_label">%1$s</xliff:g> Clone</string>
+
     <!-- Label for button to continue install of an app whose source cannot be identified [CHAR LIMIT=40] -->
     <string name="anonymous_source_continue">Continue</string>
 
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/UninstallUninstalling.java b/packages/PackageInstaller/src/com/android/packageinstaller/UninstallUninstalling.java
index 7bf27df..1485352 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/UninstallUninstalling.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/UninstallUninstalling.java
@@ -33,8 +33,10 @@
 import android.content.pm.VersionedPackage;
 import android.os.Bundle;
 import android.os.IBinder;
+import android.os.Process;
 import android.os.RemoteException;
 import android.os.UserHandle;
+import android.os.UserManager;
 import android.util.Log;
 import android.widget.Toast;
 
@@ -51,6 +53,7 @@
 
     static final String EXTRA_APP_LABEL = "com.android.packageinstaller.extra.APP_LABEL";
     static final String EXTRA_KEEP_DATA = "com.android.packageinstaller.extra.KEEP_DATA";
+    public static final String EXTRA_IS_CLONE_USER = "isCloneUser";
 
     private int mUninstallId;
     private ApplicationInfo mAppInfo;
@@ -76,6 +79,18 @@
                 boolean keepData = getIntent().getBooleanExtra(EXTRA_KEEP_DATA, false);
                 UserHandle user = getIntent().getParcelableExtra(Intent.EXTRA_USER);
 
+                boolean isCloneUser = false;
+                if (user == null) {
+                    user = Process.myUserHandle();
+                }
+
+                UserManager customUserManager = UninstallUninstalling.this
+                        .createContextAsUser(UserHandle.of(user.getIdentifier()), 0)
+                        .getSystemService(UserManager.class);
+                if (customUserManager.isUserOfType(UserManager.USER_TYPE_PROFILE_CLONE)) {
+                    isCloneUser = true;
+                }
+
                 // Show dialog, which is the whole UI
                 FragmentTransaction transaction = getFragmentManager().beginTransaction();
                 Fragment prev = getFragmentManager().findFragmentByTag("dialog");
@@ -83,6 +98,9 @@
                     transaction.remove(prev);
                 }
                 DialogFragment dialog = new UninstallUninstallingFragment();
+                Bundle args = new Bundle();
+                args.putBoolean(EXTRA_IS_CLONE_USER, isCloneUser);
+                dialog.setArguments(args);
                 dialog.setCancelable(false);
                 dialog.show(transaction, "dialog");
 
@@ -176,9 +194,20 @@
         public Dialog onCreateDialog(Bundle savedInstanceState) {
             AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(getActivity());
 
+            Bundle bundle = getArguments();
+            boolean isCloneUser = false;
+            if (bundle != null) {
+                isCloneUser = bundle.getBoolean(EXTRA_IS_CLONE_USER);
+            }
+
             dialogBuilder.setCancelable(false);
-            dialogBuilder.setMessage(getActivity().getString(R.string.uninstalling_app,
-                    ((UninstallUninstalling) getActivity()).mLabel));
+            if (isCloneUser) {
+                dialogBuilder.setMessage(getActivity().getString(R.string.uninstalling_cloned_app,
+                        ((UninstallUninstalling) getActivity()).mLabel));
+            } else {
+                dialogBuilder.setMessage(getActivity().getString(R.string.uninstalling_app,
+                        ((UninstallUninstalling) getActivity()).mLabel));
+            }
 
             Dialog dialog = dialogBuilder.create();
             dialog.setCanceledOnTouchOutside(false);
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java b/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java
index c9230b4..a1bc992 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java
@@ -123,6 +123,7 @@
                 messageBuilder.append(" ").append(appLabel).append(".\n\n");
             }
         }
+        boolean isClonedApp = false;
 
         final boolean isUpdate =
                 ((dialogInfo.appInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0);
@@ -144,16 +145,36 @@
                     messageBuilder.append(
                             getString(R.string.uninstall_application_text_current_user_work_profile,
                                     userInfo.name));
+                } else if (userInfo.isCloneProfile()
+                        && userInfo.profileGroupId == myUserHandle.getIdentifier()) {
+                    isClonedApp = true;
+                    messageBuilder.append(getString(
+                            R.string.uninstall_application_text_current_user_clone_profile));
                 } else {
                     messageBuilder.append(
                             getString(R.string.uninstall_application_text_user, userInfo.name));
                 }
+            } else if (isCloneProfile(myUserHandle)) {
+                isClonedApp = true;
+                messageBuilder.append(getString(
+                        R.string.uninstall_application_text_current_user_clone_profile));
             } else {
-                messageBuilder.append(getString(R.string.uninstall_application_text));
+                if (Process.myUserHandle().equals(UserHandle.SYSTEM)
+                        && hasClonedInstance(dialogInfo.appInfo.packageName)) {
+                    messageBuilder.append(getString(
+                            R.string.uninstall_application_text_with_clone_instance,
+                            appLabel));
+                } else {
+                    messageBuilder.append(getString(R.string.uninstall_application_text));
+                }
             }
         }
 
-        dialogBuilder.setTitle(appLabel);
+        if (isClonedApp) {
+            dialogBuilder.setTitle(getString(R.string.cloned_app_label, appLabel));
+        } else {
+            dialogBuilder.setTitle(appLabel);
+        }
         dialogBuilder.setPositiveButton(android.R.string.ok, this);
         dialogBuilder.setNegativeButton(android.R.string.cancel, this);
 
@@ -192,6 +213,42 @@
         return dialogBuilder.create();
     }
 
+    private boolean isCloneProfile(UserHandle userHandle) {
+        UserManager customUserManager = getContext()
+                .createContextAsUser(UserHandle.of(userHandle.getIdentifier()), 0)
+                .getSystemService(UserManager.class);
+        if (customUserManager.isUserOfType(UserManager.USER_TYPE_PROFILE_CLONE)) {
+            return true;
+        }
+        return false;
+    }
+
+    private boolean hasClonedInstance(String packageName) {
+        // Check if clone user is present on the device.
+        UserHandle cloneUser = null;
+        UserManager userManager = getContext().getSystemService(UserManager.class);
+        List<UserHandle> profiles = userManager.getUserProfiles();
+        for (UserHandle userHandle : profiles) {
+            if (!Process.myUserHandle().equals(UserHandle.SYSTEM) && isCloneProfile(userHandle)) {
+                cloneUser = userHandle;
+                break;
+            }
+        }
+
+        // Check if another instance of given package exists in clone user profile.
+        if (cloneUser != null) {
+            try {
+                if (getContext().getPackageManager()
+                        .getPackageUidAsUser(packageName, cloneUser.getIdentifier()) > 0) {
+                    return true;
+                }
+            } catch (PackageManager.NameNotFoundException e) {
+                return false;
+            }
+        }
+        return false;
+    }
+
     @Override
     public void onClick(DialogInterface dialog, int which) {
         if (which == Dialog.BUTTON_POSITIVE) {
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt
index 5ae5ada..62db7bd 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt
@@ -93,7 +93,7 @@
         ) {
             val context = LocalContext.current
             val internalListModel = remember {
-                TogglePermissionInternalAppListModel(context, listModel)
+                TogglePermissionInternalAppListModel(context, listModel, ::RestrictionsProviderImpl)
             }
             val record = remember { listModel.transformItem(app) }
             if (!remember { listModel.isChangeable(record) }) return
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt
index 00eb607..f65e310 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt
@@ -42,6 +42,7 @@
 import com.android.settingslib.spaprivileged.model.app.AppRecord
 import com.android.settingslib.spaprivileged.model.app.userId
 import com.android.settingslib.spaprivileged.model.enterprise.Restrictions
+import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProviderFactory
 import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProviderImpl
 import com.android.settingslib.spaprivileged.template.preference.RestrictedSwitchPreference
 import kotlinx.coroutines.flow.Flow
@@ -64,41 +65,18 @@
         val permissionType = parameter.getStringArg(PERMISSION, arguments)!!
         val appListPage = SettingsPage.create(name, parameter = parameter, arguments = arguments)
         val appInfoPage = TogglePermissionAppInfoPageProvider.buildPageData(permissionType)
-        val entryList = mutableListOf<SettingsEntry>()
         // TODO: add more categories, such as personal, work, cloned, etc.
-        for (category in listOf("personal")) {
-            entryList.add(
-                SettingsEntryBuilder.createLinkFrom("${ENTRY_NAME}_$category", appListPage)
-                    .setLink(toPage = appInfoPage)
-                    .build()
-            )
+        return listOf("personal").map { category ->
+            SettingsEntryBuilder.createLinkFrom("${ENTRY_NAME}_$category", appListPage)
+                .setLink(toPage = appInfoPage)
+                .build()
         }
-        return entryList
     }
 
     @Composable
     override fun Page(arguments: Bundle?) {
-        TogglePermissionAppList(arguments?.getString(PERMISSION)!!)
-    }
-
-    @Composable
-    private fun TogglePermissionAppList(permissionType: String) {
-        val listModel = appListTemplate.rememberModel(permissionType)
-        val context = LocalContext.current
-        val internalListModel = remember {
-            TogglePermissionInternalAppListModel(context, listModel)
-        }
-        AppListPage(
-            title = stringResource(listModel.pageTitleResId),
-            listModel = internalListModel,
-        ) {
-            AppListItem(
-                onClick = TogglePermissionAppInfoPageProvider.navigator(
-                    permissionType = permissionType,
-                    app = record.app,
-                ),
-            )
-        }
+        val permissionType = arguments?.getString(PERMISSION)!!
+        appListTemplate.rememberModel(permissionType).TogglePermissionAppList(permissionType)
     }
 
     companion object {
@@ -132,9 +110,34 @@
     }
 }
 
+@Composable
+internal fun <T : AppRecord> TogglePermissionAppListModel<T>.TogglePermissionAppList(
+    permissionType: String,
+    restrictionsProviderFactory: RestrictionsProviderFactory = ::RestrictionsProviderImpl,
+    appList: @Composable AppListInput<T>.() -> Unit = { AppList() },
+) {
+    val context = LocalContext.current
+    val internalListModel = remember {
+        TogglePermissionInternalAppListModel(context, this, restrictionsProviderFactory)
+    }
+    AppListPage(
+        title = stringResource(pageTitleResId),
+        listModel = internalListModel,
+        appList = appList,
+    ) {
+        AppListItem(
+            onClick = TogglePermissionAppInfoPageProvider.navigator(
+                permissionType = permissionType,
+                app = record.app,
+            ),
+        )
+    }
+}
+
 internal class TogglePermissionInternalAppListModel<T : AppRecord>(
     private val context: Context,
     private val listModel: TogglePermissionAppListModel<T>,
+    private val restrictionsProviderFactory: RestrictionsProviderFactory,
 ) : AppListModel<T> {
     override fun transform(userIdFlow: Flow<Int>, appListFlow: Flow<List<ApplicationInfo>>) =
         listModel.transform(userIdFlow, appListFlow)
@@ -147,12 +150,12 @@
 
     @Composable
     fun getSummary(record: T): State<String> {
-        val restrictionsProvider = remember {
+        val restrictionsProvider = remember(record.app.userId) {
             val restrictions = Restrictions(
                 userId = record.app.userId,
                 keys = listModel.switchRestrictionKeys,
             )
-            RestrictionsProviderImpl(context, restrictions)
+            restrictionsProviderFactory(context, restrictions)
         }
         val restrictedMode = restrictionsProvider.restrictedModeState()
         val allowed = listModel.isAllowed(record)
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/scaffold/RestrictedMenuItem.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/scaffold/RestrictedMenuItem.kt
index 8c1421a..86b6f02 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/scaffold/RestrictedMenuItem.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/scaffold/RestrictedMenuItem.kt
@@ -16,7 +16,6 @@
 
 package com.android.settingslib.spaprivileged.template.scaffold
 
-import android.content.Context
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.remember
 import androidx.compose.ui.platform.LocalContext
@@ -24,7 +23,7 @@
 import com.android.settingslib.spaprivileged.model.enterprise.BaseUserRestricted
 import com.android.settingslib.spaprivileged.model.enterprise.BlockedByAdmin
 import com.android.settingslib.spaprivileged.model.enterprise.Restrictions
-import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProvider
+import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProviderFactory
 import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProviderImpl
 
 @Composable
@@ -41,7 +40,7 @@
     text: String,
     restrictions: Restrictions,
     onClick: () -> Unit,
-    restrictionsProviderFactory: (Context, Restrictions) -> RestrictionsProvider,
+    restrictionsProviderFactory: RestrictionsProviderFactory,
 ) {
     val context = LocalContext.current
     val restrictionsProvider = remember(restrictions) {
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPageTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPageTest.kt
index 355dfb6..75b884c 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPageTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPageTest.kt
@@ -17,14 +17,22 @@
 package com.android.settingslib.spaprivileged.template.app
 
 import android.content.Context
+import android.content.pm.ApplicationInfo
 import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.State
 import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.test.assertIsDisplayed
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.test.performClick
 import androidx.test.core.app.ApplicationProvider
 import androidx.test.ext.junit.runners.AndroidJUnit4
-import com.android.settingslib.spaprivileged.test.R
+import com.android.settingslib.spa.framework.compose.stateOf
+import com.android.settingslib.spa.testutils.FakeNavControllerWrapper
+import com.android.settingslib.spaprivileged.R
+import com.android.settingslib.spaprivileged.model.enterprise.NoRestricted
+import com.android.settingslib.spaprivileged.tests.testutils.FakeRestrictionsProvider
+import com.android.settingslib.spaprivileged.tests.testutils.TestAppRecord
 import com.android.settingslib.spaprivileged.tests.testutils.TestTogglePermissionAppListModel
 import com.google.common.truth.Truth.assertThat
 import org.junit.Rule
@@ -38,10 +46,97 @@
 
     private val context: Context = ApplicationProvider.getApplicationContext()
 
+    private val fakeNavControllerWrapper = FakeNavControllerWrapper()
+
+    private val fakeRestrictionsProvider = FakeRestrictionsProvider()
+
     @Test
-    fun appListInjectEntry_titleDisplayed() {
+    fun internalAppListModel_whenAllowed() {
+        fakeRestrictionsProvider.restrictedMode = NoRestricted
+        val listModel = TestTogglePermissionAppListModel(isAllowed = true)
+        val internalAppListModel = TogglePermissionInternalAppListModel(
+            context = context,
+            listModel = listModel,
+            restrictionsProviderFactory = { _, _ -> fakeRestrictionsProvider },
+        )
+
+        val summaryState = getSummary(internalAppListModel)
+
+        assertThat(summaryState.value).isEqualTo(
+            context.getString(R.string.app_permission_summary_allowed)
+        )
+    }
+
+    @Test
+    fun internalAppListModel_whenNotAllowed() {
+        fakeRestrictionsProvider.restrictedMode = NoRestricted
+        val listModel = TestTogglePermissionAppListModel(isAllowed = false)
+        val internalAppListModel = TogglePermissionInternalAppListModel(
+            context = context,
+            listModel = listModel,
+            restrictionsProviderFactory = { _, _ -> fakeRestrictionsProvider },
+        )
+
+        val summaryState = getSummary(internalAppListModel)
+
+        assertThat(summaryState.value).isEqualTo(
+            context.getString(R.string.app_permission_summary_not_allowed)
+        )
+    }
+
+    @Test
+    fun internalAppListModel_whenComputingAllowed() {
+        fakeRestrictionsProvider.restrictedMode = NoRestricted
+        val listModel = TestTogglePermissionAppListModel(isAllowed = null)
+        val internalAppListModel = TogglePermissionInternalAppListModel(
+            context = context,
+            listModel = listModel,
+            restrictionsProviderFactory = { _, _ -> fakeRestrictionsProvider },
+        )
+
+        val summaryState = getSummary(internalAppListModel)
+
+        assertThat(summaryState.value).isEqualTo(
+            context.getString(R.string.summary_placeholder)
+        )
+    }
+
+    @Test
+    fun appListItem_onClick_navigate() {
+        val listModel = TestTogglePermissionAppListModel()
+        composeTestRule.setContent {
+            listModel.TogglePermissionAppList(
+                permissionType = PERMISSION_TYPE,
+                restrictionsProviderFactory = { _, _ -> fakeRestrictionsProvider },
+            ) {
+                fakeNavControllerWrapper.Wrapper {
+                    AppListItemModel(
+                        record = listModel.transformItem(APP),
+                        label = LABEL,
+                        summary = stateOf(SUMMARY),
+                    ).appItem()
+                }
+            }
+        }
+
+        composeTestRule.onNodeWithText(LABEL).performClick()
+
+        assertThat(fakeNavControllerWrapper.navigateCalledWith)
+            .isEqualTo("TogglePermissionAppInfoPage/test.PERMISSION/package.name/0")
+    }
+
+    @Test
+    fun getRoute() {
+        val route = TogglePermissionAppListPageProvider.getRoute(PERMISSION_TYPE)
+
+        assertThat(route).isEqualTo("TogglePermissionAppList/test.PERMISSION")
+    }
+
+    @Test
+    fun buildInjectEntry_titleDisplayed() {
+        val listModel = TestTogglePermissionAppListModel()
         val entry = TogglePermissionAppListPageProvider.buildInjectEntry(PERMISSION_TYPE) {
-            TestTogglePermissionAppListModel()
+            listModel
         }.build()
 
         composeTestRule.setContent {
@@ -50,18 +145,27 @@
             }
         }
 
-        composeTestRule.onNodeWithText(context.getString(R.string.test_permission_title))
+        composeTestRule.onNodeWithText(context.getString(listModel.pageTitleResId))
             .assertIsDisplayed()
     }
 
-    @Test
-    fun appListRoute() {
-        val route = TogglePermissionAppListPageProvider.getRoute(PERMISSION_TYPE)
-
-        assertThat(route).isEqualTo("TogglePermissionAppList/test.PERMISSION")
+    private fun getSummary(
+        internalAppListModel: TogglePermissionInternalAppListModel<TestAppRecord>,
+    ): State<String> {
+        lateinit var summary: State<String>
+        composeTestRule.setContent {
+            summary = internalAppListModel.getSummary(record = TestAppRecord(APP))
+        }
+        return summary
     }
 
     private companion object {
         const val PERMISSION_TYPE = "test.PERMISSION"
+        const val PACKAGE_NAME = "package.name"
+        const val LABEL = "Label"
+        const val SUMMARY = "Summary"
+        val APP = ApplicationInfo().apply {
+            packageName = PACKAGE_NAME
+        }
     }
 }
diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java
index 1573edb..5610ac4 100644
--- a/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java
+++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java
@@ -17,6 +17,7 @@
 package com.android.settingslib;
 
 import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_FEATURES_NONE;
+import static android.app.admin.DevicePolicyManager.MTE_NOT_CONTROLLED_BY_POLICY;
 import static android.app.admin.DevicePolicyManager.PROFILE_KEYGUARD_FEATURES_AFFECT_OWNER;
 
 import android.annotation.NonNull;
@@ -733,6 +734,26 @@
     }
 
     /**
+     * Checks whether MTE (Advanced memory protection) controls are disabled by the enterprise
+     * policy.
+     */
+    @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    public static EnforcedAdmin checkIfMteIsDisabled(Context context) {
+        final DevicePolicyManager dpm = context.getSystemService(DevicePolicyManager.class);
+        if (dpm.getMtePolicy() == MTE_NOT_CONTROLLED_BY_POLICY) {
+            return null;
+        }
+        EnforcedAdmin admin =
+                RestrictedLockUtils.getProfileOrDeviceOwner(
+                        context, UserHandle.of(UserHandle.USER_SYSTEM));
+        if (admin != null) {
+            return admin;
+        }
+        int profileId = getManagedProfileId(context, UserHandle.USER_SYSTEM);
+        return RestrictedLockUtils.getProfileOrDeviceOwner(context, UserHandle.of(profileId));
+    }
+
+    /**
      * Show restricted setting dialog.
      */
     @RequiresApi(Build.VERSION_CODES.TIRAMISU)
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
index f4355c3..3e63052 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
@@ -161,10 +161,7 @@
      * @return {@code true} if successfully call, otherwise return {@code false}
      */
     public boolean connectDevice(MediaDevice connectDevice) {
-        MediaDevice device = null;
-        synchronized (mMediaDevicesLock) {
-            device = getMediaDeviceById(mMediaDevices, connectDevice.getId());
-        }
+        MediaDevice device = getMediaDeviceById(connectDevice.getId());
         if (device == null) {
             Log.w(TAG, "connectDevice() connectDevice not in the list!");
             return false;
@@ -277,23 +274,6 @@
     /**
      * Find the MediaDevice through id.
      *
-     * @param devices the list of MediaDevice
-     * @param id the unique id of MediaDevice
-     * @return MediaDevice
-     */
-    public MediaDevice getMediaDeviceById(List<MediaDevice> devices, String id) {
-        for (MediaDevice mediaDevice : devices) {
-            if (TextUtils.equals(mediaDevice.getId(), id)) {
-                return mediaDevice;
-            }
-        }
-        Log.i(TAG, "getMediaDeviceById() can't found device");
-        return null;
-    }
-
-    /**
-     * Find the MediaDevice from all media devices by id.
-     *
      * @param id the unique id of MediaDevice
      * @return MediaDevice
      */
@@ -305,7 +285,7 @@
                 }
             }
         }
-        Log.i(TAG, "Unable to find device " + id);
+        Log.i(TAG, "getMediaDeviceById() failed to find device with id: " + id);
         return null;
     }
 
@@ -672,10 +652,7 @@
 
         @Override
         public void onConnectedDeviceChanged(String id) {
-            MediaDevice connectDevice = null;
-            synchronized (mMediaDevicesLock) {
-                connectDevice = getMediaDeviceById(mMediaDevices, id);
-            }
+            MediaDevice connectDevice = getMediaDeviceById(id);
             connectDevice = connectDevice != null
                     ? connectDevice : updateCurrentConnectedDevice();
 
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java
index 24bb1bc..3ec0ba6 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java
@@ -206,8 +206,7 @@
         when(device1.getId()).thenReturn(TEST_DEVICE_ID_1);
         when(device2.getId()).thenReturn(TEST_DEVICE_ID_2);
 
-        final MediaDevice device = mLocalMediaManager
-                .getMediaDeviceById(mLocalMediaManager.mMediaDevices, TEST_DEVICE_ID_2);
+        MediaDevice device = mLocalMediaManager.getMediaDeviceById(TEST_DEVICE_ID_2);
 
         assertThat(device.getId()).isEqualTo(TEST_DEVICE_ID_2);
     }
@@ -222,8 +221,7 @@
         when(device1.getId()).thenReturn(TEST_DEVICE_ID_1);
         when(device2.getId()).thenReturn(TEST_DEVICE_ID_2);
 
-        final MediaDevice device = mLocalMediaManager
-                .getMediaDeviceById(mLocalMediaManager.mMediaDevices, TEST_CURRENT_DEVICE_ID);
+        MediaDevice device = mLocalMediaManager.getMediaDeviceById(TEST_CURRENT_DEVICE_ID);
 
         assertThat(device).isNull();
     }
@@ -238,12 +236,7 @@
         when(device1.getId()).thenReturn(null);
         when(device2.getId()).thenReturn(null);
 
-        MediaDevice device = mLocalMediaManager
-                .getMediaDeviceById(mLocalMediaManager.mMediaDevices, TEST_CURRENT_DEVICE_ID);
-
-        assertThat(device).isNull();
-
-        device = mLocalMediaManager.getMediaDeviceById(TEST_CURRENT_DEVICE_ID);
+        MediaDevice device = mLocalMediaManager.getMediaDeviceById(TEST_CURRENT_DEVICE_ID);
 
         assertThat(device).isNull();
     }
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index 48114ba1e..4365a9b 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -425,6 +425,7 @@
                     Settings.Global.RESTRICTED_NETWORKING_MODE,
                     Settings.Global.REQUIRE_PASSWORD_TO_DECRYPT,
                     Settings.Global.SAFE_BOOT_DISALLOWED,
+                    Settings.Global.SECURE_FRP_MODE,
                     Settings.Global.SELINUX_STATUS,
                     Settings.Global.SELINUX_UPDATE_CONTENT_URL,
                     Settings.Global.SELINUX_UPDATE_METADATA_URL,
diff --git a/packages/Shell/src/com/android/shell/BugreportProgressService.java b/packages/Shell/src/com/android/shell/BugreportProgressService.java
index 6f7d20a..68679c79 100644
--- a/packages/Shell/src/com/android/shell/BugreportProgressService.java
+++ b/packages/Shell/src/com/android/shell/BugreportProgressService.java
@@ -455,7 +455,8 @@
         intent.putExtra(DevicePolicyManager.EXTRA_REMOTE_BUGREPORT_HASH, bugreportHash);
         intent.putExtra(DevicePolicyManager.EXTRA_REMOTE_BUGREPORT_NONCE, nonce);
         intent.putExtra(EXTRA_BUGREPORT, bugreportFileName);
-        context.sendBroadcast(intent, android.Manifest.permission.DUMP);
+        context.sendBroadcastAsUser(intent, UserHandle.SYSTEM,
+                android.Manifest.permission.DUMP);
     }
 
     /**
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 4538179..6984b5a 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -411,6 +411,7 @@
 
         <service android:name=".screenshot.ScreenshotCrossProfileService"
                  android:permission="com.android.systemui.permission.SELF"
+                 android:process=":screenshot_cross_profile"
                  android:exported="false" />
 
         <service android:name=".screenrecord.RecordingService" />
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_host_view.xml b/packages/SystemUI/res-keyguard/layout/keyguard_host_view.xml
index e64b586..8497ff0 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_host_view.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_host_view.xml
@@ -27,6 +27,7 @@
     android:layout_height="match_parent"
     android:clipChildren="false"
     android:clipToPadding="false"
+    android:paddingTop="@dimen/keyguard_lock_padding"
     android:importantForAccessibility="yes"> <!-- Needed because TYPE_WINDOW_STATE_CHANGED is sent
                                                   from this view when bouncer is shown -->
 
diff --git a/packages/SystemUI/res/drawable/dream_aqi_badge_bg.xml b/packages/SystemUI/res/drawable/controls_panel_background.xml
similarity index 82%
rename from packages/SystemUI/res/drawable/dream_aqi_badge_bg.xml
rename to packages/SystemUI/res/drawable/controls_panel_background.xml
index 1992c77..9092877 100644
--- a/packages/SystemUI/res/drawable/dream_aqi_badge_bg.xml
+++ b/packages/SystemUI/res/drawable/controls_panel_background.xml
@@ -1,3 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
 <!--
   ~ Copyright (C) 2022 The Android Open Source Project
   ~
@@ -12,9 +13,10 @@
   ~ 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">
-    <solid android:color="@color/dream_overlay_aqi_unknown" />
-    <corners android:radius="@dimen/dream_aqi_badge_corner_radius" />
+    <solid android:color="#1F1F1F" />
+    <corners android:radius="@dimen/notification_corner_radius" />
 </shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/controls_with_favorites.xml b/packages/SystemUI/res/layout/controls_with_favorites.xml
index 9efad22..ee3adba 100644
--- a/packages/SystemUI/res/layout/controls_with_favorites.xml
+++ b/packages/SystemUI/res/layout/controls_with_favorites.xml
@@ -90,7 +90,7 @@
       android:layout_weight="1"
       android:layout_marginLeft="@dimen/global_actions_side_margin"
       android:layout_marginRight="@dimen/global_actions_side_margin"
-      android:background="#ff0000"
+      android:background="@drawable/controls_panel_background"
       android:padding="@dimen/global_actions_side_margin"
       android:visibility="gone"
       />
diff --git a/packages/SystemUI/res/layout/dream_overlay_complication_aqi.xml b/packages/SystemUI/res/layout/dream_overlay_complication_aqi.xml
deleted file mode 100644
index fcebb8d..0000000
--- a/packages/SystemUI/res/layout/dream_overlay_complication_aqi.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<!--
-  ~ 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.
-  -->
-
-<TextView
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/aqi_view"
-    style="@style/clock_subtitle"
-    android:visibility="gone"
-    android:background="@drawable/dream_aqi_badge_bg"
-    android:paddingHorizontal="@dimen/dream_aqi_badge_padding_horizontal"
-    android:paddingVertical="@dimen/dream_aqi_badge_padding_vertical"
-    android:layout_width="wrap_content"
-    android:layout_height="wrap_content"/>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/dream_overlay_complication_clock_date.xml b/packages/SystemUI/res/layout/dream_overlay_complication_clock_date.xml
deleted file mode 100644
index efbdd1a..0000000
--- a/packages/SystemUI/res/layout/dream_overlay_complication_clock_date.xml
+++ /dev/null
@@ -1,24 +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.
--->
-<TextClock
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/date_view"
-    style="@style/clock_subtitle"
-    android:layout_width="wrap_content"
-    android:layout_height="wrap_content"
-    android:format12Hour="@string/dream_date_complication_date_format"
-    android:format24Hour="@string/dream_date_complication_date_format"/>
diff --git a/packages/SystemUI/res/layout/dream_overlay_complication_weather.xml b/packages/SystemUI/res/layout/dream_overlay_complication_weather.xml
deleted file mode 100644
index f05922f..0000000
--- a/packages/SystemUI/res/layout/dream_overlay_complication_weather.xml
+++ /dev/null
@@ -1,22 +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.
--->
-<TextView
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/weather_view"
-    style="@style/clock_subtitle"
-    android:layout_width="wrap_content"
-    android:layout_height="wrap_content" />
diff --git a/packages/SystemUI/res/drawable/dream_aqi_badge_bg.xml b/packages/SystemUI/res/values-h411dp/dimens.xml
similarity index 74%
copy from packages/SystemUI/res/drawable/dream_aqi_badge_bg.xml
copy to packages/SystemUI/res/values-h411dp/dimens.xml
index 1992c77..6b21353 100644
--- a/packages/SystemUI/res/drawable/dream_aqi_badge_bg.xml
+++ b/packages/SystemUI/res/values-h411dp/dimens.xml
@@ -1,3 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
 <!--
   ~ Copyright (C) 2022 The Android Open Source Project
   ~
@@ -13,8 +14,6 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-
-<shape xmlns:android="http://schemas.android.com/apk/res/android">
-    <solid android:color="@color/dream_overlay_aqi_unknown" />
-    <corners android:radius="@dimen/dream_aqi_badge_corner_radius" />
-</shape>
\ No newline at end of file
+<resources>
+    <dimen name="volume_row_slider_height">137dp</dimen>
+</resources>
diff --git a/packages/SystemUI/res/values-h700dp/dimens.xml b/packages/SystemUI/res/values-h700dp/dimens.xml
index 055308f..39777ab 100644
--- a/packages/SystemUI/res/values-h700dp/dimens.xml
+++ b/packages/SystemUI/res/values-h700dp/dimens.xml
@@ -17,4 +17,5 @@
 <resources>
     <!-- Margin above the ambient indication container -->
     <dimen name="ambient_indication_container_margin_top">15dp</dimen>
-</resources>
\ No newline at end of file
+    <dimen name="volume_row_slider_height">177dp</dimen>
+</resources>
diff --git a/packages/SystemUI/res/drawable/dream_aqi_badge_bg.xml b/packages/SystemUI/res/values-h841dp/dimens.xml
similarity index 74%
copy from packages/SystemUI/res/drawable/dream_aqi_badge_bg.xml
copy to packages/SystemUI/res/values-h841dp/dimens.xml
index 1992c77..412da19 100644
--- a/packages/SystemUI/res/drawable/dream_aqi_badge_bg.xml
+++ b/packages/SystemUI/res/values-h841dp/dimens.xml
@@ -1,3 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
 <!--
   ~ Copyright (C) 2022 The Android Open Source Project
   ~
@@ -13,8 +14,6 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-
-<shape xmlns:android="http://schemas.android.com/apk/res/android">
-    <solid android:color="@color/dream_overlay_aqi_unknown" />
-    <corners android:radius="@dimen/dream_aqi_badge_corner_radius" />
-</shape>
\ No newline at end of file
+<resources>
+    <dimen name="volume_row_slider_height">237dp</dimen>
+</resources>
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index 70d53c7..ba6977a 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -246,15 +246,6 @@
 
     <color name="dream_overlay_camera_mic_off_dot_color">#FCBE03</color>
 
-    <!-- Air Quality -->
-    <color name="dream_overlay_aqi_good">#689F38</color>
-    <color name="dream_overlay_aqi_moderate">#FBC02D</color>
-    <color name="dream_overlay_aqi_unhealthy_sensitive">#F57C00</color>
-    <color name="dream_overlay_aqi_unhealthy">#C53929</color>
-    <color name="dream_overlay_aqi_very_unhealthy">#AD1457</color>
-    <color name="dream_overlay_aqi_hazardous">#880E4F</color>
-    <color name="dream_overlay_aqi_unknown">#BDC1C6</color>
-
     <!-- Dream overlay text shadows -->
     <color name="dream_overlay_clock_key_text_shadow_color">#4D000000</color>
     <color name="dream_overlay_clock_ambient_text_shadow_color">#4D000000</color>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 73baeac..ea51a89 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1583,10 +1583,6 @@
     <dimen name="dream_overlay_y_offset">80dp</dimen>
     <dimen name="dream_overlay_exit_y_offset">40dp</dimen>
 
-    <dimen name="dream_aqi_badge_corner_radius">28dp</dimen>
-    <dimen name="dream_aqi_badge_padding_vertical">6dp</dimen>
-    <dimen name="dream_aqi_badge_padding_horizontal">16dp</dimen>
-
     <dimen name="status_view_margin_horizontal">0dp</dimen>
 
     <!-- Media output broadcast dialog QR code picture size -->
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 5d4fa58..ae1ff1a 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -2792,9 +2792,6 @@
     <!-- [CHAR LIMIT=NONE] Le audio broadcast dialog, media app is unknown -->
     <string name="bt_le_audio_broadcast_dialog_unknown_name">Unknown</string>
 
-    <!-- Date format for the Dream Date Complication [CHAR LIMIT=NONE] -->
-    <string name="dream_date_complication_date_format">EEE, MMM d</string>
-
     <!-- Time format for the Dream Time Complication for 12-hour time format [CHAR LIMIT=NONE] -->
     <string name="dream_time_complication_12_hr_time_format">h:mm</string>
 
diff --git a/packages/SystemUI/res/xml/combined_qs_header_scene.xml b/packages/SystemUI/res/xml/combined_qs_header_scene.xml
index c32de70..38c1640 100644
--- a/packages/SystemUI/res/xml/combined_qs_header_scene.xml
+++ b/packages/SystemUI/res/xml/combined_qs_header_scene.xml
@@ -124,6 +124,11 @@
         </KeyFrameSet>
     </Transition>
 
+    <Transition
+        android:id="@+id/large_screen_header_transition"
+        app:constraintSetStart="@id/large_screen_header_constraint"
+        app:constraintSetEnd="@id/large_screen_header_constraint"/>
+
     <Include app:constraintSet="@xml/large_screen_shade_header"/>
 
     <Include app:constraintSet="@xml/qs_header"/>
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java
index 860c8e3..7da27b1 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java
@@ -260,7 +260,8 @@
         if (reason != PROMPT_REASON_NONE) {
             int promtReasonStringRes = mView.getPromptReasonStringRes(reason);
             if (promtReasonStringRes != 0) {
-                mMessageAreaController.setMessage(promtReasonStringRes);
+                mMessageAreaController.setMessage(
+                        mView.getResources().getString(promtReasonStringRes), false);
             }
         }
     }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
index 2e9ad58..d1c9a30 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
@@ -142,8 +142,11 @@
     }
 
     public void startAppearAnimation() {
-        if (TextUtils.isEmpty(mMessageAreaController.getMessage())) {
-            mMessageAreaController.setMessage(getInitialMessageResId());
+        if (TextUtils.isEmpty(mMessageAreaController.getMessage())
+                && getInitialMessageResId() != 0) {
+            mMessageAreaController.setMessage(
+                    mView.getResources().getString(getInitialMessageResId()),
+                    /* animate= */ false);
         }
         mView.startAppearAnimation();
     }
@@ -163,9 +166,7 @@
     }
 
     /** Determines the message to show in the bouncer when it first appears. */
-    protected int getInitialMessageResId() {
-        return 0;
-    }
+    protected abstract int getInitialMessageResId();
 
     /** Factory for a {@link KeyguardInputViewController}. */
     public static class Factory {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java
index 5d86ccd..67e3400 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java
@@ -52,6 +52,7 @@
     private int mYTransOffset;
     private View mBouncerMessageView;
     @DevicePostureInt private int mLastDevicePosture = DEVICE_POSTURE_UNKNOWN;
+    public static final long ANIMATION_DURATION = 650;
 
     public KeyguardPINView(Context context) {
         this(context, null);
@@ -181,7 +182,7 @@
         if (mAppearAnimator.isRunning()) {
             mAppearAnimator.cancel();
         }
-        mAppearAnimator.setDuration(650);
+        mAppearAnimator.setDuration(ANIMATION_DURATION);
         mAppearAnimator.addUpdateListener(animation -> animate(animation.getAnimatedFraction()));
         mAppearAnimator.start();
     }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java
index f7423ed..8011efd 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java
@@ -139,4 +139,9 @@
         super.startErrorAnimation();
         mView.startErrorAnimation();
     }
+
+    @Override
+    protected int getInitialMessageResId() {
+        return R.string.keyguard_enter_your_pin;
+    }
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
index f51ac32..35b2db2 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
@@ -74,9 +74,4 @@
         return mView.startDisappearAnimation(
                 mKeyguardUpdateMonitor.needsSlowUnlockTransition(), finishRunnable);
     }
-
-    @Override
-    protected int getInitialMessageResId() {
-        return R.string.keyguard_enter_your_pin;
-    }
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
index 8f3484a..5d7a6f1 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
@@ -36,8 +36,11 @@
 
 import static java.lang.Integer.max;
 
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
 import android.animation.AnimatorSet;
 import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
 import android.app.Activity;
 import android.app.AlertDialog;
 import android.app.admin.DevicePolicyManager;
@@ -967,11 +970,23 @@
             }
 
             mUserSwitcherViewGroup.setAlpha(0f);
-            ObjectAnimator alphaAnim = ObjectAnimator.ofFloat(mUserSwitcherViewGroup, View.ALPHA,
-                    1f);
-            alphaAnim.setInterpolator(Interpolators.ALPHA_IN);
-            alphaAnim.setDuration(500);
-            alphaAnim.start();
+            ValueAnimator animator = ValueAnimator.ofFloat(0f, 1f);
+            int yTrans = mView.getResources().getDimensionPixelSize(R.dimen.pin_view_trans_y_entry);
+            animator.setInterpolator(Interpolators.STANDARD_DECELERATE);
+            animator.setDuration(650);
+            animator.addListener(new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    mUserSwitcherViewGroup.setAlpha(1f);
+                    mUserSwitcherViewGroup.setTranslationY(0f);
+                }
+            });
+            animator.addUpdateListener(animation -> {
+                float value = (float) animation.getAnimatedValue();
+                mUserSwitcherViewGroup.setAlpha(value);
+                mUserSwitcherViewGroup.setTranslationY(yTrans - yTrans * value);
+            });
+            animator.start();
         }
 
         @Override
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java
index a5c8c78..39b567f 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java
@@ -156,5 +156,10 @@
         @Override
         public void onStartingToHide() {
         }
+
+        @Override
+        protected int getInitialMessageResId() {
+            return 0;
+        }
     }
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 84ef505..3a592a9 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -1644,7 +1644,7 @@
 
                 @Override
                 public void onAuthenticationFailed() {
-                    requestActiveUnlock(
+                    requestActiveUnlockDismissKeyguard(
                             ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.BIOMETRIC_FAIL,
                             "fingerprintFailure");
                     handleFingerprintAuthFailed();
@@ -2576,6 +2576,18 @@
     }
 
     /**
+     * Attempts to trigger active unlock from trust agent with a request to dismiss the keyguard.
+     */
+    public void requestActiveUnlockDismissKeyguard(
+            @NonNull ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN requestOrigin,
+            String extraReason
+    ) {
+        requestActiveUnlock(
+                requestOrigin,
+                extraReason + "-dismissKeyguard", true);
+    }
+
+    /**
      * Whether the UDFPS bouncer is showing.
      */
     public void setUdfpsBouncerShowing(boolean showing) {
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java b/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java
index 632fcdc..0fc9ef9 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java
@@ -22,6 +22,8 @@
 import android.os.HandlerThread;
 import android.util.Log;
 
+import androidx.annotation.Nullable;
+
 import com.android.systemui.dagger.GlobalRootComponent;
 import com.android.systemui.dagger.SysUIComponent;
 import com.android.systemui.dagger.WMComponent;
@@ -53,6 +55,7 @@
         mContext = context;
     }
 
+    @Nullable
     protected abstract GlobalRootComponent.Builder getGlobalRootComponentBuilder();
 
     /**
@@ -69,6 +72,11 @@
      * Starts the initialization process. This stands up the Dagger graph.
      */
     public void init(boolean fromTest) throws ExecutionException, InterruptedException {
+        GlobalRootComponent.Builder globalBuilder = getGlobalRootComponentBuilder();
+        if (globalBuilder == null) {
+            return;
+        }
+
         mRootComponent = getGlobalRootComponentBuilder()
                 .context(mContext)
                 .instrumentationTest(fromTest)
@@ -119,6 +127,7 @@
                     .setBackAnimation(Optional.ofNullable(null))
                     .setDesktopMode(Optional.ofNullable(null));
         }
+
         mSysUIComponent = builder.build();
         if (initializeComponents) {
             mSysUIComponent.init();
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIInitializerImpl.kt b/packages/SystemUI/src/com/android/systemui/SystemUIInitializerImpl.kt
index 8aa3040..55c095b 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIInitializerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIInitializerImpl.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui
 
+import android.app.Application
 import android.content.Context
 import com.android.systemui.dagger.DaggerReferenceGlobalRootComponent
 import com.android.systemui.dagger.GlobalRootComponent
@@ -24,7 +25,17 @@
  * {@link SystemUIInitializer} that stands up AOSP SystemUI.
  */
 class SystemUIInitializerImpl(context: Context) : SystemUIInitializer(context) {
-    override fun getGlobalRootComponentBuilder(): GlobalRootComponent.Builder {
-        return DaggerReferenceGlobalRootComponent.builder()
+
+    override fun getGlobalRootComponentBuilder(): GlobalRootComponent.Builder? {
+        return when (Application.getProcessName()) {
+            SCREENSHOT_CROSS_PROFILE_PROCESS -> null
+            else -> DaggerReferenceGlobalRootComponent.builder()
+        }
+    }
+
+    companion object {
+        private const val SYSTEMUI_PROCESS = "com.android.systemui"
+        private const val SCREENSHOT_CROSS_PROFILE_PROCESS =
+                "$SYSTEMUI_PROCESS:screenshot_cross_profile"
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/camera/CameraGestureHelper.kt b/packages/SystemUI/src/com/android/systemui/camera/CameraGestureHelper.kt
index e2ef247..58d40d3 100644
--- a/packages/SystemUI/src/com/android/systemui/camera/CameraGestureHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/camera/CameraGestureHelper.kt
@@ -28,7 +28,6 @@
 import android.os.UserHandle
 import android.util.Log
 import android.view.WindowManager
-import androidx.annotation.VisibleForTesting
 import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.systemui.ActivityIntentHelper
 import com.android.systemui.dagger.qualifiers.Main
@@ -83,7 +82,7 @@
      */
     fun launchCamera(source: Int) {
         val intent: Intent = getStartCameraIntent()
-        intent.putExtra(EXTRA_CAMERA_LAUNCH_SOURCE, source)
+        intent.putExtra(CameraIntents.EXTRA_LAUNCH_SOURCE, source)
         val wouldLaunchResolverActivity = activityIntentHelper.wouldLaunchResolverActivity(
             intent, KeyguardUpdateMonitor.getCurrentUser()
         )
@@ -149,9 +148,4 @@
             cameraIntents.getInsecureCameraIntent()
         }
     }
-
-    companion object {
-        @VisibleForTesting
-        const val EXTRA_CAMERA_LAUNCH_SOURCE = "com.android.systemui.camera_launch_source"
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/camera/CameraIntents.kt b/packages/SystemUI/src/com/android/systemui/camera/CameraIntents.kt
index f8a2002..867faf9 100644
--- a/packages/SystemUI/src/com/android/systemui/camera/CameraIntents.kt
+++ b/packages/SystemUI/src/com/android/systemui/camera/CameraIntents.kt
@@ -29,6 +29,7 @@
                 MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE
         val DEFAULT_INSECURE_CAMERA_INTENT_ACTION =
                 MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA
+        const val EXTRA_LAUNCH_SOURCE = "com.android.systemui.camera_launch_source"
 
         @JvmStatic
         fun getOverrideCameraPackage(context: Context): String? {
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsAnimations.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsAnimations.kt
index 4aa597e..8d0edf8 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsAnimations.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsAnimations.kt
@@ -50,7 +50,12 @@
      * Setup an activity to handle enter/exit animations. [view] should be the root of the content.
      * Fade and translate together.
      */
-    fun observerForAnimations(view: ViewGroup, window: Window, intent: Intent): LifecycleObserver {
+    fun observerForAnimations(
+            view: ViewGroup,
+            window: Window,
+            intent: Intent,
+            animateY: Boolean = true
+    ): LifecycleObserver {
         return object : LifecycleObserver {
             var showAnimation = intent.getBooleanExtra(ControlsUiController.EXTRA_ANIMATE, false)
 
@@ -61,8 +66,12 @@
                 view.transitionAlpha = 0.0f
 
                 if (translationY == -1f) {
-                    translationY = view.context.resources.getDimensionPixelSize(
-                        R.dimen.global_actions_controls_y_translation).toFloat()
+                    if (animateY) {
+                        translationY = view.context.resources.getDimensionPixelSize(
+                                R.dimen.global_actions_controls_y_translation).toFloat()
+                    } else {
+                        translationY = 0f
+                    }
                 }
             }
 
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt
index 5d611c4..d8d8c0e 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt
@@ -70,7 +70,8 @@
             ControlsAnimations.observerForAnimations(
                 requireViewById<ViewGroup>(R.id.control_detail_root),
                 window,
-                intent
+                intent,
+                !featureFlags.isEnabled(Flags.USE_APP_PANELS)
             )
         )
 
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
index fb678aa..1e3e5cd 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
@@ -186,7 +186,7 @@
         val allStructures = controlsController.get().getFavorites()
         val selected = getPreferredSelectedItem(allStructures)
         val anyPanels = controlsListingController.get().getCurrentServices()
-                .none { it.panelActivity != null }
+                .any { it.panelActivity != null }
 
         return if (controlsController.get().addSeedingFavoritesCallback(onSeedingComplete)) {
             ControlsActivity::class.java
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/PanelTaskViewController.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/PanelTaskViewController.kt
index 7143be2..f5764c2 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/PanelTaskViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/PanelTaskViewController.kt
@@ -24,6 +24,10 @@
 import android.content.ComponentName
 import android.content.Context
 import android.content.Intent
+import android.graphics.Color
+import android.graphics.drawable.ShapeDrawable
+import android.graphics.drawable.shapes.RoundRectShape
+import com.android.systemui.R
 import com.android.systemui.util.boundsOnScreen
 import com.android.wm.shell.TaskView
 import java.util.concurrent.Executor
@@ -64,6 +68,16 @@
                 options.taskAlwaysOnTop = true
 
                 taskView.post {
+                    val roundedCorner =
+                        activityContext.resources.getDimensionPixelSize(
+                            R.dimen.notification_corner_radius
+                        )
+                    val radii = FloatArray(8) { roundedCorner.toFloat() }
+                    taskView.background =
+                        ShapeDrawable(RoundRectShape(radii, null, null)).apply {
+                            setTint(Color.TRANSPARENT)
+                        }
+                    taskView.clipToOutline = true
                     taskView.startActivity(
                         pendingIntent,
                         fillInIntent,
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
index 8fdcb0d..16b4f99 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
@@ -45,7 +45,10 @@
 import com.android.systemui.dreams.complication.dagger.ComplicationComponent;
 import com.android.systemui.dreams.dagger.DreamOverlayComponent;
 import com.android.systemui.dreams.touch.DreamOverlayTouchMonitor;
+import com.android.systemui.touch.TouchInsetManager;
 
+import java.util.Arrays;
+import java.util.HashSet;
 import java.util.concurrent.Executor;
 
 import javax.inject.Inject;
@@ -84,6 +87,9 @@
 
     private final ComplicationComponent mComplicationComponent;
 
+    private final com.android.systemui.dreams.dreamcomplication.dagger.ComplicationComponent
+            mDreamComplicationComponent;
+
     private final DreamOverlayComponent mDreamOverlayComponent;
 
     private final DreamOverlayLifecycleOwner mLifecycleOwner;
@@ -135,10 +141,13 @@
             DreamOverlayLifecycleOwner lifecycleOwner,
             WindowManager windowManager,
             ComplicationComponent.Factory complicationComponentFactory,
+            com.android.systemui.dreams.dreamcomplication.dagger.ComplicationComponent.Factory
+                    dreamComplicationComponentFactory,
             DreamOverlayComponent.Factory dreamOverlayComponentFactory,
             DreamOverlayStateController stateController,
             KeyguardUpdateMonitor keyguardUpdateMonitor,
             UiEventLogger uiEventLogger,
+            TouchInsetManager touchInsetManager,
             @Nullable @Named(LowLightDreamModule.LOW_LIGHT_DREAM_COMPONENT)
                     ComponentName lowLightDreamComponent) {
         mContext = context;
@@ -154,9 +163,14 @@
         final Complication.Host host =
                 () -> mExecutor.execute(DreamOverlayService.this::requestExit);
 
-        mComplicationComponent = complicationComponentFactory.create();
-        mDreamOverlayComponent =
-                dreamOverlayComponentFactory.create(lifecycleOwner, viewModelStore, host, null);
+        mComplicationComponent = complicationComponentFactory.create(lifecycleOwner, host,
+                viewModelStore, touchInsetManager);
+        mDreamComplicationComponent = dreamComplicationComponentFactory.create(
+                mComplicationComponent.getVisibilityController(), touchInsetManager);
+        mDreamOverlayComponent = dreamOverlayComponentFactory.create(lifecycleOwner,
+                mComplicationComponent.getComplicationHostViewController(), touchInsetManager,
+                new HashSet<>(Arrays.asList(
+                        mDreamComplicationComponent.getHideComplicationTouchHandler())));
         mLifecycleOwner = lifecycleOwner;
         mLifecycleRegistry = mLifecycleOwner.getRegistry();
 
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutEngine.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutEngine.java
index 46ce7a9..3e9b010 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutEngine.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutEngine.java
@@ -30,7 +30,7 @@
 
 import com.android.systemui.R;
 import com.android.systemui.dreams.complication.ComplicationLayoutParams.Position;
-import com.android.systemui.dreams.dagger.DreamOverlayComponent;
+import com.android.systemui.dreams.complication.dagger.ComplicationModule;
 import com.android.systemui.statusbar.CrossFadeHelper;
 import com.android.systemui.touch.TouchInsetManager;
 
@@ -50,7 +50,7 @@
  * their layout parameters and attributes. The management of this set is done by
  * {@link ComplicationHostViewController}.
  */
-@DreamOverlayComponent.DreamOverlayScope
+@ComplicationModule.ComplicationScope
 public class ComplicationLayoutEngine implements Complication.VisibilityController {
     public static final String TAG = "ComplicationLayoutEng";
 
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamHomeControlsComplication.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamHomeControlsComplication.java
index ee00512..1065b94 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamHomeControlsComplication.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamHomeControlsComplication.java
@@ -136,8 +136,15 @@
             final boolean hasFavorites = mControlsComponent.getControlsController()
                     .map(c -> !c.getFavorites().isEmpty())
                     .orElse(false);
+            boolean hasPanels = false;
+            for (int i = 0; i < controlsServices.size(); i++) {
+                if (controlsServices.get(i).getPanelActivity() != null) {
+                    hasPanels = true;
+                    break;
+                }
+            }
             final ControlsComponent.Visibility visibility = mControlsComponent.getVisibility();
-            return hasFavorites && visibility != UNAVAILABLE;
+            return (hasFavorites || hasPanels) && visibility != UNAVAILABLE;
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/ComplicationComponent.kt b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/ComplicationComponent.kt
index 8949709..8d133bd 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/ComplicationComponent.kt
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/ComplicationComponent.kt
@@ -1,12 +1,29 @@
 package com.android.systemui.dreams.complication.dagger
 
+import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.ViewModelStore
+import com.android.systemui.dreams.complication.Complication
+import com.android.systemui.dreams.complication.ComplicationHostViewController
+import com.android.systemui.dreams.complication.ComplicationLayoutEngine
+import com.android.systemui.touch.TouchInsetManager
+import dagger.BindsInstance
 import dagger.Subcomponent
 
-@Subcomponent
+@Subcomponent(modules = [ComplicationModule::class])
+@ComplicationModule.ComplicationScope
 interface ComplicationComponent {
     /** Factory for generating [ComplicationComponent]. */
     @Subcomponent.Factory
     interface Factory {
-        fun create(): ComplicationComponent
+        fun create(
+            @BindsInstance lifecycleOwner: LifecycleOwner,
+            @BindsInstance host: Complication.Host,
+            @BindsInstance viewModelStore: ViewModelStore,
+            @BindsInstance touchInsetManager: TouchInsetManager
+        ): ComplicationComponent
     }
+
+    fun getComplicationHostViewController(): ComplicationHostViewController
+
+    fun getVisibilityController(): ComplicationLayoutEngine
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/ComplicationHostViewModule.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/ComplicationHostViewModule.java
index 09cc7c5..797906f 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/ComplicationHostViewModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/ComplicationHostViewModule.java
@@ -24,13 +24,12 @@
 import com.android.internal.util.Preconditions;
 import com.android.systemui.R;
 import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.dreams.dagger.DreamOverlayComponent;
-
-import javax.inject.Named;
 
 import dagger.Module;
 import dagger.Provides;
 
+import javax.inject.Named;
+
 /**
  * Module for providing a scoped host view.
  */
@@ -49,7 +48,7 @@
      */
     @Provides
     @Named(SCOPED_COMPLICATIONS_LAYOUT)
-    @DreamOverlayComponent.DreamOverlayScope
+    @ComplicationModule.ComplicationScope
     static ConstraintLayout providesComplicationHostView(
             LayoutInflater layoutInflater) {
         return Preconditions.checkNotNull((ConstraintLayout)
@@ -60,7 +59,6 @@
 
     @Provides
     @Named(COMPLICATION_MARGIN_DEFAULT)
-    @DreamOverlayComponent.DreamOverlayScope
     static int providesComplicationPadding(@Main Resources resources) {
         return resources.getDimensionPixelSize(R.dimen.dream_overlay_complication_margin);
     }
@@ -70,7 +68,6 @@
      */
     @Provides
     @Named(COMPLICATIONS_FADE_OUT_DURATION)
-    @DreamOverlayComponent.DreamOverlayScope
     static int providesComplicationsFadeOutDuration(@Main Resources resources) {
         return resources.getInteger(R.integer.complicationFadeOutMs);
     }
@@ -80,7 +77,6 @@
      */
     @Provides
     @Named(COMPLICATIONS_FADE_OUT_DELAY)
-    @DreamOverlayComponent.DreamOverlayScope
     static int providesComplicationsFadeOutDelay(@Main Resources resources) {
         return resources.getInteger(R.integer.complicationFadeOutDelayMs);
     }
@@ -90,7 +86,6 @@
      */
     @Provides
     @Named(COMPLICATIONS_FADE_IN_DURATION)
-    @DreamOverlayComponent.DreamOverlayScope
     static int providesComplicationsFadeInDuration(@Main Resources resources) {
         return resources.getInteger(R.integer.complicationFadeInMs);
     }
@@ -100,7 +95,6 @@
      */
     @Provides
     @Named(COMPLICATIONS_RESTORE_TIMEOUT)
-    @DreamOverlayComponent.DreamOverlayScope
     static int providesComplicationsRestoreTimeout(@Main Resources resources) {
         return resources.getInteger(R.integer.complicationRestoreMs);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/ComplicationModule.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/ComplicationModule.java
index 5c2fdf5..dbf5ab0 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/ComplicationModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/ComplicationModule.java
@@ -24,16 +24,16 @@
 import com.android.systemui.dreams.complication.Complication;
 import com.android.systemui.dreams.complication.ComplicationCollectionViewModel;
 import com.android.systemui.dreams.complication.ComplicationLayoutEngine;
+import com.android.systemui.touch.TouchInsetManager;
+
+import dagger.Module;
+import dagger.Provides;
 
 import java.lang.annotation.Documented;
 import java.lang.annotation.Retention;
 
 import javax.inject.Named;
 import javax.inject.Scope;
-
-import dagger.Module;
-import dagger.Provides;
-
 /**
  * Module for housing components related to rendering complications.
  */
@@ -73,4 +73,13 @@
             ComplicationLayoutEngine engine) {
         return engine;
     }
+
+    /**
+     * Provides a new touch inset session instance for complication logic.
+     */
+    @Provides
+    static TouchInsetManager.TouchInsetSession providesTouchInsetSession(
+            TouchInsetManager manager) {
+        return manager.createSession();
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayComponent.java b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayComponent.java
index 5848290..0332f88 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayComponent.java
@@ -23,14 +23,13 @@
 import android.annotation.Nullable;
 
 import androidx.lifecycle.LifecycleOwner;
-import androidx.lifecycle.ViewModelStore;
 
 import com.android.systemui.dreams.DreamOverlayContainerViewController;
-import com.android.systemui.dreams.complication.Complication;
-import com.android.systemui.dreams.complication.dagger.ComplicationModule;
+import com.android.systemui.dreams.complication.ComplicationHostViewController;
 import com.android.systemui.dreams.touch.DreamOverlayTouchMonitor;
 import com.android.systemui.dreams.touch.DreamTouchHandler;
 import com.android.systemui.dreams.touch.dagger.DreamTouchModule;
+import com.android.systemui.touch.TouchInsetManager;
 
 import dagger.BindsInstance;
 import dagger.Subcomponent;
@@ -48,7 +47,6 @@
 @Subcomponent(modules = {
         DreamTouchModule.class,
         DreamOverlayModule.class,
-        ComplicationModule.class,
 })
 @DreamOverlayComponent.DreamOverlayScope
 public interface DreamOverlayComponent {
@@ -57,8 +55,8 @@
     interface Factory {
         DreamOverlayComponent create(
                 @BindsInstance LifecycleOwner lifecycleOwner,
-                @BindsInstance ViewModelStore store,
-                @BindsInstance Complication.Host host,
+                @BindsInstance ComplicationHostViewController complicationHostViewController,
+                @BindsInstance TouchInsetManager touchInsetManager,
                 @BindsInstance @Named(DREAM_TOUCH_HANDLERS) @Nullable
                         Set<DreamTouchHandler> dreamTouchHandlers);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java
index 67e2571..4485381 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java
@@ -29,20 +29,15 @@
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dreams.DreamOverlayContainerView;
 import com.android.systemui.dreams.DreamOverlayStatusBarView;
-import com.android.systemui.dreams.complication.Complication;
-import com.android.systemui.dreams.dreamcomplication.HideComplicationTouchHandler;
-import com.android.systemui.dreams.dreamcomplication.dagger.ComplicationComponent;
 import com.android.systemui.dreams.touch.DreamTouchHandler;
 import com.android.systemui.touch.TouchInsetManager;
 
 import dagger.Module;
 import dagger.Provides;
 import dagger.multibindings.ElementsIntoSet;
-import dagger.multibindings.IntoSet;
 
 import java.util.HashSet;
 import java.util.Set;
-import java.util.concurrent.Executor;
 
 import javax.inject.Named;
 
@@ -109,14 +104,6 @@
     /** */
     @Provides
     @DreamOverlayComponent.DreamOverlayScope
-    public static TouchInsetManager providesTouchInsetManager(@Main Executor executor,
-            DreamOverlayContainerView view) {
-        return new TouchInsetManager(executor, view);
-    }
-
-    /** */
-    @Provides
-    @DreamOverlayComponent.DreamOverlayScope
     public static DreamOverlayStatusBarView providesDreamOverlayStatusBarView(
             DreamOverlayContainerView view) {
         return Preconditions.checkNotNull(view.findViewById(R.id.dream_overlay_status_bar),
@@ -264,18 +251,4 @@
             @Named(DREAM_TOUCH_HANDLERS) @Nullable Set<DreamTouchHandler> touchHandlers) {
         return touchHandlers != null ? touchHandlers : new HashSet<>();
     }
-
-    /**
-     * Provides {@link HideComplicationTouchHandler} for inclusion in touch handling over the dream.
-     */
-    @Provides
-    @IntoSet
-    public static DreamTouchHandler providesHideComplicationTouchHandler(
-            ComplicationComponent.Factory componentFactory,
-            Complication.VisibilityController visibilityController,
-            TouchInsetManager touchInsetManager) {
-        ComplicationComponent component =
-                componentFactory.create(visibilityController, touchInsetManager);
-        return component.getHideComplicationTouchHandler();
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 4a046e1..d4cd57f 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -399,9 +399,10 @@
     val CHOOSER_UNBUNDLED = unreleasedFlag(1500, "chooser_unbundled", teamfood = true)
 
     // 1600 - accessibility
+    // TODO(b/262224538): Tracking Bug
     @JvmField
     val A11Y_FLOATING_MENU_FLING_SPRING_ANIMATIONS =
-        unreleasedFlag(1600, "a11y_floating_menu_fling_spring_animations", teamfood = true)
+        releasedFlag(1600, "a11y_floating_menu_fling_spring_animations")
 
     // 1700 - clipboard
     @JvmField val CLIPBOARD_OVERLAY_REFACTOR = releasedFlag(1700, "clipboard_overlay_refactor")
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 36c939d..d6418d0 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -1614,7 +1614,7 @@
         // TODO: Rename all screen off/on references to interactive/sleeping
         synchronized (this) {
             mDeviceInteractive = true;
-            if (mPendingLock && !cameraGestureTriggered) {
+            if (mPendingLock && !cameraGestureTriggered && !mWakeAndUnlocking) {
                 doKeyguardLocked(null);
             }
             mAnimatingScreenOff = false;
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt
index 2558fab..394426d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt
@@ -130,6 +130,7 @@
                             state(
                                 isFeatureEnabled = component.isEnabled(),
                                 hasFavorites = favorites?.isNotEmpty() == true,
+                                hasPanels = serviceInfos.any { it.panelActivity != null },
                                 hasServiceInfos = serviceInfos.isNotEmpty(),
                                 iconResourceId = component.getTileImageId(),
                                 visibility = component.getVisibility(),
@@ -148,13 +149,14 @@
     private fun state(
         isFeatureEnabled: Boolean,
         hasFavorites: Boolean,
+        hasPanels: Boolean,
         hasServiceInfos: Boolean,
         visibility: ControlsComponent.Visibility,
         @DrawableRes iconResourceId: Int?,
     ): KeyguardQuickAffordanceConfig.LockScreenState {
         return if (
             isFeatureEnabled &&
-                hasFavorites &&
+                (hasFavorites || hasPanels) &&
                 hasServiceInfos &&
                 iconResourceId != null &&
                 visibility == ControlsComponent.Visibility.AVAILABLE
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java
index 9743c3e..206a620 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java
@@ -358,6 +358,9 @@
                     if (!isChecked && shouldShowMobileDialog()) {
                         showTurnOffMobileDialog();
                     } else if (!shouldShowMobileDialog()) {
+                        if (mInternetDialogController.isMobileDataEnabled() == isChecked) {
+                            return;
+                        }
                         mInternetDialogController.setMobileDataEnabled(mContext, mDefaultDataSubId,
                                 isChecked, false);
                     }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt b/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt
index b511b54..7fc0a5f 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt
@@ -101,6 +101,8 @@
         @VisibleForTesting
         internal val HEADER_TRANSITION_ID = R.id.header_transition
         @VisibleForTesting
+        internal val LARGE_SCREEN_HEADER_TRANSITION_ID = R.id.large_screen_header_transition
+        @VisibleForTesting
         internal val QQS_HEADER_CONSTRAINT = R.id.qqs_header_constraint
         @VisibleForTesting
         internal val QS_HEADER_CONSTRAINT = R.id.qs_header_constraint
@@ -429,8 +431,11 @@
         }
         header as MotionLayout
         if (largeScreenActive) {
-            header.getConstraintSet(LARGE_SCREEN_HEADER_CONSTRAINT).applyTo(header)
+            logInstantEvent("Large screen constraints set")
+            header.setTransition(HEADER_TRANSITION_ID)
+            header.transitionToStart()
         } else {
+            logInstantEvent("Small screen constraints set")
             header.setTransition(HEADER_TRANSITION_ID)
             header.transitionToStart()
             updatePosition()
@@ -440,15 +445,19 @@
 
     private fun updatePosition() {
         if (header is MotionLayout && !largeScreenActive && visible) {
-            Trace.instantForTrack(
-                TRACE_TAG_APP,
-                "LargeScreenHeaderController - updatePosition",
-                "position: $qsExpandedFraction"
-            )
+            logInstantEvent("updatePosition: $qsExpandedFraction")
             header.progress = qsExpandedFraction
         }
     }
 
+    private fun logInstantEvent(message: String) {
+        Trace.instantForTrack(
+                TRACE_TAG_APP,
+                "LargeScreenHeaderController",
+                message
+        )
+    }
+
     private fun updateListeners() {
         qsCarrierGroupController.setListening(visible)
         if (visible) {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index ee89a48..5c2c1b6 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -40,6 +40,7 @@
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_QUICK_SETTINGS_EXPANDED;
 import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
 import static com.android.systemui.statusbar.StatusBarState.SHADE;
+import static com.android.systemui.statusbar.StatusBarState.SHADE_LOCKED;
 import static com.android.systemui.statusbar.VibratorHelper.TOUCH_VIBRATION_ATTRIBUTES;
 import static com.android.systemui.statusbar.notification.stack.StackStateAnimator.ANIMATION_DURATION_FOLD_TO_AOD;
 import static com.android.systemui.util.DumpUtilsKt.asIndenting;
@@ -2325,7 +2326,7 @@
 
 
     private boolean handleQsTouch(MotionEvent event) {
-        if (mSplitShadeEnabled && touchXOutsideOfQs(event.getX())) {
+        if (isSplitShadeAndTouchXOutsideQs(event.getX())) {
             return false;
         }
         final int action = event.getActionMasked();
@@ -2382,12 +2383,14 @@
         return false;
     }
 
-    private boolean touchXOutsideOfQs(float touchX) {
-        return touchX < mQsFrame.getX() || touchX > mQsFrame.getX() + mQsFrame.getWidth();
+    /** Returns whether split shade is enabled and an x coordinate is outside of the QS frame. */
+    private boolean isSplitShadeAndTouchXOutsideQs(float touchX) {
+        return mSplitShadeEnabled && (touchX < mQsFrame.getX()
+                || touchX > mQsFrame.getX() + mQsFrame.getWidth());
     }
 
     private boolean isInQsArea(float x, float y) {
-        if (touchXOutsideOfQs(x)) {
+        if (isSplitShadeAndTouchXOutsideQs(x)) {
             return false;
         }
         // Let's reject anything at the very bottom around the home handle in gesture nav
@@ -4720,6 +4723,7 @@
             if (!openingWithTouch || !mHasVibratedOnOpen) {
                 mVibratorHelper.vibrate(VibrationEffect.EFFECT_TICK);
                 mHasVibratedOnOpen = true;
+                mShadeLog.v("Vibrating on opening, mHasVibratedOnOpen=true");
             }
         }
     }
@@ -5316,7 +5320,7 @@
         @Override
         public void flingTopOverscroll(float velocity, boolean open) {
             // in split shade mode we want to expand/collapse QS only when touch happens within QS
-            if (mSplitShadeEnabled && touchXOutsideOfQs(mInitialTouchX)) {
+            if (isSplitShadeAndTouchXOutsideQs(mInitialTouchX)) {
                 return;
             }
             mLastOverscroll = 0f;
@@ -5477,6 +5481,15 @@
             mBarState = statusBarState;
             mKeyguardShowing = keyguardShowing;
 
+            boolean fromShadeToKeyguard = statusBarState == KEYGUARD
+                    && (oldState == SHADE || oldState == SHADE_LOCKED);
+            if (mSplitShadeEnabled && fromShadeToKeyguard) {
+                // user can go to keyguard from different shade states and closing animation
+                // may not fully run - we always want to make sure we close QS when that happens
+                // as we never need QS open in fresh keyguard state
+                closeQs();
+            }
+
             if (oldState == KEYGUARD && (goingToFullShade
                     || statusBarState == StatusBarState.SHADE_LOCKED)) {
 
@@ -5496,27 +5509,12 @@
                 mKeyguardStatusBarViewController.animateKeyguardStatusBarIn();
 
                 mNotificationStackScrollLayoutController.resetScrollPosition();
-                // Only animate header if the header is visible. If not, it will partially
-                // animate out
-                // the top of QS
-                if (!mQsExpanded) {
-                    // TODO(b/185683835) Nicer clipping when using new spacial model
-                    if (mSplitShadeEnabled) {
-                        mQs.animateHeaderSlidingOut();
-                    }
-                }
             } else {
                 // this else branch means we are doing one of:
                 //  - from KEYGUARD to SHADE (but not fully expanded as when swiping from the top)
                 //  - from SHADE to KEYGUARD
                 //  - from SHADE_LOCKED to SHADE
                 //  - getting notified again about the current SHADE or KEYGUARD state
-                if (mSplitShadeEnabled && oldState == SHADE && statusBarState == KEYGUARD) {
-                    // user can go to keyguard from different shade states and closing animation
-                    // may not fully run - we always want to make sure we close QS when that happens
-                    // as we never need QS open in fresh keyguard state
-                    closeQs();
-                }
                 final boolean animatingUnlockedShadeToKeyguard = oldState == SHADE
                         && statusBarState == KEYGUARD
                         && mScreenOffAnimationController.isKeyguardShowDelayed();
@@ -6120,6 +6118,7 @@
                     if (isFullyCollapsed()) {
                         // If panel is fully collapsed, reset haptic effect before adding movement.
                         mHasVibratedOnOpen = false;
+                        mShadeLog.logHasVibrated(mHasVibratedOnOpen, mExpandedFraction);
                     }
                     addMovement(event);
                     if (!isFullyCollapsed()) {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt
index 0b59af3..5fedbeb 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt
@@ -140,6 +140,15 @@
         })
     }
 
+    fun logHasVibrated(hasVibratedOnOpen: Boolean, fraction: Float) {
+        log(LogLevel.VERBOSE, {
+            bool1 = hasVibratedOnOpen
+            double1 = fraction.toDouble()
+        }, {
+            "hasVibratedOnOpen=$bool1, expansionFraction=$double1"
+        })
+    }
+
     fun logQsExpansionChanged(
             message: String,
             qsExpanded: Boolean,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt
index 39daa13..3072c81 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt
@@ -33,6 +33,8 @@
     fun fullScreenIntentRequiresKeyguard(): Boolean =
         featureFlags.isEnabled(Flags.FSI_REQUIRES_KEYGUARD)
 
+    fun fsiOnDNDUpdate(): Boolean = featureFlags.isEnabled(Flags.FSI_ON_DND_UPDATE)
+
     val isStabilityIndexFixEnabled: Boolean by lazy {
         featureFlags.isEnabled(Flags.STABILITY_INDEX_FIX)
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt
index 5dbb4f9..1004ec1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt
@@ -22,6 +22,7 @@
 import com.android.internal.annotations.VisibleForTesting
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.statusbar.NotificationRemoteInputManager
+import com.android.systemui.statusbar.notification.NotifPipelineFlags
 import com.android.systemui.statusbar.notification.collection.GroupEntry
 import com.android.systemui.statusbar.notification.collection.ListEntry
 import com.android.systemui.statusbar.notification.collection.NotifPipeline
@@ -38,6 +39,7 @@
 import com.android.systemui.statusbar.notification.dagger.IncomingHeader
 import com.android.systemui.statusbar.notification.interruption.HeadsUpViewBinder
 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider
+import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider.FullScreenIntentDecision
 import com.android.systemui.statusbar.notification.logKey
 import com.android.systemui.statusbar.notification.stack.BUCKET_HEADS_UP
 import com.android.systemui.statusbar.policy.HeadsUpManager
@@ -70,11 +72,13 @@
     private val mNotificationInterruptStateProvider: NotificationInterruptStateProvider,
     private val mRemoteInputManager: NotificationRemoteInputManager,
     private val mLaunchFullScreenIntentProvider: LaunchFullScreenIntentProvider,
+    private val mFlags: NotifPipelineFlags,
     @IncomingHeader private val mIncomingHeaderController: NodeController,
     @Main private val mExecutor: DelayableExecutor,
 ) : Coordinator {
     private val mEntriesBindingUntil = ArrayMap<String, Long>()
     private val mEntriesUpdateTimes = ArrayMap<String, Long>()
+    private val mFSIUpdateCandidates = ArrayMap<String, Long>()
     private var mEndLifetimeExtension: OnEndLifetimeExtensionCallback? = null
     private lateinit var mNotifPipeline: NotifPipeline
     private var mNow: Long = -1
@@ -278,7 +282,7 @@
         mPostedEntries.clear()
 
         // Also take this opportunity to clean up any stale entry update times
-        cleanUpEntryUpdateTimes()
+        cleanUpEntryTimes()
     }
 
     /**
@@ -384,8 +388,15 @@
         override fun onEntryAdded(entry: NotificationEntry) {
             // First check whether this notification should launch a full screen intent, and
             // launch it if needed.
-            if (mNotificationInterruptStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry)) {
+            val fsiDecision = mNotificationInterruptStateProvider.getFullScreenIntentDecision(entry)
+            if (fsiDecision != null && fsiDecision.shouldLaunch) {
+                mNotificationInterruptStateProvider.logFullScreenIntentDecision(entry, fsiDecision)
                 mLaunchFullScreenIntentProvider.launchFullScreenIntent(entry)
+            } else if (mFlags.fsiOnDNDUpdate() &&
+                fsiDecision.equals(FullScreenIntentDecision.NO_FSI_SUPPRESSED_ONLY_BY_DND)) {
+                // If DND was the only reason this entry was suppressed, note it for potential
+                // reconsideration on later ranking updates.
+                addForFSIReconsideration(entry, mSystemClock.currentTimeMillis())
             }
 
             // shouldHeadsUp includes check for whether this notification should be filtered
@@ -488,11 +499,32 @@
                 if (!isNewEnoughForRankingUpdate(entry)) continue
 
                 // The only entries we consider alerting for here are entries that have never
-                // interrupted and that now say they should heads up; if they've alerted in the
-                // past, we don't want to incorrectly alert a second time if there wasn't an
+                // interrupted and that now say they should heads up or FSI; if they've alerted in
+                // the past, we don't want to incorrectly alert a second time if there wasn't an
                 // explicit notification update.
                 if (entry.hasInterrupted()) continue
 
+                // Before potentially allowing heads-up, check for any candidates for a FSI launch.
+                // Any entry that is a candidate meets two criteria:
+                //   - was suppressed from FSI launch only by a DND suppression
+                //   - is within the recency window for reconsideration
+                // If any of these entries are no longer suppressed, launch the FSI now.
+                if (mFlags.fsiOnDNDUpdate() && isCandidateForFSIReconsideration(entry)) {
+                    val decision =
+                        mNotificationInterruptStateProvider.getFullScreenIntentDecision(entry)
+                    if (decision.shouldLaunch) {
+                        // Log both the launch of the full screen and also that this was via a
+                        // ranking update.
+                        mLogger.logEntryUpdatedToFullScreen(entry.key)
+                        mNotificationInterruptStateProvider.logFullScreenIntentDecision(
+                            entry, decision)
+                        mLaunchFullScreenIntentProvider.launchFullScreenIntent(entry)
+
+                        // if we launch the FSI then this is no longer a candidate for HUN
+                        continue
+                    }
+                }
+
                 // The cases where we should consider this notification to be updated:
                 // - if this entry is not present in PostedEntries, and is now in a shouldHeadsUp
                 //   state
@@ -528,6 +560,15 @@
     }
 
     /**
+     * Add the entry to the list of entries potentially considerable for FSI ranking update, where
+     * the provided time is the time the entry was added.
+     */
+    @VisibleForTesting
+    fun addForFSIReconsideration(entry: NotificationEntry, time: Long) {
+        mFSIUpdateCandidates[entry.key] = time
+    }
+
+    /**
      * Checks whether the entry is new enough to be updated via ranking update.
      * We want to avoid updating an entry too long after it was originally posted/updated when we're
      * only reacting to a ranking change, as relevant ranking updates are expected to come in
@@ -541,17 +582,38 @@
         return (mSystemClock.currentTimeMillis() - updateTime) <= MAX_RANKING_UPDATE_DELAY_MS
     }
 
-    private fun cleanUpEntryUpdateTimes() {
+    /**
+     * Checks whether the entry is present new enough for reconsideration for full screen launch.
+     * The time window is the same as for ranking update, but this doesn't allow a potential update
+     * to an entry with full screen intent to count for timing purposes.
+     */
+    private fun isCandidateForFSIReconsideration(entry: NotificationEntry): Boolean {
+        val addedTime = mFSIUpdateCandidates[entry.key] ?: return false
+        return (mSystemClock.currentTimeMillis() - addedTime) <= MAX_RANKING_UPDATE_DELAY_MS
+    }
+
+    private fun cleanUpEntryTimes() {
         // Because we won't update entries that are older than this amount of time anyway, clean
-        // up any entries that are too old to notify.
+        // up any entries that are too old to notify from both the general and FSI specific lists.
+
+        // Anything newer than this time is still within the window.
+        val timeThreshold = mSystemClock.currentTimeMillis() - MAX_RANKING_UPDATE_DELAY_MS
+
         val toRemove = ArraySet<String>()
         for ((key, updateTime) in mEntriesUpdateTimes) {
-            if (updateTime == null ||
-                    (mSystemClock.currentTimeMillis() - updateTime) > MAX_RANKING_UPDATE_DELAY_MS) {
+            if (updateTime == null || timeThreshold > updateTime) {
                 toRemove.add(key)
             }
         }
         mEntriesUpdateTimes.removeAll(toRemove)
+
+        val toRemoveForFSI = ArraySet<String>()
+        for ((key, addedTime) in mFSIUpdateCandidates) {
+            if (addedTime == null || timeThreshold > addedTime) {
+                toRemoveForFSI.add(key)
+            }
+        }
+        mFSIUpdateCandidates.removeAll(toRemoveForFSI)
     }
 
     /** When an action is pressed on a notification, end HeadsUp lifetime extension. */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorLogger.kt
index 473c35d..2c6bf6b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorLogger.kt
@@ -70,6 +70,14 @@
         })
     }
 
+    fun logEntryUpdatedToFullScreen(key: String) {
+        buffer.log(TAG, LogLevel.DEBUG, {
+            str1 = key
+        }, {
+            "updating entry to launch full screen intent: $str1"
+        })
+    }
+
     fun logSummaryMarkedInterrupted(summaryKey: String, childKey: String) {
         buffer.log(TAG, LogLevel.DEBUG, {
             str1 = summaryKey
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
index 6b72e96..936589c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
@@ -371,6 +371,7 @@
 
         if (!mKeyguardStateController.isShowing()) {
             final Intent cameraIntent = CameraIntents.getInsecureCameraIntent(mContext);
+            cameraIntent.putExtra(CameraIntents.EXTRA_LAUNCH_SOURCE, source);
             mCentralSurfaces.startActivityDismissingKeyguard(cameraIntent,
                     false /* onlyProvisioned */, true /* dismissShade */,
                     true /* disallowEnterPictureInPictureWhileLaunching */, null /* callback */, 0,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt
index f5b5950..cc67c84 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt
@@ -46,6 +46,7 @@
  * view-model to be reused for multiple view/view-binder bindings.
  */
 @OptIn(InternalCoroutinesApi::class)
+@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
 object WifiViewBinder {
 
     /**
@@ -59,6 +60,12 @@
 
         /** Notifies that the visibility state has changed. */
         fun onVisibilityStateChanged(@StatusBarIconView.VisibleState state: Int)
+
+        /** Notifies that the icon tint has been updated. */
+        fun onIconTintChanged(newTint: Int)
+
+        /** Notifies that the decor tint has been updated (used only for the dot). */
+        fun onDecorTintChanged(newTint: Int)
     }
 
     /** Binds the view to the view-model, continuing to update the former based on the latter. */
@@ -82,6 +89,9 @@
         @StatusBarIconView.VisibleState
         val visibilityState: MutableStateFlow<Int> = MutableStateFlow(STATE_HIDDEN)
 
+        val iconTint: MutableStateFlow<Int> = MutableStateFlow(viewModel.defaultColor)
+        val decorTint: MutableStateFlow<Int> = MutableStateFlow(viewModel.defaultColor)
+
         view.repeatWhenAttached {
             repeatOnLifecycle(Lifecycle.State.STARTED) {
                 launch {
@@ -101,7 +111,7 @@
                 }
 
                 launch {
-                    viewModel.tint.collect { tint ->
+                    iconTint.collect { tint ->
                         val tintList = ColorStateList.valueOf(tint)
                         iconView.imageTintList = tintList
                         activityInView.imageTintList = tintList
@@ -110,6 +120,8 @@
                     }
                 }
 
+                launch { decorTint.collect { tint -> dotView.setDecorColor(tint) } }
+
                 launch {
                     viewModel.isActivityInViewVisible.distinctUntilChanged().collect { visible ->
                         activityInView.isVisible = visible
@@ -144,6 +156,20 @@
             override fun onVisibilityStateChanged(@StatusBarIconView.VisibleState state: Int) {
                 visibilityState.value = state
             }
+
+            override fun onIconTintChanged(newTint: Int) {
+                if (viewModel.useDebugColoring) {
+                    return
+                }
+                iconTint.value = newTint
+            }
+
+            override fun onDecorTintChanged(newTint: Int) {
+                if (viewModel.useDebugColoring) {
+                    return
+                }
+                decorTint.value = newTint
+            }
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiView.kt
index a45076b..be7782c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiView.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiView.kt
@@ -22,6 +22,7 @@
 import android.view.Gravity
 import android.view.LayoutInflater
 import com.android.systemui.R
+import com.android.systemui.plugins.DarkIconDispatcher
 import com.android.systemui.statusbar.BaseStatusBarFrameLayout
 import com.android.systemui.statusbar.StatusBarIconView
 import com.android.systemui.statusbar.StatusBarIconView.STATE_DOT
@@ -51,18 +52,20 @@
             binding.onVisibilityStateChanged(value)
         }
 
-    override fun onDarkChanged(areas: ArrayList<Rect>?, darkIntensity: Float, tint: Int) {
-        // TODO(b/238425913)
-    }
-
     override fun getSlot() = slot
 
+    override fun onDarkChanged(areas: ArrayList<Rect>?, darkIntensity: Float, tint: Int) {
+        val newTint = DarkIconDispatcher.getTint(areas, this, tint)
+        binding.onIconTintChanged(newTint)
+        binding.onDecorTintChanged(newTint)
+    }
+
     override fun setStaticDrawableColor(color: Int) {
-        // TODO(b/238425913)
+        binding.onIconTintChanged(color)
     }
 
     override fun setDecorColor(color: Int) {
-        // TODO(b/238425913)
+        binding.onDecorTintChanged(color)
     }
 
     override fun setVisibleState(@StatusBarIconView.VisibleState state: Int, animate: Boolean) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/LocationBasedWifiViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/LocationBasedWifiViewModel.kt
index e35a8fe..a4615cc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/LocationBasedWifiViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/LocationBasedWifiViewModel.kt
@@ -21,7 +21,6 @@
 import com.android.systemui.statusbar.pipeline.wifi.ui.model.WifiIcon
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.flowOf
 
 /**
  * A view model for a wifi icon in a specific location. This allows us to control parameters that
@@ -48,24 +47,12 @@
     /** True if the airplane spacer view should be visible. */
     val isAirplaneSpacerVisible: Flow<Boolean>,
 ) {
-    /** The color that should be used to tint the icon. */
-    val tint: Flow<Int> =
-        flowOf(
-            if (statusBarPipelineFlags.useWifiDebugColoring()) {
-                debugTint
-            } else {
-                DEFAULT_TINT
-            }
-        )
+    val useDebugColoring: Boolean = statusBarPipelineFlags.useWifiDebugColoring()
 
-    companion object {
-        /**
-         * A default icon tint.
-         *
-         * TODO(b/238425913): The tint is actually controlled by
-         * [com.android.systemui.statusbar.phone.StatusBarIconController.TintedIconManager]. We
-         * should use that logic instead of white as a default.
-         */
-        private const val DEFAULT_TINT = Color.WHITE
-    }
+    val defaultColor: Int =
+        if (useDebugColoring) {
+            debugTint
+        } else {
+            Color.WHITE
+        }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/touch/TouchInsetManager.java b/packages/SystemUI/src/com/android/systemui/touch/TouchInsetManager.java
index 3d07491..166ac9e 100644
--- a/packages/SystemUI/src/com/android/systemui/touch/TouchInsetManager.java
+++ b/packages/SystemUI/src/com/android/systemui/touch/TouchInsetManager.java
@@ -18,41 +18,58 @@
 
 import android.graphics.Rect;
 import android.graphics.Region;
+import android.util.Log;
+import android.view.AttachedSurfaceControl;
 import android.view.View;
-import android.view.ViewRootImpl;
 
 import androidx.concurrent.futures.CallbackToFutureAdapter;
 
+import com.android.systemui.dagger.qualifiers.Main;
+
 import com.google.common.util.concurrent.ListenableFuture;
 
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.concurrent.Executor;
 
+import javax.inject.Inject;
+
 /**
  * {@link TouchInsetManager} handles setting the touchable inset regions for a given View. This
  * is useful for passing through touch events for all but select areas.
  */
 public class TouchInsetManager {
+    private static final String TAG = "TouchInsetManager";
     /**
      * {@link TouchInsetSession} provides an individualized session with the
      * {@link TouchInsetManager}, linking any action to the client.
      */
     public static class TouchInsetSession {
         private final TouchInsetManager mManager;
-
         private final HashSet<View> mTrackedViews;
         private final Executor mExecutor;
 
         private final View.OnLayoutChangeListener mOnLayoutChangeListener =
                 (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom)
-                        -> updateTouchRegion();
+                        -> updateTouchRegions();
+
+        private final View.OnAttachStateChangeListener mAttachListener =
+                new View.OnAttachStateChangeListener() {
+                    @Override
+                    public void onViewAttachedToWindow(View v) {
+                        updateTouchRegions();
+                    }
+
+                    @Override
+                    public void onViewDetachedFromWindow(View v) {
+                        updateTouchRegions();
+                    }
+                };
 
         /**
          * Default constructor
          * @param manager The parent {@link TouchInsetManager} which will be affected by actions on
          *                this session.
-         * @param rootView The parent of views that will be tracked.
          * @param executor An executor for marshalling operations.
          */
         TouchInsetSession(TouchInsetManager manager, Executor executor) {
@@ -68,8 +85,9 @@
         public void addViewToTracking(View view) {
             mExecutor.execute(() -> {
                 mTrackedViews.add(view);
+                view.addOnAttachStateChangeListener(mAttachListener);
                 view.addOnLayoutChangeListener(mOnLayoutChangeListener);
-                updateTouchRegion();
+                updateTouchRegions();
             });
         }
 
@@ -81,22 +99,30 @@
             mExecutor.execute(() -> {
                 mTrackedViews.remove(view);
                 view.removeOnLayoutChangeListener(mOnLayoutChangeListener);
-                updateTouchRegion();
+                view.removeOnAttachStateChangeListener(mAttachListener);
+                updateTouchRegions();
             });
         }
 
-        private void updateTouchRegion() {
-            final Region cumulativeRegion = Region.obtain();
+        private void updateTouchRegions() {
+            mExecutor.execute(() -> {
+                final HashMap<AttachedSurfaceControl, Region> affectedSurfaces = new HashMap<>();
+                mTrackedViews.stream().forEach(view -> {
+                    if (!view.isAttachedToWindow()) {
+                        return;
+                    }
 
-            mTrackedViews.stream().forEach(view -> {
-                final Rect boundaries = new Rect();
-                view.getBoundsOnScreen(boundaries);
-                cumulativeRegion.op(boundaries, Region.Op.UNION);
+                    final AttachedSurfaceControl surface = view.getRootSurfaceControl();
+
+                    if (!affectedSurfaces.containsKey(surface)) {
+                        affectedSurfaces.put(surface, Region.obtain());
+                    }
+                    final Rect boundaries = new Rect();
+                    view.getBoundsOnScreen(boundaries);
+                    affectedSurfaces.get(surface).op(boundaries, Region.Op.UNION);
+                });
+                mManager.setTouchRegions(this, affectedSurfaces);
             });
-
-            mManager.setTouchRegion(this, cumulativeRegion);
-
-            cumulativeRegion.recycle();
         }
 
         /**
@@ -110,32 +136,18 @@
         }
     }
 
-    private final HashMap<TouchInsetSession, Region> mDefinedRegions = new HashMap<>();
+    private final HashMap<TouchInsetSession, HashMap<AttachedSurfaceControl, Region>>
+            mSessionRegions = new HashMap<>();
+    private final HashMap<AttachedSurfaceControl, Region> mLastAffectedSurfaces = new HashMap();
     private final Executor mExecutor;
-    private final View mRootView;
-
-    private final View.OnAttachStateChangeListener mAttachListener =
-            new View.OnAttachStateChangeListener() {
-                @Override
-                public void onViewAttachedToWindow(View v) {
-                    updateTouchInset();
-                }
-
-                @Override
-                public void onViewDetachedFromWindow(View v) {
-                }
-            };
 
     /**
      * Default constructor.
      * @param executor An {@link Executor} to marshal all operations on.
-     * @param rootView The root {@link View} for all views in sessions.
      */
-    public TouchInsetManager(Executor executor, View rootView) {
+    @Inject
+    public TouchInsetManager(@Main Executor executor) {
         mExecutor = executor;
-        mRootView = rootView;
-        mRootView.addOnAttachStateChangeListener(mAttachListener);
-
     }
 
     /**
@@ -151,47 +163,68 @@
     public ListenableFuture<Boolean> checkWithinTouchRegion(int x, int y) {
         return CallbackToFutureAdapter.getFuture(completer -> {
             mExecutor.execute(() -> completer.set(
-                    mDefinedRegions.values().stream().anyMatch(region -> region.contains(x, y))));
+                    mLastAffectedSurfaces.values().stream().anyMatch(
+                            region -> region.contains(x, y))));
 
             return "DreamOverlayTouchMonitor::checkWithinTouchRegion";
         });
     }
 
-    private void updateTouchInset() {
-        final ViewRootImpl viewRootImpl = mRootView.getViewRootImpl();
+    private void updateTouchInsets() {
+        // Get affected
+        final HashMap<AttachedSurfaceControl, Region> affectedSurfaces = new HashMap<>();
+        mSessionRegions.values().stream().forEach(regionMapping -> {
+            regionMapping.entrySet().stream().forEach(entry -> {
+                final AttachedSurfaceControl surface = entry.getKey();
+                if (!affectedSurfaces.containsKey(surface)) {
+                    affectedSurfaces.put(surface, Region.obtain());
+                }
 
-        if (viewRootImpl == null) {
+                affectedSurfaces.get(surface).op(entry.getValue(), Region.Op.UNION);
+            });
+        });
+
+        affectedSurfaces.entrySet().stream().forEach(entry -> {
+            entry.getKey().setTouchableRegion(entry.getValue());
+        });
+
+        mLastAffectedSurfaces.entrySet().forEach(entry -> {
+            final AttachedSurfaceControl surface = entry.getKey();
+            if (!affectedSurfaces.containsKey(surface)) {
+                surface.setTouchableRegion(null);
+            }
+            entry.getValue().recycle();
+        });
+
+        mLastAffectedSurfaces.clear();
+        mLastAffectedSurfaces.putAll(affectedSurfaces);
+    }
+
+    protected void setTouchRegions(TouchInsetSession session,
+            HashMap<AttachedSurfaceControl, Region> regions) {
+        mExecutor.execute(() -> {
+            recycleRegions(session);
+            mSessionRegions.put(session, regions);
+            updateTouchInsets();
+        });
+    }
+
+    private void recycleRegions(TouchInsetSession session) {
+        if (!mSessionRegions.containsKey(session)) {
+            Log.w(TAG,  "Removing a session with no regions:" + session);
             return;
         }
 
-        final Region aggregateRegion = Region.obtain();
-
-        for (Region region : mDefinedRegions.values()) {
-            aggregateRegion.op(region, Region.Op.UNION);
+        for (Region region : mSessionRegions.get(session).values()) {
+            region.recycle();
         }
-
-        viewRootImpl.setTouchableRegion(aggregateRegion);
-
-        aggregateRegion.recycle();
-    }
-
-    protected void setTouchRegion(TouchInsetSession session, Region region) {
-        final Region introducedRegion = Region.obtain(region);
-        mExecutor.execute(() -> {
-            mDefinedRegions.put(session, introducedRegion);
-            updateTouchInset();
-        });
     }
 
     private void clearRegion(TouchInsetSession session) {
         mExecutor.execute(() -> {
-            final Region storedRegion = mDefinedRegions.remove(session);
-
-            if (storedRegion != null) {
-                storedRegion.recycle();
-            }
-
-            updateTouchInset();
+            recycleRegions(session);
+            mSessionRegions.remove(session);
+            updateTouchInsets();
         });
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java
index 8bbaf3d..1059543 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java
@@ -19,6 +19,7 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyZeroInteractions;
@@ -87,6 +88,7 @@
         when(mAbsKeyInputView.isAttachedToWindow()).thenReturn(true);
         when(mAbsKeyInputView.requireViewById(R.id.bouncer_message_area))
                 .thenReturn(mKeyguardMessageArea);
+        when(mAbsKeyInputView.getResources()).thenReturn(getContext().getResources());
         mKeyguardAbsKeyInputViewController = new KeyguardAbsKeyInputViewController(mAbsKeyInputView,
                 mKeyguardUpdateMonitor, mSecurityMode, mLockPatternUtils, mKeyguardSecurityCallback,
                 mKeyguardMessageAreaControllerFactory, mLatencyTracker, mFalsingCollector,
@@ -99,6 +101,11 @@
             public void onResume(int reason) {
                 super.onResume(reason);
             }
+
+            @Override
+            protected int getInitialMessageResId() {
+                return 0;
+            }
         };
         mKeyguardAbsKeyInputViewController.init();
         reset(mKeyguardMessageAreaController);  // Clear out implicit call to init.
@@ -125,4 +132,22 @@
         verifyZeroInteractions(mKeyguardSecurityCallback);
         verifyZeroInteractions(mKeyguardMessageAreaController);
     }
+
+    @Test
+    public void onPromptReasonNone_doesNotSetMessage() {
+        mKeyguardAbsKeyInputViewController.showPromptReason(0);
+        verify(mKeyguardMessageAreaController, never()).setMessage(
+                getContext().getResources().getString(R.string.kg_prompt_reason_restart_password),
+                false);
+    }
+
+    @Test
+    public void onPromptReason_setsMessage() {
+        when(mAbsKeyInputView.getPromptReasonStringRes(1)).thenReturn(
+                R.string.kg_prompt_reason_restart_password);
+        mKeyguardAbsKeyInputViewController.showPromptReason(1);
+        verify(mKeyguardMessageAreaController).setMessage(
+                getContext().getResources().getString(R.string.kg_prompt_reason_restart_password),
+                false);
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt
index d20be56..d912793 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt
@@ -30,64 +30,54 @@
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyBoolean
+import org.mockito.ArgumentMatchers.anyString
 import org.mockito.Mock
 import org.mockito.Mockito
-import org.mockito.Mockito.`when`
 import org.mockito.Mockito.never
 import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
 import org.mockito.MockitoAnnotations
 
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
 @TestableLooper.RunWithLooper
 class KeyguardPasswordViewControllerTest : SysuiTestCase() {
-    @Mock
-    private lateinit var keyguardPasswordView: KeyguardPasswordView
-    @Mock
-    private lateinit var passwordEntry: EditText
-    @Mock
-    lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
-    @Mock
-    lateinit var securityMode: KeyguardSecurityModel.SecurityMode
-    @Mock
-    lateinit var lockPatternUtils: LockPatternUtils
-    @Mock
-    lateinit var keyguardSecurityCallback: KeyguardSecurityCallback
-    @Mock
-    lateinit var messageAreaControllerFactory: KeyguardMessageAreaController.Factory
-    @Mock
-    lateinit var latencyTracker: LatencyTracker
-    @Mock
-    lateinit var inputMethodManager: InputMethodManager
-    @Mock
-    lateinit var emergencyButtonController: EmergencyButtonController
-    @Mock
-    lateinit var mainExecutor: DelayableExecutor
-    @Mock
-    lateinit var falsingCollector: FalsingCollector
-    @Mock
-    lateinit var keyguardViewController: KeyguardViewController
-    @Mock
-    private lateinit var mKeyguardMessageArea: BouncerKeyguardMessageArea
-    @Mock
-    private lateinit var mKeyguardMessageAreaController:
-        KeyguardMessageAreaController<BouncerKeyguardMessageArea>
+  @Mock private lateinit var keyguardPasswordView: KeyguardPasswordView
+  @Mock private lateinit var passwordEntry: EditText
+  @Mock lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
+  @Mock lateinit var securityMode: KeyguardSecurityModel.SecurityMode
+  @Mock lateinit var lockPatternUtils: LockPatternUtils
+  @Mock lateinit var keyguardSecurityCallback: KeyguardSecurityCallback
+  @Mock lateinit var messageAreaControllerFactory: KeyguardMessageAreaController.Factory
+  @Mock lateinit var latencyTracker: LatencyTracker
+  @Mock lateinit var inputMethodManager: InputMethodManager
+  @Mock lateinit var emergencyButtonController: EmergencyButtonController
+  @Mock lateinit var mainExecutor: DelayableExecutor
+  @Mock lateinit var falsingCollector: FalsingCollector
+  @Mock lateinit var keyguardViewController: KeyguardViewController
+  @Mock private lateinit var mKeyguardMessageArea: BouncerKeyguardMessageArea
+  @Mock
+  private lateinit var mKeyguardMessageAreaController:
+      KeyguardMessageAreaController<BouncerKeyguardMessageArea>
 
-    private lateinit var keyguardPasswordViewController: KeyguardPasswordViewController
+  private lateinit var keyguardPasswordViewController: KeyguardPasswordViewController
 
-    @Before
-    fun setup() {
-        MockitoAnnotations.initMocks(this)
-        Mockito.`when`(
-            keyguardPasswordView
-                .requireViewById<BouncerKeyguardMessageArea>(R.id.bouncer_message_area)
-        ).thenReturn(mKeyguardMessageArea)
-        Mockito.`when`(messageAreaControllerFactory.create(mKeyguardMessageArea))
-            .thenReturn(mKeyguardMessageAreaController)
-        Mockito.`when`(keyguardPasswordView.passwordTextViewId).thenReturn(R.id.passwordEntry)
-        Mockito.`when`(keyguardPasswordView.findViewById<EditText>(R.id.passwordEntry)
-        ).thenReturn(passwordEntry)
-        keyguardPasswordViewController = KeyguardPasswordViewController(
+  @Before
+  fun setup() {
+    MockitoAnnotations.initMocks(this)
+    Mockito.`when`(
+            keyguardPasswordView.requireViewById<BouncerKeyguardMessageArea>(
+                R.id.bouncer_message_area))
+        .thenReturn(mKeyguardMessageArea)
+    Mockito.`when`(messageAreaControllerFactory.create(mKeyguardMessageArea))
+        .thenReturn(mKeyguardMessageAreaController)
+    Mockito.`when`(keyguardPasswordView.passwordTextViewId).thenReturn(R.id.passwordEntry)
+    Mockito.`when`(keyguardPasswordView.findViewById<EditText>(R.id.passwordEntry))
+        .thenReturn(passwordEntry)
+    `when`(keyguardPasswordView.resources).thenReturn(context.resources)
+    keyguardPasswordViewController =
+        KeyguardPasswordViewController(
             keyguardPasswordView,
             keyguardUpdateMonitor,
             securityMode,
@@ -100,51 +90,48 @@
             mainExecutor,
             mContext.resources,
             falsingCollector,
-            keyguardViewController
-        )
-    }
+            keyguardViewController)
+  }
 
-    @Test
-    fun testFocusWhenBouncerIsShown() {
-        Mockito.`when`(keyguardViewController.isBouncerShowing).thenReturn(true)
-        Mockito.`when`(keyguardPasswordView.isShown).thenReturn(true)
-        keyguardPasswordViewController.onResume(KeyguardSecurityView.VIEW_REVEALED)
-        keyguardPasswordView.post {
-            verify(keyguardPasswordView).requestFocus()
-            verify(keyguardPasswordView).showKeyboard()
-        }
+  @Test
+  fun testFocusWhenBouncerIsShown() {
+    Mockito.`when`(keyguardViewController.isBouncerShowing).thenReturn(true)
+    Mockito.`when`(keyguardPasswordView.isShown).thenReturn(true)
+    keyguardPasswordViewController.onResume(KeyguardSecurityView.VIEW_REVEALED)
+    keyguardPasswordView.post {
+      verify(keyguardPasswordView).requestFocus()
+      verify(keyguardPasswordView).showKeyboard()
     }
+  }
 
-    @Test
-    fun testDoNotFocusWhenBouncerIsHidden() {
-        Mockito.`when`(keyguardViewController.isBouncerShowing).thenReturn(false)
-        Mockito.`when`(keyguardPasswordView.isShown).thenReturn(true)
-        keyguardPasswordViewController.onResume(KeyguardSecurityView.VIEW_REVEALED)
-        verify(keyguardPasswordView, never()).requestFocus()
-    }
+  @Test
+  fun testDoNotFocusWhenBouncerIsHidden() {
+    Mockito.`when`(keyguardViewController.isBouncerShowing).thenReturn(false)
+    Mockito.`when`(keyguardPasswordView.isShown).thenReturn(true)
+    keyguardPasswordViewController.onResume(KeyguardSecurityView.VIEW_REVEALED)
+    verify(keyguardPasswordView, never()).requestFocus()
+  }
 
-    @Test
-    fun testHideKeyboardWhenOnPause() {
-        keyguardPasswordViewController.onPause()
-        keyguardPasswordView.post {
-            verify(keyguardPasswordView).clearFocus()
-            verify(keyguardPasswordView).hideKeyboard()
-        }
+  @Test
+  fun testHideKeyboardWhenOnPause() {
+    keyguardPasswordViewController.onPause()
+    keyguardPasswordView.post {
+      verify(keyguardPasswordView).clearFocus()
+      verify(keyguardPasswordView).hideKeyboard()
     }
+  }
 
-    @Test
-    fun startAppearAnimation() {
-        keyguardPasswordViewController.startAppearAnimation()
-        verify(mKeyguardMessageAreaController).setMessage(R.string.keyguard_enter_your_password)
-    }
+  @Test
+  fun startAppearAnimation() {
+    keyguardPasswordViewController.startAppearAnimation()
+    verify(mKeyguardMessageAreaController)
+        .setMessage(context.resources.getString(R.string.keyguard_enter_your_password), false)
+  }
 
-    @Test
-    fun startAppearAnimation_withExistingMessage() {
-        `when`(mKeyguardMessageAreaController.message).thenReturn("Unlock to continue.")
-        keyguardPasswordViewController.startAppearAnimation()
-        verify(
-            mKeyguardMessageAreaController,
-            never()
-        ).setMessage(R.string.keyguard_enter_your_password)
-    }
+  @Test
+  fun startAppearAnimation_withExistingMessage() {
+    `when`(mKeyguardMessageAreaController.message).thenReturn("Unlock to continue.")
+    keyguardPasswordViewController.startAppearAnimation()
+    verify(mKeyguardMessageAreaController, never()).setMessage(anyString(), anyBoolean())
+  }
 }
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
index b3d1c8f..85dbdb8 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
@@ -30,97 +30,93 @@
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyBoolean
+import org.mockito.ArgumentMatchers.anyString
 import org.mockito.Mock
+import org.mockito.Mockito.never
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.`when`
-import org.mockito.Mockito.never
 import org.mockito.MockitoAnnotations
 
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
 @TestableLooper.RunWithLooper
 class KeyguardPatternViewControllerTest : SysuiTestCase() {
-    @Mock
-    private lateinit var mKeyguardPatternView: KeyguardPatternView
+  @Mock private lateinit var mKeyguardPatternView: KeyguardPatternView
 
-    @Mock
-    private lateinit var mKeyguardUpdateMonitor: KeyguardUpdateMonitor
+  @Mock private lateinit var mKeyguardUpdateMonitor: KeyguardUpdateMonitor
 
-    @Mock
-    private lateinit var mSecurityMode: KeyguardSecurityModel.SecurityMode
+  @Mock private lateinit var mSecurityMode: KeyguardSecurityModel.SecurityMode
 
-    @Mock
-    private lateinit var mLockPatternUtils: LockPatternUtils
+  @Mock private lateinit var mLockPatternUtils: LockPatternUtils
 
-    @Mock
-    private lateinit var mKeyguardSecurityCallback: KeyguardSecurityCallback
+  @Mock private lateinit var mKeyguardSecurityCallback: KeyguardSecurityCallback
 
-    @Mock
-    private lateinit var mLatencyTracker: LatencyTracker
-    private var mFalsingCollector: FalsingCollector = FalsingCollectorFake()
+  @Mock private lateinit var mLatencyTracker: LatencyTracker
+  private var mFalsingCollector: FalsingCollector = FalsingCollectorFake()
 
-    @Mock
-    private lateinit var mEmergencyButtonController: EmergencyButtonController
+  @Mock private lateinit var mEmergencyButtonController: EmergencyButtonController
 
-    @Mock
-    private lateinit
-    var mKeyguardMessageAreaControllerFactory: KeyguardMessageAreaController.Factory
+  @Mock
+  private lateinit var mKeyguardMessageAreaControllerFactory: KeyguardMessageAreaController.Factory
 
-    @Mock
-    private lateinit var mKeyguardMessageArea: BouncerKeyguardMessageArea
+  @Mock private lateinit var mKeyguardMessageArea: BouncerKeyguardMessageArea
 
-    @Mock
-    private lateinit var mKeyguardMessageAreaController:
-        KeyguardMessageAreaController<BouncerKeyguardMessageArea>
+  @Mock
+  private lateinit var mKeyguardMessageAreaController:
+      KeyguardMessageAreaController<BouncerKeyguardMessageArea>
 
-    @Mock
-    private lateinit var mLockPatternView: LockPatternView
+  @Mock private lateinit var mLockPatternView: LockPatternView
 
-    @Mock
-    private lateinit var mPostureController: DevicePostureController
+  @Mock private lateinit var mPostureController: DevicePostureController
 
-    private lateinit var mKeyguardPatternViewController: KeyguardPatternViewController
+  private lateinit var mKeyguardPatternViewController: KeyguardPatternViewController
 
-    @Before
-    fun setup() {
-        MockitoAnnotations.initMocks(this)
-        `when`(mKeyguardPatternView.isAttachedToWindow).thenReturn(true)
-        `when`(mKeyguardPatternView
-            .requireViewById<BouncerKeyguardMessageArea>(R.id.bouncer_message_area))
-            .thenReturn(mKeyguardMessageArea)
-        `when`(mKeyguardPatternView.findViewById<LockPatternView>(R.id.lockPatternView))
-            .thenReturn(mLockPatternView)
-        `when`(mKeyguardMessageAreaControllerFactory.create(mKeyguardMessageArea))
-            .thenReturn(mKeyguardMessageAreaController)
-        mKeyguardPatternViewController = KeyguardPatternViewController(
+  @Before
+  fun setup() {
+    MockitoAnnotations.initMocks(this)
+    `when`(mKeyguardPatternView.isAttachedToWindow).thenReturn(true)
+    `when`(
+            mKeyguardPatternView.requireViewById<BouncerKeyguardMessageArea>(
+                R.id.bouncer_message_area))
+        .thenReturn(mKeyguardMessageArea)
+    `when`(mKeyguardPatternView.findViewById<LockPatternView>(R.id.lockPatternView))
+        .thenReturn(mLockPatternView)
+    `when`(mKeyguardMessageAreaControllerFactory.create(mKeyguardMessageArea))
+        .thenReturn(mKeyguardMessageAreaController)
+    `when`(mKeyguardPatternView.resources).thenReturn(context.resources)
+    mKeyguardPatternViewController =
+        KeyguardPatternViewController(
             mKeyguardPatternView,
-            mKeyguardUpdateMonitor, mSecurityMode, mLockPatternUtils, mKeyguardSecurityCallback,
-            mLatencyTracker, mFalsingCollector, mEmergencyButtonController,
-            mKeyguardMessageAreaControllerFactory, mPostureController
-        )
-    }
+            mKeyguardUpdateMonitor,
+            mSecurityMode,
+            mLockPatternUtils,
+            mKeyguardSecurityCallback,
+            mLatencyTracker,
+            mFalsingCollector,
+            mEmergencyButtonController,
+            mKeyguardMessageAreaControllerFactory,
+            mPostureController)
+  }
 
-    @Test
-    fun onPause_resetsText() {
-        mKeyguardPatternViewController.init()
-        mKeyguardPatternViewController.onPause()
-        verify(mKeyguardMessageAreaController).setMessage(R.string.keyguard_enter_your_pattern)
-    }
+  @Test
+  fun onPause_resetsText() {
+    mKeyguardPatternViewController.init()
+    mKeyguardPatternViewController.onPause()
+    verify(mKeyguardMessageAreaController).setMessage(R.string.keyguard_enter_your_pattern)
+  }
 
+  @Test
+  fun startAppearAnimation() {
+    mKeyguardPatternViewController.startAppearAnimation()
+    verify(mKeyguardMessageAreaController)
+        .setMessage(context.resources.getString(R.string.keyguard_enter_your_pattern), false)
+  }
 
-    @Test
-    fun startAppearAnimation() {
-        mKeyguardPatternViewController.startAppearAnimation()
-        verify(mKeyguardMessageAreaController).setMessage(R.string.keyguard_enter_your_pattern)
-    }
-
-    @Test
-    fun startAppearAnimation_withExistingMessage() {
-        `when`(mKeyguardMessageAreaController.message).thenReturn("Unlock to continue.")
-        mKeyguardPatternViewController.startAppearAnimation()
-        verify(
-            mKeyguardMessageAreaController,
-            never()
-        ).setMessage(R.string.keyguard_enter_your_password)
-    }
+  @Test
+  fun startAppearAnimation_withExistingMessage() {
+    `when`(mKeyguardMessageAreaController.message).thenReturn("Unlock to continue.")
+    mKeyguardPatternViewController.startAppearAnimation()
+    verify(mKeyguardMessageAreaController, never()).setMessage(anyString(), anyBoolean())
+  }
 }
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java
index ce1101f..b742100 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java
@@ -16,6 +16,8 @@
 
 package com.android.keyguard;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -113,4 +115,9 @@
         mKeyguardPinViewController.onResume(KeyguardSecurityView.SCREEN_ON);
         verify(mPasswordEntry).requestFocus();
     }
+
+    @Test
+    public void testGetInitialMessageResId() {
+        assertThat(mKeyguardPinViewController.getInitialMessageResId()).isNotEqualTo(0);
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
index 8bcfe6f..cdb7bbb 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
@@ -31,10 +31,13 @@
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyBoolean
+import org.mockito.ArgumentMatchers.anyString
 import org.mockito.Mock
 import org.mockito.Mockito
 import org.mockito.Mockito.any
 import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
 import org.mockito.MockitoAnnotations
 
 @SmallTest
@@ -79,6 +82,7 @@
                 keyguardMessageAreaControllerFactory.create(any(KeyguardMessageArea::class.java))
             )
             .thenReturn(keyguardMessageAreaController)
+        `when`(keyguardPinView.resources).thenReturn(context.resources)
         pinViewController =
             KeyguardPinViewController(
                 keyguardPinView,
@@ -98,14 +102,14 @@
     @Test
     fun startAppearAnimation() {
         pinViewController.startAppearAnimation()
-        verify(keyguardMessageAreaController).setMessage(R.string.keyguard_enter_your_pin)
+        verify(keyguardMessageAreaController)
+            .setMessage(context.resources.getString(R.string.keyguard_enter_your_pin), false)
     }
 
     @Test
     fun startAppearAnimation_withExistingMessage() {
         Mockito.`when`(keyguardMessageAreaController.message).thenReturn("Unlock to continue.")
         pinViewController.startAppearAnimation()
-        verify(keyguardMessageAreaController, Mockito.never())
-            .setMessage(R.string.keyguard_enter_your_password)
+        verify(keyguardMessageAreaController, Mockito.never()).setMessage(anyString(), anyBoolean())
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index bdd29aa..0f4cf41 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -2083,6 +2083,96 @@
         assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isFalse();
     }
 
+    @Test
+    public void fingerprintFailure_requestActiveUnlock_dismissKeyguard()
+            throws RemoteException {
+        // GIVEN shouldTriggerActiveUnlock
+        bouncerFullyVisible();
+        when(mLockPatternUtils.isSecure(KeyguardUpdateMonitor.getCurrentUser())).thenReturn(true);
+
+        // GIVEN active unlock triggers on biometric failures
+        when(mActiveUnlockConfig.shouldAllowActiveUnlockFromOrigin(
+                ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.BIOMETRIC_FAIL))
+                .thenReturn(true);
+
+        // WHEN fingerprint fails
+        mKeyguardUpdateMonitor.mFingerprintAuthenticationCallback.onAuthenticationFailed();
+
+        // ALWAYS request unlock with a keyguard dismissal
+        verify(mTrustManager).reportUserRequestedUnlock(eq(KeyguardUpdateMonitor.getCurrentUser()),
+                eq(true));
+    }
+
+    @Test
+    public void faceNonBypassFailure_requestActiveUnlock_doesNotDismissKeyguard()
+            throws RemoteException {
+        // GIVEN shouldTriggerActiveUnlock
+        when(mAuthController.isUdfpsFingerDown()).thenReturn(false);
+        keyguardIsVisible();
+        keyguardNotGoingAway();
+        statusBarShadeIsNotLocked();
+        when(mLockPatternUtils.isSecure(KeyguardUpdateMonitor.getCurrentUser())).thenReturn(true);
+
+        // GIVEN active unlock triggers on biometric failures
+        when(mActiveUnlockConfig.shouldAllowActiveUnlockFromOrigin(
+                ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.BIOMETRIC_FAIL))
+                .thenReturn(true);
+
+        // WHEN face fails & bypass is not allowed
+        lockscreenBypassIsNotAllowed();
+        mKeyguardUpdateMonitor.mFaceAuthenticationCallback.onAuthenticationFailed();
+
+        // THEN request unlock with NO keyguard dismissal
+        verify(mTrustManager).reportUserRequestedUnlock(eq(KeyguardUpdateMonitor.getCurrentUser()),
+                eq(false));
+    }
+
+    @Test
+    public void faceBypassFailure_requestActiveUnlock_dismissKeyguard()
+            throws RemoteException {
+        // GIVEN shouldTriggerActiveUnlock
+        when(mAuthController.isUdfpsFingerDown()).thenReturn(false);
+        keyguardIsVisible();
+        keyguardNotGoingAway();
+        statusBarShadeIsNotLocked();
+        when(mLockPatternUtils.isSecure(KeyguardUpdateMonitor.getCurrentUser())).thenReturn(true);
+
+        // GIVEN active unlock triggers on biometric failures
+        when(mActiveUnlockConfig.shouldAllowActiveUnlockFromOrigin(
+                ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.BIOMETRIC_FAIL))
+                .thenReturn(true);
+
+        // WHEN face fails & bypass is not allowed
+        lockscreenBypassIsAllowed();
+        mKeyguardUpdateMonitor.mFaceAuthenticationCallback.onAuthenticationFailed();
+
+        // THEN request unlock with a keyguard dismissal
+        verify(mTrustManager).reportUserRequestedUnlock(eq(KeyguardUpdateMonitor.getCurrentUser()),
+                eq(true));
+    }
+
+    @Test
+    public void faceNonBypassFailure_requestActiveUnlock_dismissKeyguard()
+            throws RemoteException {
+        // GIVEN shouldTriggerActiveUnlock
+        when(mAuthController.isUdfpsFingerDown()).thenReturn(false);
+        lockscreenBypassIsNotAllowed();
+        when(mLockPatternUtils.isSecure(KeyguardUpdateMonitor.getCurrentUser())).thenReturn(true);
+
+        // GIVEN active unlock triggers on biometric failures
+        when(mActiveUnlockConfig.shouldAllowActiveUnlockFromOrigin(
+                ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.BIOMETRIC_FAIL))
+                .thenReturn(true);
+
+        // WHEN face fails & on the bouncer
+        bouncerFullyVisible();
+        mKeyguardUpdateMonitor.mFaceAuthenticationCallback.onAuthenticationFailed();
+
+        // THEN request unlock with a keyguard dismissal
+        verify(mTrustManager).reportUserRequestedUnlock(eq(KeyguardUpdateMonitor.getCurrentUser()),
+                eq(true));
+    }
+
     private void userDeviceLockDown() {
         when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(false);
         when(mStrongAuthTracker.getStrongAuthForUser(mCurrentUserId))
@@ -2100,6 +2190,9 @@
     }
 
     private void mockCanBypassLockscreen(boolean canBypass) {
+        // force update the isFaceEnrolled cache:
+        mKeyguardUpdateMonitor.isFaceAuthEnabledForUser(getCurrentUser());
+
         mKeyguardUpdateMonitor.setKeyguardBypassController(mKeyguardBypassController);
         when(mKeyguardBypassController.canBypass()).thenReturn(canBypass);
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/camera/CameraGestureHelperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/camera/CameraGestureHelperTest.kt
index ca94ea8..262b4b8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/camera/CameraGestureHelperTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/camera/CameraGestureHelperTest.kt
@@ -301,7 +301,7 @@
         val intent = intentCaptor.value
 
         assertThat(CameraIntents.isSecureCameraIntent(intent)).isEqualTo(isSecure)
-        assertThat(intent.getIntExtra(CameraGestureHelper.EXTRA_CAMERA_LAUNCH_SOURCE, -1))
+        assertThat(intent.getIntExtra(CameraIntents.EXTRA_LAUNCH_SOURCE, -1))
             .isEqualTo(source)
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt
index 779788a..d172c9a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt
@@ -38,6 +38,7 @@
 import com.android.systemui.controls.controller.ControlsController
 import com.android.systemui.controls.controller.StructureInfo
 import com.android.systemui.controls.management.ControlsListingController
+import com.android.systemui.controls.management.ControlsProviderSelectorActivity
 import com.android.systemui.controls.settings.FakeControlsSettingsRepository
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.plugins.ActivityStarter
@@ -53,6 +54,7 @@
 import com.android.systemui.util.mockito.capture
 import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
 import com.android.systemui.util.time.FakeSystemClock
 import com.android.wm.shell.TaskView
 import com.android.wm.shell.TaskViewFactory
@@ -322,6 +324,45 @@
             .isFalse()
     }
 
+    @Test
+    fun testResolveActivityWhileSeeding_ControlsActivity() {
+        whenever(controlsController.addSeedingFavoritesCallback(any())).thenReturn(true)
+        assertThat(underTest.resolveActivity()).isEqualTo(ControlsActivity::class.java)
+    }
+
+    @Test
+    fun testResolveActivityNotSeedingNoFavoritesNoPanels_ControlsProviderSelectorActivity() {
+        whenever(controlsController.addSeedingFavoritesCallback(any())).thenReturn(false)
+        whenever(controlsController.getFavorites()).thenReturn(emptyList())
+
+        val selectedItems =
+            listOf(
+                SelectedItem.StructureItem(
+                    StructureInfo(ComponentName.unflattenFromString("pkg/.cls1"), "a", ArrayList())
+                ),
+            )
+        sharedPreferences
+            .edit()
+            .putString("controls_component", selectedItems[0].componentName.flattenToString())
+            .putString("controls_structure", selectedItems[0].name.toString())
+            .commit()
+
+        assertThat(underTest.resolveActivity())
+            .isEqualTo(ControlsProviderSelectorActivity::class.java)
+    }
+
+    @Test
+    fun testResolveActivityNotSeedingNoDefaultNoFavoritesPanel_ControlsActivity() {
+        val panel = SelectedItem.PanelItem("App name", ComponentName("pkg", "cls"))
+        val activity = ComponentName("pkg", "activity")
+        val csi = ControlsServiceInfo(panel.componentName, panel.appName, activity)
+        whenever(controlsController.addSeedingFavoritesCallback(any())).thenReturn(true)
+        whenever(controlsController.getFavorites()).thenReturn(emptyList())
+        whenever(controlsListingController.getCurrentServices()).thenReturn(listOf(csi))
+
+        assertThat(underTest.resolveActivity()).isEqualTo(ControlsActivity::class.java)
+    }
+
     private fun setUpPanel(panel: SelectedItem.PanelItem): ControlsServiceInfo {
         val activity = ComponentName("pkg", "activity")
         sharedPreferences
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/PanelTaskViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/PanelTaskViewControllerTest.kt
index 5cd2ace..de04ef8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/PanelTaskViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/PanelTaskViewControllerTest.kt
@@ -75,6 +75,7 @@
             uiExecutor.execute(it.arguments[0] as Runnable)
             true
         }
+        whenever(activityContext.resources).thenReturn(context.resources)
 
         uiExecutor = FakeExecutor(FakeSystemClock())
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
index 67cf2fc..430575c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
@@ -19,7 +19,6 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.isNull;
 import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
@@ -45,9 +44,12 @@
 import com.android.internal.logging.UiEventLogger;
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.dreams.complication.ComplicationLayoutEngine;
 import com.android.systemui.dreams.complication.dagger.ComplicationComponent;
 import com.android.systemui.dreams.dagger.DreamOverlayComponent;
+import com.android.systemui.dreams.dreamcomplication.HideComplicationTouchHandler;
 import com.android.systemui.dreams.touch.DreamOverlayTouchMonitor;
+import com.android.systemui.touch.TouchInsetManager;
 import com.android.systemui.util.concurrency.FakeExecutor;
 import com.android.systemui.util.time.FakeSystemClock;
 import com.android.systemui.utils.leaks.LeakCheckedTest;
@@ -94,6 +96,20 @@
     ComplicationComponent mComplicationComponent;
 
     @Mock
+    ComplicationLayoutEngine mComplicationVisibilityController;
+
+    @Mock
+    com.android.systemui.dreams.dreamcomplication.dagger.ComplicationComponent.Factory
+            mDreamComplicationComponentFactory;
+
+    @Mock
+    com.android.systemui.dreams.dreamcomplication.dagger.ComplicationComponent
+            mDreamComplicationComponent;
+
+    @Mock
+    HideComplicationTouchHandler mHideComplicationTouchHandler;
+
+    @Mock
     DreamOverlayComponent.Factory mDreamOverlayComponentFactory;
 
     @Mock
@@ -118,6 +134,9 @@
     ViewGroup mDreamOverlayContainerViewParent;
 
     @Mock
+    TouchInsetManager mTouchInsetManager;
+
+    @Mock
     UiEventLogger mUiEventLogger;
 
     @Captor
@@ -136,25 +155,33 @@
         when(mDreamOverlayComponent.getDreamOverlayTouchMonitor())
                 .thenReturn(mDreamOverlayTouchMonitor);
         when(mComplicationComponentFactory
-                .create())
+                .create(any(), any(), any(), any()))
                 .thenReturn(mComplicationComponent);
-        // TODO(b/261781069): A touch handler should be passed in from the complication component
-        // when the complication component is introduced.
+        when(mComplicationComponent.getVisibilityController())
+                .thenReturn(mComplicationVisibilityController);
+        when(mDreamComplicationComponent.getHideComplicationTouchHandler())
+                .thenReturn(mHideComplicationTouchHandler);
+        when(mDreamComplicationComponentFactory
+                .create(any(), any()))
+                .thenReturn(mDreamComplicationComponent);
         when(mDreamOverlayComponentFactory
-                .create(any(), any(), any(), isNull()))
+                .create(any(), any(), any(), any()))
                 .thenReturn(mDreamOverlayComponent);
         when(mDreamOverlayContainerViewController.getContainerView())
                 .thenReturn(mDreamOverlayContainerView);
 
-        mService = new DreamOverlayService(mContext,
+        mService = new DreamOverlayService(
+                mContext,
                 mMainExecutor,
                 mLifecycleOwner,
                 mWindowManager,
                 mComplicationComponentFactory,
+                mDreamComplicationComponentFactory,
                 mDreamOverlayComponentFactory,
                 mStateController,
                 mKeyguardUpdateMonitor,
                 mUiEventLogger,
+                mTouchInsetManager,
                 LOW_LIGHT_COMPONENT);
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamHomeControlsComplicationTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamHomeControlsComplicationTest.java
index e6d3a69..89c7280 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamHomeControlsComplicationTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamHomeControlsComplicationTest.java
@@ -27,6 +27,7 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.content.ComponentName;
 import android.content.Context;
 import android.testing.AndroidTestingRunner;
 import android.view.View;
@@ -54,6 +55,7 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.util.ArrayList;
 import java.util.List;
 import java.util.Optional;
 
@@ -147,6 +149,19 @@
     }
 
     @Test
+    public void complicationAvailability_serviceAvailable_noFavorites_panel_addComplication() {
+        final DreamHomeControlsComplication.Registrant registrant =
+                new DreamHomeControlsComplication.Registrant(mComplication,
+                        mDreamOverlayStateController, mControlsComponent);
+        registrant.start();
+
+        setHaveFavorites(false);
+        setServiceWithPanel();
+
+        verify(mDreamOverlayStateController).addComplication(mComplication);
+    }
+
+    @Test
     public void complicationAvailability_serviceNotAvailable_haveFavorites_doNotAddComplication() {
         final DreamHomeControlsComplication.Registrant registrant =
                 new DreamHomeControlsComplication.Registrant(mComplication,
@@ -232,6 +247,15 @@
         triggerControlsListingCallback(serviceInfos);
     }
 
+    private void setServiceWithPanel() {
+        final List<ControlsServiceInfo> serviceInfos = new ArrayList<>();
+        ControlsServiceInfo csi = mock(ControlsServiceInfo.class);
+        serviceInfos.add(csi);
+        when(csi.getPanelActivity()).thenReturn(new ComponentName("a", "b"));
+        when(mControlsListingController.getCurrentServices()).thenReturn(serviceInfos);
+        triggerControlsListingCallback(serviceInfos);
+    }
+
     private void setDreamOverlayActive(boolean value) {
         when(mDreamOverlayStateController.isOverlayActive()).thenReturn(value);
         verify(mDreamOverlayStateController).addCallback(mStateCallbackCaptor.capture());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
index 798839d..804960d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -168,6 +168,45 @@
     }
 
     @Test
+    @TestableLooper.RunWithLooper(setAsMainLooper = true)
+    public void testOnStartedWakingUp_whileSleeping_ifWakeAndUnlocking_doesNotShowKeyguard() {
+        when(mLockPatternUtils.isLockScreenDisabled(anyInt())).thenReturn(false);
+        when(mLockPatternUtils.getPowerButtonInstantlyLocks(anyInt())).thenReturn(true);
+        mViewMediator.onSystemReady();
+        TestableLooper.get(this).processAllMessages();
+
+        mViewMediator.setShowingLocked(false);
+        TestableLooper.get(this).processAllMessages();
+
+        mViewMediator.onStartedGoingToSleep(OFF_BECAUSE_OF_USER);
+        mViewMediator.onWakeAndUnlocking();
+        mViewMediator.onStartedWakingUp(OFF_BECAUSE_OF_USER, false);
+        TestableLooper.get(this).processAllMessages();
+
+        assertFalse(mViewMediator.isShowingAndNotOccluded());
+        verify(mKeyguardStateController, never()).notifyKeyguardState(eq(true), anyBoolean());
+    }
+
+    @Test
+    @TestableLooper.RunWithLooper(setAsMainLooper = true)
+    public void testOnStartedWakingUp_whileSleeping_ifNotWakeAndUnlocking_showsKeyguard() {
+        when(mLockPatternUtils.isLockScreenDisabled(anyInt())).thenReturn(false);
+        when(mLockPatternUtils.getPowerButtonInstantlyLocks(anyInt())).thenReturn(true);
+        mViewMediator.onSystemReady();
+        TestableLooper.get(this).processAllMessages();
+
+        mViewMediator.setShowingLocked(false);
+        TestableLooper.get(this).processAllMessages();
+
+        mViewMediator.onStartedGoingToSleep(OFF_BECAUSE_OF_USER);
+        mViewMediator.onStartedWakingUp(OFF_BECAUSE_OF_USER, false);
+
+        TestableLooper.get(this).processAllMessages();
+
+        assertTrue(mViewMediator.isShowingAndNotOccluded());
+    }
+
+    @Test
     public void testRegisterDumpable() {
         verify(mDumpManager).registerDumpable(KeyguardViewMediator.class.getName(), mViewMediator);
         verify(mStatusBarKeyguardViewManager, never()).setKeyguardGoingAwayState(anyBoolean());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest.kt
index 322014a..f8cb408 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest.kt
@@ -20,13 +20,14 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.controls.ControlsServiceInfo
 import com.android.systemui.controls.controller.ControlsController
 import com.android.systemui.controls.dagger.ControlsComponent
 import com.android.systemui.controls.management.ControlsListingController
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
-import java.util.*
+import java.util.Optional
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.onEach
@@ -50,20 +51,22 @@
     companion object {
         @Parameters(
             name =
-                "feature enabled = {0}, has favorites = {1}, has service infos = {2}, can show" +
-                    " while locked = {3}, visibility is AVAILABLE {4} - expected visible = {5}"
+                "feature enabled = {0}, has favorites = {1}, has panels = {2}, " +
+                    "has service infos = {3}, can show while locked = {4}, " +
+                    "visibility is AVAILABLE {5} - expected visible = {6}"
         )
         @JvmStatic
         fun data() =
-            (0 until 32)
+            (0 until 64)
                 .map { combination ->
                     arrayOf(
-                        /* isFeatureEnabled= */ combination and 0b10000 != 0,
-                        /* hasFavorites= */ combination and 0b01000 != 0,
-                        /* hasServiceInfos= */ combination and 0b00100 != 0,
-                        /* canShowWhileLocked= */ combination and 0b00010 != 0,
-                        /* visibilityAvailable= */ combination and 0b00001 != 0,
-                        /* isVisible= */ combination == 0b11111,
+                        /* isFeatureEnabled= */ combination and 0b100000 != 0,
+                        /* hasFavorites = */ combination and 0b010000 != 0,
+                        /* hasPanels = */ combination and 0b001000 != 0,
+                        /* hasServiceInfos= */ combination and 0b000100 != 0,
+                        /* canShowWhileLocked= */ combination and 0b000010 != 0,
+                        /* visibilityAvailable= */ combination and 0b000001 != 0,
+                        /* isVisible= */ combination in setOf(0b111111, 0b110111, 0b101111),
                     )
                 }
                 .toList()
@@ -72,6 +75,7 @@
     @Mock private lateinit var component: ControlsComponent
     @Mock private lateinit var controlsController: ControlsController
     @Mock private lateinit var controlsListingController: ControlsListingController
+    @Mock private lateinit var controlsServiceInfo: ControlsServiceInfo
     @Captor
     private lateinit var callbackCaptor:
         ArgumentCaptor<ControlsListingController.ControlsListingCallback>
@@ -80,10 +84,11 @@
 
     @JvmField @Parameter(0) var isFeatureEnabled: Boolean = false
     @JvmField @Parameter(1) var hasFavorites: Boolean = false
-    @JvmField @Parameter(2) var hasServiceInfos: Boolean = false
-    @JvmField @Parameter(3) var canShowWhileLocked: Boolean = false
-    @JvmField @Parameter(4) var isVisibilityAvailable: Boolean = false
-    @JvmField @Parameter(5) var isVisibleExpected: Boolean = false
+    @JvmField @Parameter(2) var hasPanels: Boolean = false
+    @JvmField @Parameter(3) var hasServiceInfos: Boolean = false
+    @JvmField @Parameter(4) var canShowWhileLocked: Boolean = false
+    @JvmField @Parameter(5) var isVisibilityAvailable: Boolean = false
+    @JvmField @Parameter(6) var isVisibleExpected: Boolean = false
 
     @Before
     fun setUp() {
@@ -93,10 +98,13 @@
         whenever(component.getControlsController()).thenReturn(Optional.of(controlsController))
         whenever(component.getControlsListingController())
             .thenReturn(Optional.of(controlsListingController))
+        if (hasPanels) {
+            whenever(controlsServiceInfo.panelActivity).thenReturn(mock())
+        }
         whenever(controlsListingController.getCurrentServices())
             .thenReturn(
                 if (hasServiceInfos) {
-                    listOf(mock(), mock())
+                    listOf(controlsServiceInfo, mock())
                 } else {
                     emptyList()
                 }
@@ -134,10 +142,15 @@
         val job = underTest.lockScreenState.onEach(values::add).launchIn(this)
 
         if (canShowWhileLocked) {
+            val serviceInfoMock: ControlsServiceInfo = mock {
+                if (hasPanels) {
+                    whenever(panelActivity).thenReturn(mock())
+                }
+            }
             verify(controlsListingController).addCallback(callbackCaptor.capture())
             callbackCaptor.value.onServicesUpdated(
                 if (hasServiceInfos) {
-                    listOf(mock())
+                    listOf(serviceInfoMock)
                 } else {
                     emptyList()
                 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java
index 3ae8428..b59005a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java
@@ -86,7 +86,8 @@
     private View mDialogView;
     private View mSubTitle;
     private LinearLayout mEthernet;
-    private LinearLayout mMobileDataToggle;
+    private LinearLayout mMobileDataLayout;
+    private Switch mMobileToggleSwitch;
     private LinearLayout mWifiToggle;
     private Switch mWifiToggleSwitch;
     private TextView mWifiToggleSummary;
@@ -135,7 +136,8 @@
         mDialogView = mInternetDialog.mDialogView;
         mSubTitle = mDialogView.requireViewById(R.id.internet_dialog_subtitle);
         mEthernet = mDialogView.requireViewById(R.id.ethernet_layout);
-        mMobileDataToggle = mDialogView.requireViewById(R.id.mobile_network_layout);
+        mMobileDataLayout = mDialogView.requireViewById(R.id.mobile_network_layout);
+        mMobileToggleSwitch = mDialogView.requireViewById(R.id.mobile_toggle);
         mWifiToggle = mDialogView.requireViewById(R.id.turn_on_wifi_layout);
         mWifiToggleSwitch = mDialogView.requireViewById(R.id.wifi_toggle);
         mWifiToggleSummary = mDialogView.requireViewById(R.id.wifi_toggle_summary);
@@ -236,7 +238,7 @@
 
         mInternetDialog.updateDialog(true);
 
-        assertThat(mMobileDataToggle.getVisibility()).isEqualTo(View.GONE);
+        assertThat(mMobileDataLayout.getVisibility()).isEqualTo(View.GONE);
     }
 
     @Test
@@ -248,7 +250,7 @@
 
         mInternetDialog.updateDialog(true);
 
-        assertThat(mMobileDataToggle.getVisibility()).isEqualTo(View.GONE);
+        assertThat(mMobileDataLayout.getVisibility()).isEqualTo(View.GONE);
 
         // Carrier network should be visible if airplane mode ON and Wi-Fi is ON.
         when(mInternetDialogController.isCarrierNetworkActive()).thenReturn(true);
@@ -257,7 +259,7 @@
 
         mInternetDialog.updateDialog(true);
 
-        assertThat(mMobileDataToggle.getVisibility()).isEqualTo(View.VISIBLE);
+        assertThat(mMobileDataLayout.getVisibility()).isEqualTo(View.VISIBLE);
     }
 
     @Test
@@ -267,7 +269,7 @@
 
         mInternetDialog.updateDialog(true);
 
-        assertThat(mMobileDataToggle.getVisibility()).isEqualTo(View.GONE);
+        assertThat(mMobileDataLayout.getVisibility()).isEqualTo(View.GONE);
     }
 
     @Test
@@ -279,7 +281,7 @@
 
         mInternetDialog.updateDialog(true);
 
-        assertThat(mMobileDataToggle.getVisibility()).isEqualTo(View.VISIBLE);
+        assertThat(mMobileDataLayout.getVisibility()).isEqualTo(View.VISIBLE);
         assertThat(mAirplaneModeSummaryText.getVisibility()).isEqualTo(View.VISIBLE);
     }
 
@@ -316,6 +318,30 @@
     }
 
     @Test
+    public void updateDialog_mobileDataIsEnabled_checkMobileDataSwitch() {
+        doReturn(true).when(mInternetDialogController).hasActiveSubId();
+        when(mInternetDialogController.isCarrierNetworkActive()).thenReturn(true);
+        when(mInternetDialogController.isMobileDataEnabled()).thenReturn(true);
+        mMobileToggleSwitch.setChecked(false);
+
+        mInternetDialog.updateDialog(true);
+
+        assertThat(mMobileToggleSwitch.isChecked()).isTrue();
+    }
+
+    @Test
+    public void updateDialog_mobileDataIsNotChanged_checkMobileDataSwitch() {
+        doReturn(true).when(mInternetDialogController).hasActiveSubId();
+        when(mInternetDialogController.isCarrierNetworkActive()).thenReturn(true);
+        when(mInternetDialogController.isMobileDataEnabled()).thenReturn(false);
+        mMobileToggleSwitch.setChecked(false);
+
+        mInternetDialog.updateDialog(true);
+
+        assertThat(mMobileToggleSwitch.isChecked()).isFalse();
+    }
+
+    @Test
     public void updateDialog_wifiOnAndHasInternetWifi_showConnectedWifi() {
         mInternetDialog.dismissDialog();
         doReturn(true).when(mInternetDialogController).hasActiveSubId();
@@ -695,7 +721,7 @@
     private void setNetworkVisible(boolean ethernetVisible, boolean mobileDataVisible,
             boolean connectedWifiVisible) {
         mEthernet.setVisibility(ethernetVisible ? View.VISIBLE : View.GONE);
-        mMobileDataToggle.setVisibility(mobileDataVisible ? View.VISIBLE : View.GONE);
+        mMobileDataLayout.setVisibility(mobileDataVisible ? View.VISIBLE : View.GONE);
         mConnectedWifi.setVisibility(connectedWifiVisible ? View.VISIBLE : View.GONE);
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
index 0302dad..3512749 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
@@ -1102,6 +1102,17 @@
 
         mStatusBarStateController.setState(KEYGUARD);
 
+        assertThat(mNotificationPanelViewController.isQsExpanded()).isEqualTo(false);
+        assertThat(mNotificationPanelViewController.isQsExpandImmediate()).isEqualTo(false);
+    }
+
+    @Test
+    public void testLockedSplitShadeTransitioningToKeyguard_closesQS() {
+        enableSplitShade(true);
+        mStatusBarStateController.setState(SHADE_LOCKED);
+        mNotificationPanelViewController.setQsExpanded(true);
+
+        mStatusBarStateController.setState(KEYGUARD);
 
         assertThat(mNotificationPanelViewController.isQsExpanded()).isEqualTo(false);
         assertThat(mNotificationPanelViewController.isQsExpandImmediate()).isEqualTo(false);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt
index aa1114b..cb4f119 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt
@@ -23,6 +23,7 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.dump.logcatLogBuffer
 import com.android.systemui.statusbar.NotificationRemoteInputManager
+import com.android.systemui.statusbar.notification.NotifPipelineFlags
 import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder
 import com.android.systemui.statusbar.notification.collection.NotifPipeline
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
@@ -38,6 +39,7 @@
 import com.android.systemui.statusbar.notification.collection.render.NodeController
 import com.android.systemui.statusbar.notification.interruption.HeadsUpViewBinder
 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider
+import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider.FullScreenIntentDecision
 import com.android.systemui.statusbar.notification.row.NotifBindPipeline.BindCallback
 import com.android.systemui.statusbar.phone.NotificationGroupTestHelper
 import com.android.systemui.statusbar.policy.HeadsUpManager
@@ -88,6 +90,7 @@
     private val mEndLifetimeExtension: OnEndLifetimeExtensionCallback = mock()
     private val mHeaderController: NodeController = mock()
     private val mLaunchFullScreenIntentProvider: LaunchFullScreenIntentProvider = mock()
+    private val mFlags: NotifPipelineFlags = mock()
 
     private lateinit var mEntry: NotificationEntry
     private lateinit var mGroupSummary: NotificationEntry
@@ -113,6 +116,7 @@
             mNotificationInterruptStateProvider,
             mRemoteInputManager,
             mLaunchFullScreenIntentProvider,
+            mFlags,
             mHeaderController,
             mExecutor)
         mCoordinator.attach(mNotifPipeline)
@@ -246,14 +250,14 @@
 
     @Test
     fun testOnEntryAdded_shouldFullScreen() {
-        setShouldFullScreen(mEntry)
+        setShouldFullScreen(mEntry, FullScreenIntentDecision.FSI_EXPECTED_NOT_TO_HUN)
         mCollectionListener.onEntryAdded(mEntry)
         verify(mLaunchFullScreenIntentProvider).launchFullScreenIntent(mEntry)
     }
 
     @Test
     fun testOnEntryAdded_shouldNotFullScreen() {
-        setShouldFullScreen(mEntry, should = false)
+        setShouldFullScreen(mEntry, FullScreenIntentDecision.NO_FULL_SCREEN_INTENT)
         mCollectionListener.onEntryAdded(mEntry)
         verify(mLaunchFullScreenIntentProvider, never()).launchFullScreenIntent(any())
     }
@@ -805,15 +809,96 @@
         verify(mHeadsUpManager, never()).showNotification(any())
     }
 
+    @Test
+    fun testOnRankingApplied_noFSIOnUpdateWhenFlagOff() {
+        // Ensure the feature flag is off
+        whenever(mFlags.fsiOnDNDUpdate()).thenReturn(false)
+
+        // GIVEN that mEntry was previously suppressed from full-screen only by DND
+        setShouldFullScreen(mEntry, FullScreenIntentDecision.NO_FSI_SUPPRESSED_ONLY_BY_DND)
+        mCollectionListener.onEntryAdded(mEntry)
+
+        // and it is then updated to allow full screen
+        setShouldFullScreen(mEntry, FullScreenIntentDecision.FSI_DEVICE_NOT_INTERACTIVE)
+        whenever(mNotifPipeline.allNotifs).thenReturn(listOf(mEntry))
+        mCollectionListener.onRankingApplied()
+
+        // THEN it should not full screen because the feature is off
+        verify(mLaunchFullScreenIntentProvider, never()).launchFullScreenIntent(mEntry)
+    }
+
+    @Test
+    fun testOnRankingApplied_updateToFullScreen() {
+        // Turn on the feature
+        whenever(mFlags.fsiOnDNDUpdate()).thenReturn(true)
+
+        // GIVEN that mEntry was previously suppressed from full-screen only by DND
+        setShouldFullScreen(mEntry, FullScreenIntentDecision.NO_FSI_SUPPRESSED_ONLY_BY_DND)
+        mCollectionListener.onEntryAdded(mEntry)
+
+        // at this point, it should not have full screened
+        verify(mLaunchFullScreenIntentProvider, never()).launchFullScreenIntent(mEntry)
+
+        // and it is then updated to allow full screen AND HUN
+        setShouldFullScreen(mEntry, FullScreenIntentDecision.FSI_DEVICE_NOT_INTERACTIVE)
+        setShouldHeadsUp(mEntry)
+        whenever(mNotifPipeline.allNotifs).thenReturn(listOf(mEntry))
+        mCollectionListener.onRankingApplied()
+        mBeforeTransformGroupsListener.onBeforeTransformGroups(listOf(mEntry))
+        mBeforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(mEntry))
+
+        // THEN it should full screen but it should NOT HUN
+        verify(mLaunchFullScreenIntentProvider).launchFullScreenIntent(mEntry)
+        verify(mHeadsUpViewBinder, never()).bindHeadsUpView(any(), any())
+        verify(mHeadsUpManager, never()).showNotification(any())
+    }
+
+    @Test
+    fun testOnRankingApplied_noFSIWhenAlsoSuppressedForOtherReasons() {
+        // Feature on
+        whenever(mFlags.fsiOnDNDUpdate()).thenReturn(true)
+
+        // GIVEN that mEntry is suppressed by DND (functionally), but not *only* DND
+        setShouldFullScreen(mEntry, FullScreenIntentDecision.NO_FSI_SUPPRESSED_BY_DND)
+        mCollectionListener.onEntryAdded(mEntry)
+
+        // and it is updated to full screen later
+        setShouldFullScreen(mEntry, FullScreenIntentDecision.FSI_DEVICE_NOT_INTERACTIVE)
+        mCollectionListener.onRankingApplied()
+
+        // THEN it should still not full screen because something else was blocking it before
+        verify(mLaunchFullScreenIntentProvider, never()).launchFullScreenIntent(mEntry)
+    }
+
+    @Test
+    fun testOnRankingApplied_noFSIWhenTooOld() {
+        // Feature on
+        whenever(mFlags.fsiOnDNDUpdate()).thenReturn(true)
+
+        // GIVEN that mEntry is suppressed only by DND
+        setShouldFullScreen(mEntry, FullScreenIntentDecision.NO_FSI_SUPPRESSED_ONLY_BY_DND)
+        mCollectionListener.onEntryAdded(mEntry)
+
+        // but it's >10s old
+        mCoordinator.addForFSIReconsideration(mEntry, mSystemClock.currentTimeMillis() - 10000)
+
+        // and it is updated to full screen later
+        setShouldFullScreen(mEntry, FullScreenIntentDecision.FSI_EXPECTED_NOT_TO_HUN)
+        mCollectionListener.onRankingApplied()
+
+        // THEN it should still not full screen because it's too old
+        verify(mLaunchFullScreenIntentProvider, never()).launchFullScreenIntent(mEntry)
+    }
+
     private fun setShouldHeadsUp(entry: NotificationEntry, should: Boolean = true) {
         whenever(mNotificationInterruptStateProvider.shouldHeadsUp(entry)).thenReturn(should)
         whenever(mNotificationInterruptStateProvider.checkHeadsUp(eq(entry), any()))
                 .thenReturn(should)
     }
 
-    private fun setShouldFullScreen(entry: NotificationEntry, should: Boolean = true) {
-        whenever(mNotificationInterruptStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry))
-            .thenReturn(should)
+    private fun setShouldFullScreen(entry: NotificationEntry, decision: FullScreenIntentDecision) {
+        whenever(mNotificationInterruptStateProvider.getFullScreenIntentDecision(entry))
+            .thenReturn(decision)
     }
 
     private fun finishBind(entry: NotificationEntry) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt
index 3d9fd96..22c0ea1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt
@@ -16,11 +16,14 @@
 
 package com.android.systemui.statusbar.pipeline.wifi.ui.view
 
+import android.content.res.ColorStateList
+import android.graphics.Rect
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
 import android.testing.TestableLooper.RunWithLooper
 import android.testing.ViewUtils
 import android.view.View
+import android.widget.ImageView
 import androidx.test.filters.SmallTest
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
@@ -44,6 +47,7 @@
 import com.android.systemui.statusbar.pipeline.wifi.shared.WifiConstants
 import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.LocationBasedWifiViewModel
 import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.WifiViewModel
+import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Dispatchers
@@ -229,10 +233,43 @@
         ViewUtils.detachView(view)
     }
 
+    @Test
+    fun onDarkChanged_iconHasNewColor() {
+        whenever(statusBarPipelineFlags.useWifiDebugColoring()).thenReturn(false)
+        val view = ModernStatusBarWifiView.constructAndBind(context, SLOT_NAME, viewModel)
+        ViewUtils.attachView(view)
+        testableLooper.processAllMessages()
+
+        val areas = ArrayList(listOf(Rect(0, 0, 1000, 1000)))
+        val color = 0x12345678
+        view.onDarkChanged(areas, 1.0f, color)
+        testableLooper.processAllMessages()
+
+        assertThat(view.getIconView().imageTintList).isEqualTo(ColorStateList.valueOf(color))
+    }
+
+    @Test
+    fun setStaticDrawableColor_iconHasNewColor() {
+        whenever(statusBarPipelineFlags.useWifiDebugColoring()).thenReturn(false)
+        val view = ModernStatusBarWifiView.constructAndBind(context, SLOT_NAME, viewModel)
+        ViewUtils.attachView(view)
+        testableLooper.processAllMessages()
+
+        val color = 0x23456789
+        view.setStaticDrawableColor(color)
+        testableLooper.processAllMessages()
+
+        assertThat(view.getIconView().imageTintList).isEqualTo(ColorStateList.valueOf(color))
+    }
+
     private fun View.getIconGroupView(): View {
         return this.requireViewById(R.id.wifi_group)
     }
 
+    private fun View.getIconView(): ImageView {
+        return this.requireViewById(R.id.wifi_signal)
+    }
+
     private fun View.getDotView(): View {
         return this.requireViewById(R.id.status_bar_dot)
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/touch/TouchInsetManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/touch/TouchInsetManagerTest.java
index 14b9bfb..a707222 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/touch/TouchInsetManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/touch/TouchInsetManagerTest.java
@@ -26,8 +26,8 @@
 import android.graphics.Rect;
 import android.graphics.Region;
 import android.testing.AndroidTestingRunner;
+import android.view.AttachedSurfaceControl;
 import android.view.View;
-import android.view.ViewRootImpl;
 
 import androidx.test.filters.SmallTest;
 
@@ -47,41 +47,78 @@
 @RunWith(AndroidTestingRunner.class)
 public class TouchInsetManagerTest extends SysuiTestCase {
     @Mock
-    private View mRootView;
-
-    @Mock
-    private ViewRootImpl mRootViewImpl;
+    private AttachedSurfaceControl mAttachedSurfaceControl;
 
     private FakeExecutor mFakeExecutor = new FakeExecutor(new FakeSystemClock());
 
     @Before
     public void setup() {
         MockitoAnnotations.initMocks(this);
-        when(mRootView.getViewRootImpl()).thenReturn(mRootViewImpl);
     }
 
     @Test
-    public void testRootViewOnAttachedHandling() {
+    public void testViewOnAttachedHandling() {
         // Create inset manager
-        final TouchInsetManager insetManager = new TouchInsetManager(mFakeExecutor,
-                mRootView);
+        final TouchInsetManager insetManager = new TouchInsetManager(mFakeExecutor);
 
         final ArgumentCaptor<View.OnAttachStateChangeListener> listener =
                 ArgumentCaptor.forClass(View.OnAttachStateChangeListener.class);
+        final View view = createView(new Rect(0, 0, 0, 0));
+        when(view.isAttachedToWindow()).thenReturn(false);
 
+
+        // Create session
+        final TouchInsetManager.TouchInsetSession session = insetManager.createSession();
+        session.addViewToTracking(view);
+
+        mFakeExecutor.runAllReady();
         // Ensure manager has registered to listen to attached state of root view.
-        verify(mRootView).addOnAttachStateChangeListener(listener.capture());
+        verify(view).addOnAttachStateChangeListener(listener.capture());
+
+        clearInvocations(mAttachedSurfaceControl);
+        when(view.isAttachedToWindow()).thenReturn(true);
 
         // Trigger attachment and verify touchable region is set.
-        listener.getValue().onViewAttachedToWindow(mRootView);
-        verify(mRootViewImpl).setTouchableRegion(any());
+        listener.getValue().onViewAttachedToWindow(view);
+
+        mFakeExecutor.runAllReady();
+
+        verify(mAttachedSurfaceControl).setTouchableRegion(any());
+    }
+
+    @Test
+    public void testViewOnDetachedHandling() {
+        // Create inset manager
+        final TouchInsetManager insetManager = new TouchInsetManager(mFakeExecutor);
+
+        final ArgumentCaptor<View.OnAttachStateChangeListener> listener =
+                ArgumentCaptor.forClass(View.OnAttachStateChangeListener.class);
+        final View view = createView(new Rect(0, 0, 0, 0));
+        when(view.isAttachedToWindow()).thenReturn(true);
+
+        // Create session
+        final TouchInsetManager.TouchInsetSession session = insetManager.createSession();
+        session.addViewToTracking(view);
+
+        mFakeExecutor.runAllReady();
+        // Ensure manager has registered to listen to attached state of root view.
+        verify(view).addOnAttachStateChangeListener(listener.capture());
+
+        clearInvocations(mAttachedSurfaceControl);
+        when(view.isAttachedToWindow()).thenReturn(false);
+
+        // Trigger detachment and verify touchable region is set.
+        listener.getValue().onViewDetachedFromWindow(view);
+
+        mFakeExecutor.runAllReady();
+
+        verify(mAttachedSurfaceControl).setTouchableRegion(any());
     }
 
     @Test
     public void testInsetRegionPropagation() {
         // Create inset manager
-        final TouchInsetManager insetManager = new TouchInsetManager(mFakeExecutor,
-                mRootView);
+        final TouchInsetManager insetManager = new TouchInsetManager(mFakeExecutor);
 
         // Create session
         final TouchInsetManager.TouchInsetSession session = insetManager.createSession();
@@ -95,14 +132,13 @@
         // Check to see if view was properly accounted for.
         final Region expectedRegion = Region.obtain();
         expectedRegion.op(rect, Region.Op.UNION);
-        verify(mRootViewImpl).setTouchableRegion(eq(expectedRegion));
+        verify(mAttachedSurfaceControl).setTouchableRegion(eq(expectedRegion));
     }
 
     @Test
     public void testMultipleRegions() {
         // Create inset manager
-        final TouchInsetManager insetManager = new TouchInsetManager(mFakeExecutor,
-                mRootView);
+        final TouchInsetManager insetManager = new TouchInsetManager(mFakeExecutor);
 
         // Create session
         final TouchInsetManager.TouchInsetSession session = insetManager.createSession();
@@ -112,7 +148,7 @@
         session.addViewToTracking(createView(firstBounds));
 
         mFakeExecutor.runAllReady();
-        clearInvocations(mRootViewImpl);
+        clearInvocations(mAttachedSurfaceControl);
 
         // Create second session
         final TouchInsetManager.TouchInsetSession secondSession = insetManager.createSession();
@@ -128,27 +164,26 @@
             final Region expectedRegion = Region.obtain();
             expectedRegion.op(firstBounds, Region.Op.UNION);
             expectedRegion.op(secondBounds, Region.Op.UNION);
-            verify(mRootViewImpl).setTouchableRegion(eq(expectedRegion));
+            verify(mAttachedSurfaceControl).setTouchableRegion(eq(expectedRegion));
         }
 
 
-        clearInvocations(mRootViewImpl);
+        clearInvocations(mAttachedSurfaceControl);
 
         // clear first session, ensure second session is still reflected.
         session.clear();
         mFakeExecutor.runAllReady();
         {
             final Region expectedRegion = Region.obtain();
-            expectedRegion.op(firstBounds, Region.Op.UNION);
-            verify(mRootViewImpl).setTouchableRegion(eq(expectedRegion));
+            expectedRegion.op(secondBounds, Region.Op.UNION);
+            verify(mAttachedSurfaceControl).setTouchableRegion(eq(expectedRegion));
         }
     }
 
     @Test
     public void testMultipleViews() {
         // Create inset manager
-        final TouchInsetManager insetManager = new TouchInsetManager(mFakeExecutor,
-                mRootView);
+        final TouchInsetManager insetManager = new TouchInsetManager(mFakeExecutor);
 
         // Create session
         final TouchInsetManager.TouchInsetSession session = insetManager.createSession();
@@ -159,7 +194,7 @@
 
         // only capture second invocation.
         mFakeExecutor.runAllReady();
-        clearInvocations(mRootViewImpl);
+        clearInvocations(mAttachedSurfaceControl);
 
         // Add a second view to the session
         final Rect secondViewBounds = new Rect(4, 4, 9, 10);
@@ -173,20 +208,20 @@
             final Region expectedRegion = Region.obtain();
             expectedRegion.op(firstViewBounds, Region.Op.UNION);
             expectedRegion.op(secondViewBounds, Region.Op.UNION);
-            verify(mRootViewImpl).setTouchableRegion(eq(expectedRegion));
+            verify(mAttachedSurfaceControl).setTouchableRegion(eq(expectedRegion));
         }
 
         // Remove second view.
         session.removeViewFromTracking(secondView);
 
-        clearInvocations(mRootViewImpl);
+        clearInvocations(mAttachedSurfaceControl);
         mFakeExecutor.runAllReady();
 
         // Ensure first view still reflected in touch region.
         {
             final Region expectedRegion = Region.obtain();
             expectedRegion.op(firstViewBounds, Region.Op.UNION);
-            verify(mRootViewImpl).setTouchableRegion(eq(expectedRegion));
+            verify(mAttachedSurfaceControl).setTouchableRegion(eq(expectedRegion));
         }
     }
 
@@ -197,6 +232,8 @@
             ((Rect) invocation.getArgument(0)).set(rect);
             return null;
         }).when(view).getBoundsOnScreen(any());
+        when(view.isAttachedToWindow()).thenReturn(true);
+        when(view.getRootSurfaceControl()).thenReturn(mAttachedSurfaceControl);
 
         return view;
     }
diff --git a/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java b/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java
index 2ad2119..dd9f1d8 100644
--- a/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java
+++ b/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java
@@ -48,6 +48,7 @@
 import android.hardware.camera2.extension.IRequestProcessorImpl;
 import android.hardware.camera2.extension.IRequestUpdateProcessorImpl;
 import android.hardware.camera2.extension.ISessionProcessorImpl;
+import android.hardware.camera2.extension.LatencyPair;
 import android.hardware.camera2.extension.LatencyRange;
 import android.hardware.camera2.extension.OutputConfigId;
 import android.hardware.camera2.extension.OutputSurface;
@@ -1266,6 +1267,21 @@
         public int startCapture(ICaptureCallback callback) {
             return mSessionProcessor.startCapture(new CaptureCallbackStub(callback, mCameraId));
         }
+
+        @Override
+        public LatencyPair getRealtimeCaptureLatency() {
+            if (LATENCY_IMPROVEMENTS_SUPPORTED) {
+                Pair<Long, Long> latency = mSessionProcessor.getRealtimeCaptureLatency();
+                if (latency != null) {
+                    LatencyPair ret = new LatencyPair();
+                    ret.first = latency.first;
+                    ret.second = latency.second;
+                    return ret;
+                }
+            }
+
+            return null;
+        }
     }
 
     private class OutputSurfaceConfigurationImplStub implements OutputSurfaceConfigurationImpl {
@@ -1578,6 +1594,21 @@
         }
 
         @Override
+        public LatencyPair getRealtimeCaptureLatency() {
+            if (LATENCY_IMPROVEMENTS_SUPPORTED) {
+                Pair<Long, Long> latency = mImageExtender.getRealtimeCaptureLatency();
+                if (latency != null) {
+                    LatencyPair ret = new LatencyPair();
+                    ret.first = latency.first;
+                    ret.second = latency.second;
+                    return ret;
+                }
+            }
+
+            return null;
+        }
+
+        @Override
         public CameraMetadataNative getAvailableCaptureRequestKeys() {
             if (RESULT_API_SUPPORTED) {
                 List<CaptureRequest.Key> supportedCaptureKeys =
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 3145139..59c1c54 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -26,8 +26,11 @@
 import static android.accessibilityservice.AccessibilityTrace.FLAGS_WINDOW_MAGNIFICATION_CONNECTION;
 import static android.accessibilityservice.AccessibilityTrace.FLAGS_WINDOW_MANAGER_INTERNAL;
 import static android.provider.Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED;
+import static android.provider.Settings.Secure.CONTRAST_LEVEL;
 import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_BUTTON;
 import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_SHORTCUT_KEY;
+import static android.view.accessibility.AccessibilityManager.CONTRAST_DEFAULT_VALUE;
+import static android.view.accessibility.AccessibilityManager.CONTRAST_NOT_SET;
 import static android.view.accessibility.AccessibilityManager.ShortcutType;
 
 import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_COMPONENT_NAME;
@@ -1018,10 +1021,13 @@
     }
 
     private void dispatchAccessibilityEventLocked(AccessibilityEvent event) {
-        notifyAccessibilityServicesDelayedLocked(event, false);
-        notifyAccessibilityServicesDelayedLocked(event, true);
+        if (mProxyManager.isProxyed(event.getDisplayId())) {
+            mProxyManager.sendAccessibilityEventLocked(event);
+        } else {
+            notifyAccessibilityServicesDelayedLocked(event, false);
+            notifyAccessibilityServicesDelayedLocked(event, true);
+        }
         mUiAutomationManager.sendAccessibilityEventLocked(event);
-        mProxyManager.sendAccessibilityEvent(event);
     }
 
     private void sendAccessibilityEventToInputFilter(AccessibilityEvent event) {
@@ -1162,7 +1168,7 @@
             }
             List<AccessibilityServiceConnection> services =
                     getUserStateLocked(resolvedUserId).mBoundServices;
-            int numServices = services.size() + mProxyManager.getNumProxys();
+            int numServices = services.size() + mProxyManager.getNumProxysLocked();
             interfacesToInterrupt = new ArrayList<>(numServices);
             for (int i = 0; i < services.size(); i++) {
                 AccessibilityServiceConnection service = services.get(i);
@@ -1172,7 +1178,7 @@
                     interfacesToInterrupt.add(a11yServiceInterface);
                 }
             }
-            mProxyManager.addServiceInterfaces(interfacesToInterrupt);
+            mProxyManager.addServiceInterfacesLocked(interfacesToInterrupt);
         }
         for (int i = 0, count = interfacesToInterrupt.size(); i < count; i++) {
             try {
@@ -1899,6 +1905,16 @@
         return false;
     }
 
+    private boolean readUiContrastLocked(AccessibilityUserState userState) {
+        float contrast = Settings.Secure.getFloatForUser(mContext.getContentResolver(),
+                CONTRAST_LEVEL, CONTRAST_DEFAULT_VALUE, userState.mUserId);
+        if (Math.abs(userState.getUiContrastLocked() - contrast) >= 1e-10) {
+            userState.setUiContrastLocked(contrast);
+            return true;
+        }
+        return false;
+    }
+
     /**
      * Performs {@link AccessibilityService}s delayed notification. The delay is configurable
      * and denotes the period after the last event before notifying the service.
@@ -1963,7 +1979,7 @@
                 mUiAutomationManager.getServiceInfo(), client)
                 ? mUiAutomationManager.getRelevantEventTypes()
                 : 0;
-        relevantEventTypes |= mProxyManager.getRelevantEventTypes();
+        relevantEventTypes |= mProxyManager.getRelevantEventTypesLocked();
         return relevantEventTypes;
     }
 
@@ -2565,6 +2581,7 @@
         somethingChanged |= readMagnificationModeForDefaultDisplayLocked(userState);
         somethingChanged |= readMagnificationCapabilitiesLocked(userState);
         somethingChanged |= readMagnificationFollowTypingLocked(userState);
+        somethingChanged |= readUiContrastLocked(userState);
         return somethingChanged;
     }
 
@@ -3706,6 +3723,19 @@
         return mProxyManager.unregisterProxy(displayId);
     }
 
+    @Override public float getUiContrast() {
+        if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) {
+            mTraceManager.logTrace(LOG_TAG + ".getUiContrast", FLAGS_ACCESSIBILITY_MANAGER);
+        }
+        synchronized (mLock) {
+            AccessibilityUserState userState = getCurrentUserStateLocked();
+            float contrast = userState.getUiContrastLocked();
+            if (contrast != CONTRAST_NOT_SET) return contrast;
+            readUiContrastLocked(userState);
+            return userState.getUiContrastLocked();
+        }
+    }
+
     @Override
     public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) {
         if (!DumpUtils.checkDumpPermission(mContext, LOG_TAG, pw)) return;
@@ -4153,6 +4183,9 @@
         private final Uri mMagnificationFollowTypingUri = Settings.Secure.getUriFor(
                 Settings.Secure.ACCESSIBILITY_MAGNIFICATION_FOLLOW_TYPING_ENABLED);
 
+        private final Uri mUiContrastUri = Settings.Secure.getUriFor(
+                CONTRAST_LEVEL);
+
         public AccessibilityContentObserver(Handler handler) {
             super(handler);
         }
@@ -4193,6 +4226,8 @@
                     mMagnificationCapabilityUri, false, this, UserHandle.USER_ALL);
             contentResolver.registerContentObserver(
                     mMagnificationFollowTypingUri, false, this, UserHandle.USER_ALL);
+            contentResolver.registerContentObserver(
+                    mUiContrastUri, false, this, UserHandle.USER_ALL);
         }
 
         @Override
@@ -4262,6 +4297,10 @@
                     }
                 } else if (mMagnificationFollowTypingUri.equals(uri)) {
                     readMagnificationFollowTypingLocked(userState);
+                } else if (mUiContrastUri.equals(uri)) {
+                    if (readUiContrastLocked(userState)) {
+                        updateUiContrastLocked(userState);
+                    }
                 }
             }
         }
@@ -4551,7 +4590,22 @@
                         userState.getFocusColorLocked());
             }));
         });
+    }
 
+    private void updateUiContrastLocked(AccessibilityUserState userState) {
+        if (userState.mUserId != mCurrentUserId) {
+            return;
+        }
+        if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_SERVICE_CLIENT)) {
+            mTraceManager.logTrace(LOG_TAG + ".updateUiContrastLocked",
+                    FLAGS_ACCESSIBILITY_SERVICE_CLIENT, "userState=" + userState);
+        }
+        float contrast = userState.getUiContrastLocked();
+        mMainHandler.post(() -> {
+            broadcastToClients(userState, ignoreRemoteException(client -> {
+                client.mCallback.setUiContrast(contrast);
+            }));
+        });
     }
 
     public AccessibilityTraceManager getTraceManager() {
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
index 0db169f..43730fc 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
@@ -26,6 +26,8 @@
 import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_NONE;
 import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_BUTTON;
 import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_SHORTCUT_KEY;
+import static android.view.accessibility.AccessibilityManager.CONTRAST_DEFAULT_VALUE;
+import static android.view.accessibility.AccessibilityManager.CONTRAST_NOT_SET;
 import static android.view.accessibility.AccessibilityManager.ShortcutType;
 
 import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_CONTROLLER_NAME;
@@ -143,6 +145,8 @@
     private final int mFocusStrokeWidthDefaultValue;
     // The default value of the focus color.
     private final int mFocusColorDefaultValue;
+    /** The color contrast in [-1, 1] */
+    private float mUiContrast = CONTRAST_DEFAULT_VALUE;
 
     private Context mContext;
 
@@ -217,6 +221,7 @@
         mFocusStrokeWidth = mFocusStrokeWidthDefaultValue;
         mFocusColor = mFocusColorDefaultValue;
         mMagnificationFollowTypingEnabled = true;
+        mUiContrast = CONTRAST_NOT_SET;
     }
 
     void addServiceLocked(AccessibilityServiceConnection serviceConnection) {
@@ -983,6 +988,7 @@
         return mFocusColor;
     }
 
+
     /**
      * Sets the stroke width and color of the focus rectangle.
      *
@@ -1008,4 +1014,20 @@
         }
         return false;
     }
+
+    /**
+     * Get the color contrast
+     * @return color contrast in [-1, 1]
+     */
+    public float getUiContrastLocked() {
+        return mUiContrast;
+    }
+
+    /**
+     * Set the color contrast
+     * @param contrast the new color contrast in [-1, 1]
+     */
+    public void setUiContrastLocked(float contrast) {
+        mUiContrast = contrast;
+    }
 }
diff --git a/services/accessibility/java/com/android/server/accessibility/ProxyManager.java b/services/accessibility/java/com/android/server/accessibility/ProxyManager.java
index 2184878..f28191f 100644
--- a/services/accessibility/java/com/android/server/accessibility/ProxyManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/ProxyManager.java
@@ -37,6 +37,7 @@
  * proxy connection will belong to a separate user state.
  *
  * TODO(241117292): Remove or cut down during simultaneous user refactoring.
+ * TODO(262244375): Add unit tests.
  */
 public class ProxyManager {
     // Names used to populate ComponentName and ResolveInfo in connection.mA11yServiceInfo and in
@@ -84,7 +85,9 @@
                         windowManagerInternal,
                         awm, displayId);
 
-        mProxyA11yServiceConnections.put(displayId, connection);
+        synchronized (mLock) {
+            mProxyA11yServiceConnections.put(displayId, connection);
+        }
 
         // If the client dies, make sure to remove the connection.
         IBinder.DeathRecipient deathRecipient =
@@ -98,7 +101,9 @@
         client.asBinder().linkToDeath(deathRecipient, 0);
         // Notify apps that the service state has changed.
         // A11yManager#A11yServicesStateChangeListener
-        connection.mSystemSupport.onClientChangeLocked(true);
+        synchronized (mLock) {
+            connection.mSystemSupport.onClientChangeLocked(true);
+        }
 
         connection.initializeServiceInterface(client);
     }
@@ -111,9 +116,11 @@
     }
 
     private boolean clearConnection(int displayId) {
-        if (mProxyA11yServiceConnections.contains(displayId)) {
-            mProxyA11yServiceConnections.remove(displayId);
-            return true;
+        synchronized (mLock) {
+            if (mProxyA11yServiceConnections.contains(displayId)) {
+                mProxyA11yServiceConnections.remove(displayId);
+                return true;
+            }
         }
         return false;
     }
@@ -122,7 +129,9 @@
      * Checks if a display id is being proxy-ed.
      */
     public boolean isProxyed(int displayId) {
-        return mProxyA11yServiceConnections.contains(displayId);
+        synchronized (mLock) {
+            return mProxyA11yServiceConnections.contains(displayId);
+        }
     }
 
     /**
@@ -130,10 +139,10 @@
      * {@link android.view.accessibility.AccessibilityDisplayProxy} will filter based on display.
      * TODO(b/250929565): Filtering should happen in the system, not in the proxy.
      */
-    public void sendAccessibilityEvent(AccessibilityEvent event) {
-        for (int i = 0; i < mProxyA11yServiceConnections.size(); i++) {
-            ProxyAccessibilityServiceConnection proxy =
-                    mProxyA11yServiceConnections.valueAt(i);
+    public void sendAccessibilityEventLocked(AccessibilityEvent event) {
+        final ProxyAccessibilityServiceConnection proxy =
+                mProxyA11yServiceConnections.get(event.getDisplayId());
+        if (proxy != null) {
             proxy.notifyAccessibilityEvent(event);
         }
     }
@@ -187,7 +196,7 @@
      * Returns the relevant event types of every proxy.
      * TODO(254545943): When A11yManager is separated, return based on the A11yManager display.
      */
-    public int getRelevantEventTypes() {
+    public int getRelevantEventTypesLocked() {
         int relevantEventTypes = 0;
         for (int i = 0; i < mProxyA11yServiceConnections.size(); i++) {
             ProxyAccessibilityServiceConnection proxy =
@@ -201,7 +210,7 @@
      * Gets the number of current proxy connections.
      * @return
      */
-    public int getNumProxys() {
+    public int getNumProxysLocked() {
         return mProxyA11yServiceConnections.size();
     }
 
@@ -209,7 +218,7 @@
      * Adds the service interfaces to a list.
      * @param interfaces
      */
-    public void addServiceInterfaces(List<IAccessibilityServiceClient> interfaces) {
+    public void addServiceInterfacesLocked(List<IAccessibilityServiceClient> interfaces) {
         for (int i = 0; i < mProxyA11yServiceConnections.size(); i++) {
             final ProxyAccessibilityServiceConnection proxy =
                     mProxyA11yServiceConnections.valueAt(i);
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
index 5819861..2f5c40a 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -108,7 +108,7 @@
     private VirtualAudioController mVirtualAudioController;
     @VisibleForTesting
     final Set<Integer> mVirtualDisplayIds = new ArraySet<>();
-    private final OnDeviceCloseListener mListener;
+    private final OnDeviceCloseListener mOnDeviceCloseListener;
     private final IBinder mAppToken;
     private final VirtualDeviceParams mParams;
     private final Map<Integer, PowerManager.WakeLock> mPerDisplayWakelocks = new ArrayMap<>();
@@ -155,7 +155,7 @@
             IBinder token,
             int ownerUid,
             int deviceId,
-            OnDeviceCloseListener listener,
+            OnDeviceCloseListener onDeviceCloseListener,
             PendingTrampolineCallback pendingTrampolineCallback,
             IVirtualDeviceActivityListener activityListener,
             Consumer<ArraySet<Integer>> runningAppsChangedCallback,
@@ -168,7 +168,7 @@
                 deviceId,
                 /* inputController= */ null,
                 /* sensorController= */ null,
-                listener,
+                onDeviceCloseListener,
                 pendingTrampolineCallback,
                 activityListener,
                 runningAppsChangedCallback,
@@ -184,7 +184,7 @@
             int deviceId,
             InputController inputController,
             SensorController sensorController,
-            OnDeviceCloseListener listener,
+            OnDeviceCloseListener onDeviceCloseListener,
             PendingTrampolineCallback pendingTrampolineCallback,
             IVirtualDeviceActivityListener activityListener,
             Consumer<ArraySet<Integer>> runningAppsChangedCallback,
@@ -212,7 +212,7 @@
         } else {
             mSensorController = sensorController;
         }
-        mListener = listener;
+        mOnDeviceCloseListener = onDeviceCloseListener;
         try {
             token.linkToDeath(this, 0);
         } catch (RemoteException e) {
@@ -330,7 +330,7 @@
                 mVirtualAudioController = null;
             }
         }
-        mListener.onClose(mAssociationInfo.getId());
+        mOnDeviceCloseListener.onClose(mDeviceId);
         mAppToken.unlinkToDeath(this, 0);
 
         final long ident = Binder.clearCallingIdentity();
@@ -650,6 +650,7 @@
     @Override
     protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) {
         fout.println("  VirtualDevice: ");
+        fout.println("    mDeviceId: " + mDeviceId);
         fout.println("    mAssociationId: " + mAssociationInfo.getId());
         fout.println("    mParams: " + mParams);
         fout.println("    mVirtualDisplayIds: ");
@@ -839,7 +840,7 @@
     }
 
     interface OnDeviceCloseListener {
-        void onClose(int associationId);
+        void onClose(int deviceId);
     }
 
     interface PendingTrampolineCallback {
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
index e092f49..da2c516 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
@@ -41,6 +41,7 @@
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.Parcel;
+import android.os.Process;
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.util.ArraySet;
@@ -62,6 +63,7 @@
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.atomic.AtomicInteger;
 
@@ -88,14 +90,20 @@
             new SparseArray<>();
 
     /**
-     * Mapping from CDM association IDs to virtual devices. Only one virtual device is allowed for
-     * each CDM associated device.
+     * Mapping from device IDs to CameraAccessControllers.
+     */
+    @GuardedBy("mVirtualDeviceManagerLock")
+    private final SparseArray<CameraAccessController> mCameraAccessControllersByDeviceId =
+            new SparseArray<>();
+
+    /**
+     * Mapping from device IDs to virtual devices.
      */
     @GuardedBy("mVirtualDeviceManagerLock")
     private final SparseArray<VirtualDeviceImpl> mVirtualDevices = new SparseArray<>();
 
     /**
-     * Mapping from CDM association IDs to app UIDs running on the corresponding virtual device.
+     * Mapping from device IDs to app UIDs running on the corresponding virtual device.
      */
     @GuardedBy("mVirtualDeviceManagerLock")
     private final SparseArray<ArraySet<Integer>> mAppsOnVirtualDevices = new SparseArray<>();
@@ -158,7 +166,7 @@
     @GuardedBy("mVirtualDeviceManagerLock")
     private boolean isValidVirtualDeviceLocked(IVirtualDevice virtualDevice) {
         try {
-            return mVirtualDevices.contains(virtualDevice.getAssociationId());
+            return mVirtualDevices.contains(virtualDevice.getDeviceId());
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -228,9 +236,9 @@
     }
 
     @VisibleForTesting
-    void notifyRunningAppsChanged(int associationId, ArraySet<Integer> uids) {
+    void notifyRunningAppsChanged(int deviceId, ArraySet<Integer> uids) {
         synchronized (mVirtualDeviceManagerLock) {
-            mAppsOnVirtualDevices.put(associationId, uids);
+            mAppsOnVirtualDevices.put(deviceId, uids);
         }
         mLocalService.onAppsOnVirtualDeviceChanged();
     }
@@ -238,7 +246,7 @@
     @VisibleForTesting
     void addVirtualDevice(VirtualDeviceImpl virtualDevice) {
         synchronized (mVirtualDeviceManagerLock) {
-            mVirtualDevices.put(virtualDevice.getAssociationId(), virtualDevice);
+            mVirtualDevices.put(virtualDevice.getDeviceId(), virtualDevice);
         }
     }
 
@@ -266,60 +274,26 @@
                 throw new IllegalArgumentException("No association with ID " + associationId);
             }
             synchronized (mVirtualDeviceManagerLock) {
-                if (mVirtualDevices.contains(associationId)) {
-                    throw new IllegalStateException(
-                            "Virtual device for association ID " + associationId
-                                    + " already exists");
-                }
                 final int userId = UserHandle.getUserId(callingUid);
                 final CameraAccessController cameraAccessController =
                         mCameraAccessControllers.get(userId);
-                final int uniqueId = sNextUniqueIndex.getAndIncrement();
-
+                final int deviceId = sNextUniqueIndex.getAndIncrement();
                 VirtualDeviceImpl virtualDevice = new VirtualDeviceImpl(getContext(),
-                        associationInfo, token, callingUid, uniqueId,
-                        new VirtualDeviceImpl.OnDeviceCloseListener() {
-                            @Override
-                            public void onClose(int associationId) {
-                                synchronized (mVirtualDeviceManagerLock) {
-                                    VirtualDeviceImpl removedDevice =
-                                            mVirtualDevices.removeReturnOld(associationId);
-                                    if (removedDevice != null) {
-                                        Intent i = new Intent(
-                                                VirtualDeviceManager.ACTION_VIRTUAL_DEVICE_REMOVED);
-                                        i.putExtra(
-                                                VirtualDeviceManager.EXTRA_VIRTUAL_DEVICE_ID,
-                                                removedDevice.getDeviceId());
-                                        i.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
-                                        final long identity = Binder.clearCallingIdentity();
-                                        try {
-                                            getContext().sendBroadcastAsUser(i, UserHandle.ALL);
-                                        } finally {
-                                            Binder.restoreCallingIdentity(identity);
-                                        }
-                                    }
-                                    mAppsOnVirtualDevices.remove(associationId);
-                                    if (cameraAccessController != null) {
-                                        cameraAccessController.stopObservingIfNeeded();
-                                    } else {
-                                        Slog.w(TAG, "cameraAccessController not found for user "
-                                                + userId);
-                                    }
-                                }
-                            }
-                        },
+                        associationInfo, token, callingUid, deviceId,
+                        /* onDeviceCloseListener= */ this::onDeviceClosed,
                         this, activityListener,
                         runningUids -> {
                             cameraAccessController.blockCameraAccessIfNeeded(runningUids);
-                            notifyRunningAppsChanged(associationInfo.getId(), runningUids);
+                            notifyRunningAppsChanged(deviceId, runningUids);
                         },
                         params);
                 if (cameraAccessController != null) {
                     cameraAccessController.startObservingIfNeeded();
+                    mCameraAccessControllersByDeviceId.put(deviceId, cameraAccessController);
                 } else {
                     Slog.w(TAG, "cameraAccessController not found for user " + userId);
                 }
-                mVirtualDevices.put(associationInfo.getId(), virtualDevice);
+                mVirtualDevices.put(deviceId, virtualDevice);
                 return virtualDevice;
             }
         }
@@ -336,7 +310,7 @@
             }
             VirtualDeviceImpl virtualDeviceImpl;
             synchronized (mVirtualDeviceManagerLock) {
-                virtualDeviceImpl = mVirtualDevices.get(virtualDevice.getAssociationId());
+                virtualDeviceImpl = mVirtualDevices.get(virtualDevice.getDeviceId());
                 if (virtualDeviceImpl == null) {
                     throw new SecurityException("Invalid VirtualDevice");
                 }
@@ -417,8 +391,7 @@
         @Nullable
         private AssociationInfo getAssociationInfo(String packageName, int associationId) {
             final int callingUserId = getCallingUserHandle().getIdentifier();
-            final List<AssociationInfo> associations =
-                    mAllAssociations.get(callingUserId);
+            final List<AssociationInfo> associations = mAllAssociations.get(callingUserId);
             if (associations != null) {
                 final int associationSize = associations.size();
                 for (int i = 0; i < associationSize; i++) {
@@ -434,6 +407,29 @@
             return null;
         }
 
+        private void onDeviceClosed(int deviceId) {
+            synchronized (mVirtualDeviceManagerLock) {
+                mVirtualDevices.remove(deviceId);
+                Intent i = new Intent(VirtualDeviceManager.ACTION_VIRTUAL_DEVICE_REMOVED);
+                i.putExtra(VirtualDeviceManager.EXTRA_VIRTUAL_DEVICE_ID, deviceId);
+                i.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+                final long identity = Binder.clearCallingIdentity();
+                try {
+                    getContext().sendBroadcastAsUser(i, UserHandle.ALL);
+                } finally {
+                    Binder.restoreCallingIdentity(identity);
+                }
+                mAppsOnVirtualDevices.remove(deviceId);
+                final CameraAccessController cameraAccessController =
+                        mCameraAccessControllersByDeviceId.removeReturnOld(deviceId);
+                if (cameraAccessController != null) {
+                    cameraAccessController.stopObservingIfNeeded();
+                } else {
+                    Slog.w(TAG, "cameraAccessController not found for device Id " + deviceId);
+                }
+            }
+        }
+
         @Override
         public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
                 throws RemoteException {
@@ -495,6 +491,35 @@
         }
 
         @Override
+        public int getDeviceOwnerUid(int deviceId) {
+            synchronized (mVirtualDeviceManagerLock) {
+                int size = mVirtualDevices.size();
+                for (int i = 0; i < size; i++) {
+                    VirtualDeviceImpl device = mVirtualDevices.valueAt(i);
+                    if (device.getDeviceId() == deviceId) {
+                        return device.getOwnerUid();
+                    }
+                }
+            }
+            return Process.INVALID_UID;
+        }
+
+        @Override
+        public @NonNull Set<Integer> getDeviceIdsForUid(int uid) {
+            ArraySet<Integer> result = new ArraySet<>();
+            synchronized (mVirtualDeviceManagerLock) {
+                int size = mVirtualDevices.size();
+                for (int i = 0; i < size; i++) {
+                    VirtualDeviceImpl device = mVirtualDevices.valueAt(i);
+                    if (device.isAppRunningOnVirtualDevice(uid)) {
+                        result.add(device.getDeviceId());
+                    }
+                }
+            }
+            return result;
+        }
+
+        @Override
         public void onVirtualDisplayCreated(int displayId) {
             final VirtualDisplayListener[] listeners;
             synchronized (mVirtualDeviceManagerLock) {
diff --git a/services/core/java/com/android/server/PersistentDataBlockService.java b/services/core/java/com/android/server/PersistentDataBlockService.java
index b2fc574..5eb0db1 100644
--- a/services/core/java/com/android/server/PersistentDataBlockService.java
+++ b/services/core/java/com/android/server/PersistentDataBlockService.java
@@ -36,6 +36,7 @@
 import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.util.DumpUtils;
+import com.android.server.pm.UserManagerInternal;
 
 import libcore.io.IoUtils;
 
@@ -156,14 +157,19 @@
         mBlockDeviceSize = -1; // Load lazily
     }
 
-    private int getAllowedUid(int userHandle) {
+    private int getAllowedUid() {
+        final UserManagerInternal umInternal = LocalServices.getService(UserManagerInternal.class);
+        final int mainUserId = umInternal.getMainUserId();
+        if (mainUserId < 0) {
+            return -1;
+        }
         String allowedPackage = mContext.getResources()
                 .getString(R.string.config_persistentDataPackageName);
         int allowedUid = -1;
         if (!TextUtils.isEmpty(allowedPackage)) {
             try {
                 allowedUid = mContext.getPackageManager().getPackageUidAsUser(
-                        allowedPackage, PackageManager.MATCH_SYSTEM_ONLY, userHandle);
+                        allowedPackage, PackageManager.MATCH_SYSTEM_ONLY, mainUserId);
             } catch (PackageManager.NameNotFoundException e) {
                 // not expected
                 Slog.e(TAG, "not able to find package " + allowedPackage, e);
@@ -176,7 +182,7 @@
     public void onStart() {
         // Do init on a separate thread, will join in PHASE_ACTIVITY_MANAGER_READY
         SystemServerInitThreadPool.submit(() -> {
-            mAllowedUid = getAllowedUid(UserHandle.USER_SYSTEM);
+            mAllowedUid = getAllowedUid();
             enforceChecksumValidity();
             formatIfOemUnlockEnabled();
             publishBinderService(Context.PERSISTENT_DATA_BLOCK_SERVICE, mService);
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index 32afcca..ee922f9 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -92,6 +92,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.IBatteryStats;
+import com.android.internal.telephony.ICarrierConfigChangeListener;
 import com.android.internal.telephony.ICarrierPrivilegesCallback;
 import com.android.internal.telephony.IOnSubscriptionsChangedListener;
 import com.android.internal.telephony.IPhoneStateListener;
@@ -155,6 +156,7 @@
         IOnSubscriptionsChangedListener onSubscriptionsChangedListenerCallback;
         IOnSubscriptionsChangedListener onOpportunisticSubscriptionsChangedListenerCallback;
         ICarrierPrivilegesCallback carrierPrivilegesCallback;
+        ICarrierConfigChangeListener carrierConfigChangeListener;
 
         int callerUid;
         int callerPid;
@@ -183,6 +185,10 @@
             return carrierPrivilegesCallback != null;
         }
 
+        boolean matchCarrierConfigChangeListener() {
+            return carrierConfigChangeListener != null;
+        }
+
         boolean canReadCallLog() {
             try {
                 return TelephonyPermissions.checkReadCallLog(
@@ -201,6 +207,7 @@
                     + " onOpportunisticSubscriptionsChangedListenererCallback="
                     + onOpportunisticSubscriptionsChangedListenerCallback
                     + " carrierPrivilegesCallback=" + carrierPrivilegesCallback
+                    + " carrierConfigChangeListener=" + carrierConfigChangeListener
                     + " subId=" + subId + " phoneId=" + phoneId + " events=" + eventList + "}";
         }
     }
@@ -3044,6 +3051,82 @@
         }
     }
 
+    @Override
+    public void addCarrierConfigChangeListener(ICarrierConfigChangeListener listener,
+            String pkg, String featureId) {
+        final int callerUserId = UserHandle.getCallingUserId();
+        mAppOps.checkPackage(Binder.getCallingUid(), pkg);
+        if (VDBG) {
+            log("addCarrierConfigChangeListener pkg=" + pii(pkg) + " uid=" + Binder.getCallingUid()
+                    + " myUserId=" + UserHandle.myUserId() + " callerUerId" + callerUserId
+                    + " listener=" + listener + " listener.asBinder=" + listener.asBinder());
+        }
+
+        synchronized (mRecords) {
+            IBinder b = listener.asBinder();
+            boolean doesLimitApply = doesLimitApplyForListeners(Binder.getCallingUid(),
+                    Process.myUid());
+            Record r = add(b, Binder.getCallingUid(), Binder.getCallingPid(), doesLimitApply);
+
+            if (r == null) {
+                loge("Can not create Record instance!");
+                return;
+            }
+
+            r.context = mContext;
+            r.carrierConfigChangeListener = listener;
+            r.callingPackage = pkg;
+            r.callingFeatureId = featureId;
+            r.callerUid = Binder.getCallingUid();
+            r.callerPid = Binder.getCallingPid();
+            r.eventList = new ArraySet<>();
+            if (DBG) {
+                log("addCarrierConfigChangeListener:  Register r=" + r);
+            }
+        }
+    }
+
+    @Override
+    public void removeCarrierConfigChangeListener(ICarrierConfigChangeListener listener,
+            String pkg) {
+        if (DBG) log("removeCarrierConfigChangeListener listener=" + listener + ", pkg=" + pkg);
+        mAppOps.checkPackage(Binder.getCallingUid(), pkg);
+        remove(listener.asBinder());
+    }
+
+    @Override
+    public void notifyCarrierConfigChanged(int phoneId, int subId, int carrierId,
+            int specificCarrierId) {
+        if (!validatePhoneId(phoneId)) {
+            throw new IllegalArgumentException("Invalid phoneId: " + phoneId);
+        }
+        if (!checkNotifyPermission("notifyCarrierConfigChanged")) {
+            loge("Caller has no notify permission!");
+            return;
+        }
+        if (VDBG) {
+            log("notifyCarrierConfigChanged: phoneId=" + phoneId + ", subId=" + subId
+                    + ", carrierId=" + carrierId + ", specificCarrierId=" + specificCarrierId);
+        }
+
+        synchronized (mRecords) {
+            mRemoveList.clear();
+            for (Record r : mRecords) {
+                // Listeners are "global", neither per-slot nor per-sub, so no idMatch check here
+                if (!r.matchCarrierConfigChangeListener()) {
+                    continue;
+                }
+                try {
+                    r.carrierConfigChangeListener.onCarrierConfigChanged(phoneId, subId, carrierId,
+                            specificCarrierId);
+                } catch (RemoteException re) {
+                    mRemoveList.add(r.binder);
+                }
+            }
+            handleRemoveListLocked();
+        }
+    }
+
     @NeverCompile // Avoid size overhead of debugging code.
     @Override
     public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
diff --git a/services/core/java/com/android/server/VcnManagementService.java b/services/core/java/com/android/server/VcnManagementService.java
index 61f7f30..f652cb0 100644
--- a/services/core/java/com/android/server/VcnManagementService.java
+++ b/services/core/java/com/android/server/VcnManagementService.java
@@ -371,8 +371,9 @@
             return new LocationPermissionChecker(context);
         }
 
-        /** Gets the transports that need to be marked as restricted by the VCN */
-        public Set<Integer> getRestrictedTransports(
+        /** Gets transports that need to be marked as restricted by the VCN from CarrierConfig */
+        @VisibleForTesting(visibility = Visibility.PRIVATE)
+        public Set<Integer> getRestrictedTransportsFromCarrierConfig(
                 ParcelUuid subGrp, TelephonySubscriptionSnapshot lastSnapshot) {
             if (!Build.IS_ENG && !Build.IS_USERDEBUG) {
                 return RESTRICTED_TRANSPORTS_DEFAULT;
@@ -398,6 +399,22 @@
             }
             return restrictedTransports;
         }
+
+        /** Gets the transports that need to be marked as restricted by the VCN */
+        public Set<Integer> getRestrictedTransports(
+                ParcelUuid subGrp,
+                TelephonySubscriptionSnapshot lastSnapshot,
+                VcnConfig vcnConfig) {
+            final Set<Integer> restrictedTransports = new ArraySet<>();
+            restrictedTransports.addAll(vcnConfig.getRestrictedUnderlyingNetworkTransports());
+
+            // TODO: b/262269892 Remove the ability to configure restricted transports
+            // via CarrierConfig
+            restrictedTransports.addAll(
+                    getRestrictedTransportsFromCarrierConfig(subGrp, lastSnapshot));
+
+            return restrictedTransports;
+        }
     }
 
     /** Notifies the VcnManagementService that external dependencies can be set up. */
@@ -719,6 +736,7 @@
         if (mVcns.containsKey(subscriptionGroup)) {
             final Vcn vcn = mVcns.get(subscriptionGroup);
             vcn.updateConfig(config);
+            notifyAllPolicyListenersLocked();
         } else {
             // TODO(b/193687515): Support multiple VCNs active at the same time
             if (isActiveSubGroup(subscriptionGroup, mLastSnapshot)) {
@@ -936,7 +954,6 @@
     }
 
     /** Adds the provided listener for receiving VcnUnderlyingNetworkPolicy updates. */
-    @GuardedBy("mLock")
     @Override
     public void addVcnUnderlyingNetworkPolicyListener(
             @NonNull IVcnUnderlyingNetworkPolicyListener listener) {
@@ -963,16 +980,7 @@
         });
     }
 
-    @VisibleForTesting(visibility = Visibility.PRIVATE)
-    void addVcnUnderlyingNetworkPolicyListenerForTest(
-            @NonNull IVcnUnderlyingNetworkPolicyListener listener) {
-        synchronized (mLock) {
-            addVcnUnderlyingNetworkPolicyListener(listener);
-        }
-    }
-
     /** Removes the provided listener from receiving VcnUnderlyingNetworkPolicy updates. */
-    @GuardedBy("mLock")
     @Override
     public void removeVcnUnderlyingNetworkPolicyListener(
             @NonNull IVcnUnderlyingNetworkPolicyListener listener) {
@@ -1062,8 +1070,8 @@
                         isVcnManagedNetwork = true;
                     }
 
-                    final Set<Integer> restrictedTransports =
-                            mDeps.getRestrictedTransports(subGrp, mLastSnapshot);
+                    final Set<Integer> restrictedTransports = mDeps.getRestrictedTransports(
+                            subGrp, mLastSnapshot, mConfigs.get(subGrp));
                     for (int restrictedTransport : restrictedTransports) {
                         if (ncCopy.hasTransport(restrictedTransport)) {
                             if (restrictedTransport == TRANSPORT_CELLULAR) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 5a27af0..b629378 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -7438,11 +7438,10 @@
         if (shareDescription != null) {
             triggerShellBugreport.putExtra(EXTRA_DESCRIPTION, shareDescription);
         }
-        UserHandle callingUser = Binder.getCallingUserHandle();
         final long identity = Binder.clearCallingIdentity();
         try {
             // Send broadcast to shell to trigger bugreport using Bugreport API
-            mContext.sendBroadcastAsUser(triggerShellBugreport, callingUser);
+            mContext.sendBroadcastAsUser(triggerShellBugreport, UserHandle.SYSTEM);
         } finally {
             Binder.restoreCallingIdentity(identity);
         }
diff --git a/services/core/java/com/android/server/am/BroadcastProcessQueue.java b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
index 7013df1..fceefad 100644
--- a/services/core/java/com/android/server/am/BroadcastProcessQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
@@ -532,15 +532,13 @@
     }
 
     public void traceActiveBegin() {
-        final int cookie = mActive.receivers.get(mActiveIndex).hashCode();
         Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
-                runningTraceTrackName, mActive.toShortString() + " scheduled", cookie);
+                runningTraceTrackName, mActive.toShortString() + " scheduled", hashCode());
     }
 
     public void traceActiveEnd() {
-        final int cookie = mActive.receivers.get(mActiveIndex).hashCode();
         Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER,
-                runningTraceTrackName, cookie);
+                runningTraceTrackName, hashCode());
     }
 
     /**
diff --git a/services/core/java/com/android/server/audio/AudioSystemAdapter.java b/services/core/java/com/android/server/audio/AudioSystemAdapter.java
index 1d25e31..c176f29 100644
--- a/services/core/java/com/android/server/audio/AudioSystemAdapter.java
+++ b/services/core/java/com/android/server/audio/AudioSystemAdapter.java
@@ -192,15 +192,7 @@
             synchronized (mDevicesForAttrCache) {
                 res = mDevicesForAttrCache.get(key);
                 if (res == null) {
-                    // result from AudioSystem guaranteed non-null, but could be invalid
-                    // if there is a failure to talk to APM
                     res = AudioSystem.getDevicesForAttributes(attributes, forVolume);
-                    if (res.size() > 1 && res.get(0) != null
-                            && res.get(0).getInternalType() == AudioSystem.DEVICE_NONE) {
-                        Log.e(TAG, "unable to get devices for " + attributes);
-                        // return now, do not put invalid value in cache
-                        return res;
-                    }
                     mDevicesForAttrCache.put(key, res);
                     if (DEBUG_CACHE) {
                         Log.d(TAG, mMethodNames[METHOD_GETDEVICESFORATTRIBUTES]
diff --git a/services/core/java/com/android/server/broadcastradio/IRadioServiceAidlImpl.java b/services/core/java/com/android/server/broadcastradio/IRadioServiceAidlImpl.java
index 6a01042..b66120d 100644
--- a/services/core/java/com/android/server/broadcastradio/IRadioServiceAidlImpl.java
+++ b/services/core/java/com/android/server/broadcastradio/IRadioServiceAidlImpl.java
@@ -86,7 +86,8 @@
 
     @Override
     public ITuner openTuner(int moduleId, RadioManager.BandConfig bandConfig,
-            boolean withAudio, ITunerCallback callback) throws RemoteException {
+            boolean withAudio, ITunerCallback callback, int targetSdkVersion)
+            throws RemoteException {
         if (isDebugEnabled()) {
             Slogf.d(TAG, "Opening module %d", moduleId);
         }
@@ -94,7 +95,7 @@
         if (callback == null) {
             throw new IllegalArgumentException("Callback must not be null");
         }
-        return mHalAidl.openSession(moduleId, bandConfig, withAudio, callback);
+        return mHalAidl.openSession(moduleId, bandConfig, withAudio, callback, targetSdkVersion);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/broadcastradio/IRadioServiceHidlImpl.java b/services/core/java/com/android/server/broadcastradio/IRadioServiceHidlImpl.java
index 408fba1..8a1ba19 100644
--- a/services/core/java/com/android/server/broadcastradio/IRadioServiceHidlImpl.java
+++ b/services/core/java/com/android/server/broadcastradio/IRadioServiceHidlImpl.java
@@ -92,7 +92,8 @@
 
     @Override
     public ITuner openTuner(int moduleId, RadioManager.BandConfig bandConfig,
-            boolean withAudio, ITunerCallback callback) throws RemoteException {
+            boolean withAudio, ITunerCallback callback, int targetSdkVersion)
+            throws RemoteException {
         if (isDebugEnabled()) {
             Slog.d(TAG, "Opening module " + moduleId);
         }
diff --git a/services/core/java/com/android/server/broadcastradio/aidl/BroadcastRadioServiceImpl.java b/services/core/java/com/android/server/broadcastradio/aidl/BroadcastRadioServiceImpl.java
index 03acf72..772cd41 100644
--- a/services/core/java/com/android/server/broadcastradio/aidl/BroadcastRadioServiceImpl.java
+++ b/services/core/java/com/android/server/broadcastradio/aidl/BroadcastRadioServiceImpl.java
@@ -199,7 +199,8 @@
      */
     @Nullable
     public ITuner openSession(int moduleId, @Nullable RadioManager.BandConfig legacyConfig,
-            boolean withAudio, ITunerCallback callback) throws RemoteException {
+            boolean withAudio, ITunerCallback callback, int targetSdkVersion)
+            throws RemoteException {
         if (DEBUG) {
             Slogf.d(TAG, "Open AIDL radio session");
         }
@@ -222,7 +223,7 @@
             }
         }
 
-        TunerSession tunerSession = radioModule.openSession(callback);
+        TunerSession tunerSession = radioModule.openSession(callback, targetSdkVersion);
         if (legacyConfig != null) {
             tunerSession.setConfiguration(legacyConfig);
         }
diff --git a/services/core/java/com/android/server/broadcastradio/aidl/ConversionUtils.java b/services/core/java/com/android/server/broadcastradio/aidl/ConversionUtils.java
index d90f9c4..3c0fda8 100644
--- a/services/core/java/com/android/server/broadcastradio/aidl/ConversionUtils.java
+++ b/services/core/java/com/android/server/broadcastradio/aidl/ConversionUtils.java
@@ -33,6 +33,8 @@
 import android.hardware.radio.ProgramSelector;
 import android.hardware.radio.RadioManager;
 import android.hardware.radio.RadioMetadata;
+import android.hardware.radio.RadioTuner;
+import android.os.Build;
 import android.os.ParcelableException;
 import android.os.ServiceSpecificException;
 import android.util.ArrayMap;
@@ -62,6 +64,11 @@
         throw new UnsupportedOperationException("ConversionUtils class is noninstantiable");
     }
 
+    static boolean isAtLeastU(int targetSdkVersion) {
+        // TODO(b/261770108): Use version code for U.
+        return targetSdkVersion >= Build.VERSION_CODES.CUR_DEVELOPMENT;
+    }
+
     static RuntimeException throwOnError(RuntimeException halException, String action) {
         if (!(halException instanceof ServiceSpecificException)) {
             return new ParcelableException(new RuntimeException(
@@ -89,6 +96,27 @@
         }
     }
 
+    @RadioTuner.TunerResultType
+    static int halResultToTunerResult(int result) {
+        switch (result) {
+            case Result.OK:
+                return RadioTuner.TUNER_RESULT_OK;
+            case Result.INTERNAL_ERROR:
+                return RadioTuner.TUNER_RESULT_INTERNAL_ERROR;
+            case Result.INVALID_ARGUMENTS:
+                return RadioTuner.TUNER_RESULT_INVALID_ARGUMENTS;
+            case Result.INVALID_STATE:
+                return RadioTuner.TUNER_RESULT_INVALID_STATE;
+            case Result.NOT_SUPPORTED:
+                return RadioTuner.TUNER_RESULT_NOT_SUPPORTED;
+            case Result.TIMEOUT:
+                return RadioTuner.TUNER_RESULT_TIMEOUT;
+            case Result.UNKNOWN_ERROR:
+            default:
+                return RadioTuner.TUNER_RESULT_UNKNOWN_ERROR;
+        }
+    }
+
     static VendorKeyValue[] vendorInfoToHalVendorKeyValues(@Nullable Map<String, String> info) {
         if (info == null) {
             return new VendorKeyValue[]{};
@@ -143,6 +171,7 @@
             case ProgramSelector.IDENTIFIER_TYPE_DAB_ENSEMBLE:
             case ProgramSelector.IDENTIFIER_TYPE_DAB_SCID:
             case ProgramSelector.IDENTIFIER_TYPE_DAB_FREQUENCY:
+            case ProgramSelector.IDENTIFIER_TYPE_DAB_DMB_SID_EXT:
                 return ProgramSelector.PROGRAM_TYPE_DAB;
             case ProgramSelector.IDENTIFIER_TYPE_DRMO_SERVICE_ID:
             case ProgramSelector.IDENTIFIER_TYPE_DRMO_FREQUENCY:
@@ -471,6 +500,60 @@
         return new ProgramList.Chunk(chunk.purge, chunk.complete, modified, removed);
     }
 
+    private static boolean isNewIdentifierInU(ProgramSelector.Identifier id) {
+        return id.getType() == ProgramSelector.IDENTIFIER_TYPE_DAB_DMB_SID_EXT;
+    }
+
+    static boolean programSelectorMeetsSdkVersionRequirement(ProgramSelector sel,
+            int targetSdkVersion) {
+        if (isAtLeastU(targetSdkVersion)) {
+            return true;
+        }
+        if (sel.getPrimaryId().getType() == ProgramSelector.IDENTIFIER_TYPE_DAB_DMB_SID_EXT) {
+            return false;
+        }
+        ProgramSelector.Identifier[] secondaryIds = sel.getSecondaryIds();
+        for (int i = 0; i < secondaryIds.length; i++) {
+            if (isNewIdentifierInU(secondaryIds[i])) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    static boolean programInfoMeetsSdkVersionRequirement(RadioManager.ProgramInfo info,
+            int targetSdkVersion) {
+        if (isAtLeastU(targetSdkVersion)) {
+            return true;
+        }
+        if (!programSelectorMeetsSdkVersionRequirement(info.getSelector(), targetSdkVersion)) {
+            return false;
+        }
+        if (isNewIdentifierInU(info.getLogicallyTunedTo())
+                || isNewIdentifierInU(info.getPhysicallyTunedTo())) {
+            return false;
+        }
+        Iterator<ProgramSelector.Identifier> relatedContentIt = info.getRelatedContent().iterator();
+        while (relatedContentIt.hasNext()) {
+            if (isNewIdentifierInU(relatedContentIt.next())) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    static ProgramList.Chunk convertChunkToTargetSdkVersion(ProgramList.Chunk chunk,
+            int targetSdkVersion) {
+        if (isAtLeastU(targetSdkVersion)) {
+            return chunk;
+        }
+        Set<RadioManager.ProgramInfo> modified = chunk.getModified();
+        modified.removeIf(info -> !programInfoMeetsSdkVersionRequirement(info, targetSdkVersion));
+        Set<ProgramSelector.Identifier> removed = chunk.getRemoved();
+        removed.removeIf(id -> isNewIdentifierInU(id));
+        return new ProgramList.Chunk(chunk.isPurge(), chunk.isComplete(), modified, removed);
+    }
+
     public static android.hardware.radio.Announcement announcementFromHalAnnouncement(
             Announcement hwAnnouncement) {
         return new android.hardware.radio.Announcement(
diff --git a/services/core/java/com/android/server/broadcastradio/aidl/RadioModule.java b/services/core/java/com/android/server/broadcastradio/aidl/RadioModule.java
index e956a9c..6193f23 100644
--- a/services/core/java/com/android/server/broadcastradio/aidl/RadioModule.java
+++ b/services/core/java/com/android/server/broadcastradio/aidl/RadioModule.java
@@ -100,7 +100,16 @@
                 synchronized (mLock) {
                     android.hardware.radio.ProgramSelector csel =
                             ConversionUtils.programSelectorFromHalProgramSelector(programSelector);
-                    fanoutAidlCallbackLocked(cb -> cb.onTuneFailed(result, csel));
+                    int tunerResult = ConversionUtils.halResultToTunerResult(result);
+                    fanoutAidlCallbackLocked((cb, sdkVersion) -> {
+                        if (csel != null && !ConversionUtils
+                                .programSelectorMeetsSdkVersionRequirement(csel, sdkVersion)) {
+                            Slogf.e(TAG, "onTuneFailed: cannot send program selector "
+                                    + "requiring higher target SDK version");
+                            return;
+                        }
+                        cb.onTuneFailed(tunerResult, csel);
+                    });
                 }
             });
         }
@@ -112,7 +121,13 @@
                     mCurrentProgramInfo =
                             ConversionUtils.programInfoFromHalProgramInfo(halProgramInfo);
                     RadioManager.ProgramInfo currentProgramInfo = mCurrentProgramInfo;
-                    fanoutAidlCallbackLocked(cb -> {
+                    fanoutAidlCallbackLocked((cb, sdkVersion) -> {
+                        if (!ConversionUtils.programInfoMeetsSdkVersionRequirement(
+                                currentProgramInfo, sdkVersion)) {
+                            Slogf.e(TAG, "onCurrentProgramInfoChanged: cannot send "
+                                    + "program info requiring higher target SDK version");
+                            return;
+                        }
                         cb.onCurrentProgramInfoChanged(currentProgramInfo);
                     });
                 }
@@ -139,7 +154,7 @@
             fireLater(() -> {
                 synchronized (mLock) {
                     mAntennaConnected = connected;
-                    fanoutAidlCallbackLocked(cb -> cb.onAntennaState(connected));
+                    fanoutAidlCallbackLocked((cb, sdkVersion) -> cb.onAntennaState(connected));
                 }
             });
         }
@@ -147,8 +162,11 @@
         @Override
         public void onConfigFlagUpdated(int flag, boolean value) {
             fireLater(() -> {
-                // TODO(b/243853343): implement config flag update method in
-                //  android.hardware.radio.ITunerCallback
+                synchronized (mLock) {
+                    fanoutAidlCallbackLocked((cb, sdkVersion) -> {
+                        cb.onConfigFlagUpdated(flag, value);
+                    });
+                }
             });
         }
 
@@ -158,7 +176,9 @@
                 synchronized (mLock) {
                     Map<String, String> cparam =
                             ConversionUtils.vendorInfoFromHalVendorKeyValues(parameters);
-                    fanoutAidlCallbackLocked(cb -> cb.onParametersUpdated(cparam));
+                    fanoutAidlCallbackLocked((cb, sdkVersion) -> {
+                        cb.onParametersUpdated(cparam);
+                    });
                 }
             });
         }
@@ -222,14 +242,14 @@
         mService.setTunerCallback(mHalTunerCallback);
     }
 
-    TunerSession openSession(android.hardware.radio.ITunerCallback userCb)
+    TunerSession openSession(android.hardware.radio.ITunerCallback userCb, int targetSdkVersion)
             throws RemoteException {
         mLogger.logRadioEvent("Open TunerSession");
         TunerSession tunerSession;
         Boolean antennaConnected;
         RadioManager.ProgramInfo currentProgramInfo;
         synchronized (mLock) {
-            tunerSession = new TunerSession(this, mService, userCb);
+            tunerSession = new TunerSession(this, mService, userCb, targetSdkVersion);
             mAidlTunerSessions.add(tunerSession);
             antennaConnected = mAntennaConnected;
             currentProgramInfo = mCurrentProgramInfo;
@@ -382,7 +402,8 @@
     }
 
     interface AidlCallbackRunnable {
-        void run(android.hardware.radio.ITunerCallback callback) throws RemoteException;
+        void run(android.hardware.radio.ITunerCallback callback, int targetSdkVersion)
+                throws RemoteException;
     }
 
     // Invokes runnable with each TunerSession currently open.
@@ -399,7 +420,8 @@
         List<TunerSession> deadSessions = null;
         for (int i = 0; i < mAidlTunerSessions.size(); i++) {
             try {
-                runnable.run(mAidlTunerSessions.valueAt(i).mCallback);
+                runnable.run(mAidlTunerSessions.valueAt(i).mCallback,
+                        mAidlTunerSessions.valueAt(i).getTargetSdkVersion());
             } catch (DeadObjectException ex) {
                 // The other side died without calling close(), so just purge it from our records.
                 Slogf.e(TAG, "Removing dead TunerSession");
diff --git a/services/core/java/com/android/server/broadcastradio/aidl/TunerSession.java b/services/core/java/com/android/server/broadcastradio/aidl/TunerSession.java
index 1ce4044..d700ed0 100644
--- a/services/core/java/com/android/server/broadcastradio/aidl/TunerSession.java
+++ b/services/core/java/com/android/server/broadcastradio/aidl/TunerSession.java
@@ -21,7 +21,6 @@
 import android.hardware.broadcastradio.ConfigFlag;
 import android.hardware.broadcastradio.IBroadcastRadio;
 import android.hardware.radio.ITuner;
-import android.hardware.radio.ITunerCallback;
 import android.hardware.radio.ProgramList;
 import android.hardware.radio.ProgramSelector;
 import android.hardware.radio.RadioManager;
@@ -47,6 +46,7 @@
     private final RadioLogger mLogger;
     private final RadioModule mModule;
     final android.hardware.radio.ITunerCallback mCallback;
+    private final int mTargetSdkVersion;
     private final IBroadcastRadio mService;
 
     @GuardedBy("mLock")
@@ -61,10 +61,11 @@
     private RadioManager.BandConfig mPlaceHolderConfig;
 
     TunerSession(RadioModule radioModule, IBroadcastRadio service,
-            android.hardware.radio.ITunerCallback callback) {
+            android.hardware.radio.ITunerCallback callback, int targetSdkVersion) {
         mModule = Objects.requireNonNull(radioModule, "radioModule cannot be null");
         mService = Objects.requireNonNull(service, "service cannot be null");
         mCallback = Objects.requireNonNull(callback, "callback cannot be null");
+        mTargetSdkVersion = targetSdkVersion;
         mLogger = new RadioLogger(TAG, TUNER_EVENT_LOGGER_QUEUE_SIZE);
     }
 
@@ -129,7 +130,7 @@
             mPlaceHolderConfig = Objects.requireNonNull(config, "config cannot be null");
         }
         Slogf.i(TAG, "Ignoring setConfiguration - not applicable for broadcastradio HAL AIDL");
-        mModule.fanoutAidlCallback(cb -> cb.onConfigurationChanged(config));
+        mModule.fanoutAidlCallback((cb, sdkVersion) -> cb.onConfigurationChanged(config));
     }
 
     @Override
@@ -178,8 +179,8 @@
     }
 
     @Override
-    public void scan(boolean directionDown, boolean skipSubChannel) throws RemoteException {
-        mLogger.logRadioEvent("Scan with direction %s, skipSubChannel? %s",
+    public void seek(boolean directionDown, boolean skipSubChannel) throws RemoteException {
+        mLogger.logRadioEvent("Seek with direction %s, skipSubChannel? %s",
                 directionDown ? "down" : "up", skipSubChannel ? "yes" : "no");
         if (!RadioServiceUserController.isCurrentOrSystemUser()) {
             Slogf.w(TAG, "Cannot scan on AIDL HAL client from non-current user");
@@ -232,8 +233,7 @@
 
     @Override
     public void cancelAnnouncement() {
-        // TODO(b/244485175): deperacte cancelAnnouncement
-        Slogf.i(TAG, "Announcements control doesn't involve cancelling at the HAL level in AIDL");
+        Slogf.w(TAG, "Announcements control doesn't involve cancelling at the HAL level in AIDL");
     }
 
     @Override
@@ -244,12 +244,14 @@
 
     @Override
     public boolean startBackgroundScan() {
-        Slogf.i(TAG, "Explicit background scan trigger is not supported with HAL AIDL");
+        Slogf.w(TAG, "Explicit background scan trigger is not supported with HAL AIDL");
         if (!RadioServiceUserController.isCurrentOrSystemUser()) {
             Slogf.w(TAG, "Cannot start background scan on AIDL HAL client from non-current user");
             return false;
         }
-        mModule.fanoutAidlCallback(ITunerCallback::onBackgroundScanComplete);
+        mModule.fanoutAidlCallback((cb, sdkVersion) -> {
+            cb.onBackgroundScanComplete();
+        });
         return true;
     }
 
@@ -277,6 +279,10 @@
         mModule.onTunerSessionProgramListFilterChanged(this);
     }
 
+    int getTargetSdkVersion() {
+        return mTargetSdkVersion;
+    }
+
     ProgramList.Filter getProgramListFilter() {
         synchronized (mLock) {
             return mProgramInfoCache == null ? null : mProgramInfoCache.getFilter();
@@ -312,7 +318,14 @@
         }
         for (int i = 0; i < chunks.size(); i++) {
             try {
-                mCallback.onProgramListUpdated(chunks.get(i));
+                if (!ConversionUtils.isAtLeastU(getTargetSdkVersion())) {
+                    ProgramList.Chunk downgradedChunk =
+                            ConversionUtils.convertChunkToTargetSdkVersion(chunks.get(i),
+                                    getTargetSdkVersion());
+                    mCallback.onProgramListUpdated(downgradedChunk);
+                } else {
+                    mCallback.onProgramListUpdated(chunks.get(i));
+                }
             } catch (RemoteException ex) {
                 Slogf.w(TAG, ex, "mCallback.onProgramListUpdated() failed");
             }
diff --git a/services/core/java/com/android/server/broadcastradio/hal1/Tuner.java b/services/core/java/com/android/server/broadcastradio/hal1/Tuner.java
index ed8a37a..8e5f6b5 100644
--- a/services/core/java/com/android/server/broadcastradio/hal1/Tuner.java
+++ b/services/core/java/com/android/server/broadcastradio/hal1/Tuner.java
@@ -189,9 +189,9 @@
     }
 
     @Override
-    public void scan(boolean directionDown, boolean skipSubChannel) {
+    public void seek(boolean directionDown, boolean skipSubChannel) {
         if (!RadioServiceUserController.isCurrentOrSystemUser()) {
-            Slogf.w(TAG, "Cannot scan on HAL 1.x client from non-current user");
+            Slogf.w(TAG, "Cannot seek on HAL 1.x client from non-current user");
             return;
         }
         synchronized (mLock) {
diff --git a/services/core/java/com/android/server/broadcastradio/hal1/TunerCallback.java b/services/core/java/com/android/server/broadcastradio/hal1/TunerCallback.java
index 0cc3833..aa43b75 100644
--- a/services/core/java/com/android/server/broadcastradio/hal1/TunerCallback.java
+++ b/services/core/java/com/android/server/broadcastradio/hal1/TunerCallback.java
@@ -174,8 +174,13 @@
     }
 
     @Override
+    public void onConfigFlagUpdated(int flag, boolean value) {
+        Slog.w(TAG, "Not applicable for HAL 1.x");
+    }
+
+    @Override
     public void onParametersUpdated(Map<String, String> parameters) {
-        Slog.e(TAG, "Not applicable for HAL 1.x");
+        Slog.w(TAG, "Not applicable for HAL 1.x");
     }
 
     @Override
diff --git a/services/core/java/com/android/server/broadcastradio/hal2/BroadcastRadioService.java b/services/core/java/com/android/server/broadcastradio/hal2/BroadcastRadioService.java
index 984bf51..1e31f20 100644
--- a/services/core/java/com/android/server/broadcastradio/hal2/BroadcastRadioService.java
+++ b/services/core/java/com/android/server/broadcastradio/hal2/BroadcastRadioService.java
@@ -169,7 +169,7 @@
     }
 
     public ITuner openSession(int moduleId, @Nullable RadioManager.BandConfig legacyConfig,
-        boolean withAudio, @NonNull ITunerCallback callback) throws RemoteException {
+            boolean withAudio, @NonNull ITunerCallback callback) throws RemoteException {
         Slog.v(TAG, "Open HIDL 2.0 session with module id " + moduleId);
         if (!RadioServiceUserController.isCurrentOrSystemUser()) {
             Slogf.e(TAG, "Cannot open tuner on HAL 2.0 client for non-current user");
diff --git a/services/core/java/com/android/server/broadcastradio/hal2/Convert.java b/services/core/java/com/android/server/broadcastradio/hal2/Convert.java
index 3daf1db..98a450f 100644
--- a/services/core/java/com/android/server/broadcastradio/hal2/Convert.java
+++ b/services/core/java/com/android/server/broadcastradio/hal2/Convert.java
@@ -36,6 +36,7 @@
 import android.hardware.radio.ProgramSelector;
 import android.hardware.radio.RadioManager;
 import android.hardware.radio.RadioMetadata;
+import android.hardware.radio.RadioTuner;
 import android.os.ParcelableException;
 import android.util.Slog;
 
@@ -81,6 +82,27 @@
         }
     }
 
+    @RadioTuner.TunerResultType
+    static int halResultToTunerResult(int result) {
+        switch (result) {
+            case Result.OK:
+                return RadioTuner.TUNER_RESULT_OK;
+            case Result.INTERNAL_ERROR:
+                return RadioTuner.TUNER_RESULT_INTERNAL_ERROR;
+            case Result.INVALID_ARGUMENTS:
+                return RadioTuner.TUNER_RESULT_INVALID_ARGUMENTS;
+            case Result.INVALID_STATE:
+                return RadioTuner.TUNER_RESULT_INVALID_STATE;
+            case Result.NOT_SUPPORTED:
+                return RadioTuner.TUNER_RESULT_NOT_SUPPORTED;
+            case Result.TIMEOUT:
+                return RadioTuner.TUNER_RESULT_TIMEOUT;
+            case Result.UNKNOWN_ERROR:
+            default:
+                return RadioTuner.TUNER_RESULT_UNKNOWN_ERROR;
+        }
+    }
+
     static @NonNull ArrayList<VendorKeyValue>
     vendorInfoToHal(@Nullable Map<String, String> info) {
         if (info == null) return new ArrayList<>();
@@ -130,6 +152,7 @@
             case ProgramSelector.IDENTIFIER_TYPE_DAB_ENSEMBLE:
             case ProgramSelector.IDENTIFIER_TYPE_DAB_SCID:
             case ProgramSelector.IDENTIFIER_TYPE_DAB_FREQUENCY:
+            case ProgramSelector.IDENTIFIER_TYPE_DAB_DMB_SID_EXT:
                 return ProgramSelector.PROGRAM_TYPE_DAB;
             case ProgramSelector.IDENTIFIER_TYPE_DRMO_SERVICE_ID:
             case ProgramSelector.IDENTIFIER_TYPE_DRMO_FREQUENCY:
diff --git a/services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java b/services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java
index 0ea5f0f..59a8154 100644
--- a/services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java
+++ b/services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java
@@ -87,8 +87,9 @@
             fireLater(() -> {
                 android.hardware.radio.ProgramSelector csel =
                         Convert.programSelectorFromHal(programSelector);
+                int tunerResult = Convert.halResultToTunerResult(result);
                 synchronized (mLock) {
-                    fanoutAidlCallbackLocked(cb -> cb.onTuneFailed(result, csel));
+                    fanoutAidlCallbackLocked(cb -> cb.onTuneFailed(tunerResult, csel));
                 }
             });
         }
diff --git a/services/core/java/com/android/server/broadcastradio/hal2/TunerSession.java b/services/core/java/com/android/server/broadcastradio/hal2/TunerSession.java
index 7afee27..204b964 100644
--- a/services/core/java/com/android/server/broadcastradio/hal2/TunerSession.java
+++ b/services/core/java/com/android/server/broadcastradio/hal2/TunerSession.java
@@ -171,8 +171,8 @@
     }
 
     @Override
-    public void scan(boolean directionDown, boolean skipSubChannel) throws RemoteException {
-        mEventLogger.logRadioEvent("Scan with direction %s, skipSubChannel? %s",
+    public void seek(boolean directionDown, boolean skipSubChannel) throws RemoteException {
+        mEventLogger.logRadioEvent("Seek with direction %s, skipSubChannel? %s",
                 directionDown ? "down" : "up", skipSubChannel ? "yes" : "no");
         if (!RadioServiceUserController.isCurrentOrSystemUser()) {
             Slogf.w(TAG, "Cannot scan on HAL 2.0 client from non-current user");
@@ -214,7 +214,7 @@
 
     @Override
     public void cancelAnnouncement() {
-        Slog.i(TAG, "Announcements control doesn't involve cancelling at the HAL level in HAL 2.0");
+        Slog.w(TAG, "Announcements control doesn't involve cancelling at the HAL level in HAL 2.0");
     }
 
     @Override
@@ -225,7 +225,7 @@
 
     @Override
     public boolean startBackgroundScan() {
-        Slog.i(TAG, "Explicit background scan trigger is not supported with HAL 2.0");
+        Slog.w(TAG, "Explicit background scan trigger is not supported with HAL 2.0");
         if (!RadioServiceUserController.isCurrentOrSystemUser()) {
             Slogf.w(TAG,
                     "Cannot start background scan on HAL 2.0 client from non-current user");
diff --git a/services/core/java/com/android/server/clipboard/EmulatorClipboardMonitor.java b/services/core/java/com/android/server/clipboard/EmulatorClipboardMonitor.java
index ab3b250..dce1c96 100644
--- a/services/core/java/com/android/server/clipboard/EmulatorClipboardMonitor.java
+++ b/services/core/java/com/android/server/clipboard/EmulatorClipboardMonitor.java
@@ -29,6 +29,7 @@
 import java.io.EOFException;
 import java.io.FileDescriptor;
 import java.io.InterruptedIOException;
+import java.net.ProtocolException;
 import java.net.SocketException;
 import java.nio.ByteBuffer;
 import java.nio.ByteOrder;
@@ -39,12 +40,16 @@
 // write contents of the host system's clipboard.
 class EmulatorClipboardMonitor implements Consumer<ClipData> {
     private static final String TAG = "EmulatorClipboardMonitor";
+
     private static final String PIPE_NAME = "pipe:clipboard";
     private static final int HOST_PORT = 5000;
-    private final Thread mHostMonitorThread;
+
     private static final boolean LOG_CLIBOARD_ACCESS =
             SystemProperties.getBoolean("ro.boot.qemu.log_clipboard_access", false);
+    private static final int MAX_CLIPBOARD_BYTES = 128 << 20;
+
     private FileDescriptor mPipe = null;
+    private final Thread mHostMonitorThread;
 
     private static byte[] createOpenHandshake() {
         // String.getBytes doesn't include the null terminator,
@@ -97,8 +102,8 @@
         return fd;
     }
 
-    private static byte[] receiveMessage(final FileDescriptor fd) throws ErrnoException,
-            InterruptedIOException, EOFException {
+    private byte[] receiveMessage(final FileDescriptor fd) throws ErrnoException,
+            InterruptedIOException, EOFException, ProtocolException {
         final byte[] lengthBits = new byte[4];
         readFully(fd, lengthBits, 0, lengthBits.length);
 
@@ -106,6 +111,10 @@
         bb.order(ByteOrder.LITTLE_ENDIAN);
         final int msgLen = bb.getInt();
 
+        if (msgLen <= 0 || msgLen > MAX_CLIPBOARD_BYTES) {
+            throw new ProtocolException("Clipboard message length: " + msgLen + " out of bounds.");
+        }
+
         final byte[] msg = new byte[msgLen];
         readFully(fd, msg, 0, msg.length);
 
@@ -150,7 +159,8 @@
                     }
                     setAndroidClipboard.accept(clip);
                 } catch (ErrnoException | EOFException | InterruptedIOException
-                         | InterruptedException e) {
+                         | InterruptedException | ProtocolException | OutOfMemoryError e) {
+                    Slog.w(TAG, "Failure to read from host clipboard", e);
                     setPipeFD(null);
 
                     try {
diff --git a/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java b/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java
index 218be9d..09bec5e 100644
--- a/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java
+++ b/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java
@@ -36,7 +36,6 @@
         void onVirtualDisplayRemoved(int displayId);
     }
 
-
     /** Interface to listen to the changes on the list of app UIDs running on any virtual device. */
     public interface AppsOnVirtualDeviceListener {
         /** Notifies that running apps on any virtual device has changed */
@@ -72,6 +71,25 @@
     public abstract boolean isValidVirtualDevice(IVirtualDevice virtualDevice);
 
     /**
+     * Gets the owner uid for a deviceId.
+     *
+     * @param deviceId which device we're asking about
+     * @return the uid of the app which created and owns the VirtualDevice with the given deviceId,
+     * or {@link android.os.Process#INVALID_UID} if no such device exists.
+     */
+    public abstract int getDeviceOwnerUid(int deviceId);
+
+    /**
+     * Finds VirtualDevices where an app is running.
+     *
+     * @param uid - the app's uid
+     * @return a set of id's of VirtualDevices where the app with the given uid is running.
+     * *Note* this only checks VirtualDevices, and does not include information about whether
+     * the app is running on the default device or not.
+     */
+    public abstract @NonNull Set<Integer> getDeviceIdsForUid(int uid);
+
+    /**
      * Notifies that a virtual display is created.
      *
      * @param displayId The display id of the created virtual display.
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index 4fcde97..19dbee7 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -646,7 +646,10 @@
                 .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
                 .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED)
                 .setTransportInfo(new VpnTransportInfo(
-                        VpnManager.TYPE_VPN_NONE, null /* sessionId */, false /* bypassable */))
+                        VpnManager.TYPE_VPN_NONE,
+                        null /* sessionId */,
+                        false /* bypassable */,
+                        false /* longLivedTcpConnectionsExpensive */))
                 .build();
 
         loadAlwaysOnPackage();
@@ -711,7 +714,10 @@
         mNetworkCapabilities = new NetworkCapabilities.Builder(mNetworkCapabilities)
                 .setUids(null)
                 .setTransportInfo(new VpnTransportInfo(
-                        VpnManager.TYPE_VPN_NONE, null /* sessionId */, false /* bypassable */))
+                        VpnManager.TYPE_VPN_NONE,
+                        null /* sessionId */,
+                        false /* bypassable */,
+                        false /* longLivedTcpConnectionsExpensive */))
                 .build();
     }
 
@@ -1570,7 +1576,8 @@
                 mConfig.allowedApplications, mConfig.disallowedApplications));
 
         capsBuilder.setTransportInfo(
-                new VpnTransportInfo(getActiveVpnType(), mConfig.session, mConfig.allowBypass));
+                new VpnTransportInfo(getActiveVpnType(), mConfig.session, mConfig.allowBypass,
+                        false /* longLivedTcpConnectionsExpensive */));
 
         // Only apps targeting Q and above can explicitly declare themselves as metered.
         // These VPNs are assumed metered unless they state otherwise.
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index c700ccb..329e3ca 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -1852,15 +1852,7 @@
         Settings.Global.putInt(mContext.getContentResolver(),
                 Settings.Global.USER_PREFERRED_RESOLUTION_WIDTH, resolutionWidth);
         mDisplayDeviceRepo.forEachLocked((DisplayDevice device) -> {
-            // If there is a display specific mode, don't override that
-            final Point deviceUserPreferredResolution =
-                    mPersistentDataStore.getUserPreferredResolution(device);
-            final float deviceRefreshRate =
-                    mPersistentDataStore.getUserPreferredRefreshRate(device);
-            if (!isValidResolution(deviceUserPreferredResolution)
-                    && !isValidRefreshRate(deviceRefreshRate)) {
-                device.setUserPreferredDisplayModeLocked(mode);
-            }
+            device.setUserPreferredDisplayModeLocked(mode);
         });
     }
 
@@ -3723,44 +3715,21 @@
         @Override
         public Set<DisplayInfo> getPossibleDisplayInfo(int displayId) {
             synchronized (mSyncRoot) {
-                // Retrieve the group associated with this display id.
-                final int displayGroupId =
-                        mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(displayId);
-                if (displayGroupId == Display.INVALID_DISPLAY_GROUP) {
-                    Slog.w(TAG,
-                            "Can't get possible display info since display group for " + displayId
-                                    + " does not exist");
-                    return new ArraySet<>();
-                }
-
-                // Assume any display in this group can be swapped out for the given display id.
                 Set<DisplayInfo> possibleInfo = new ArraySet<>();
-                final DisplayGroup group = mLogicalDisplayMapper.getDisplayGroupLocked(
-                        displayGroupId);
-                for (int i = 0; i < group.getSizeLocked(); i++) {
-                    final int id = group.getIdLocked(i);
-                    final LogicalDisplay logical = mLogicalDisplayMapper.getDisplayLocked(id);
-                    if (logical == null) {
-                        Slog.w(TAG,
-                                "Can't get possible display info since logical display for "
-                                        + "display id " + id + " does not exist, as part of group "
-                                        + displayGroupId);
-                    } else {
-                        possibleInfo.add(logical.getDisplayInfoLocked());
-                    }
-                }
-
-                // For the supported device states, retrieve the DisplayInfos for the logical
-                // display layout.
+                // For each of supported device states, retrieve the display layout of that state,
+                // and return all of the DisplayInfos (one per state) for the given display id.
                 if (mDeviceStateManager == null) {
                     Slog.w(TAG, "Can't get supported states since DeviceStateManager not ready");
-                } else {
-                    final int[] supportedStates =
-                            mDeviceStateManager.getSupportedStateIdentifiers();
-                    for (int state : supportedStates) {
-                        possibleInfo.addAll(
-                                mLogicalDisplayMapper.getDisplayInfoForStateLocked(state, displayId,
-                                        displayGroupId));
+                    return possibleInfo;
+                }
+                final int[] supportedStates =
+                        mDeviceStateManager.getSupportedStateIdentifiers();
+                DisplayInfo displayInfo;
+                for (int state : supportedStates) {
+                    displayInfo = mLogicalDisplayMapper.getDisplayInfoForStateLocked(state,
+                            displayId);
+                    if (displayInfo != null) {
+                        possibleInfo.add(displayInfo);
                     }
                 }
                 return possibleInfo;
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 9d47892..75415cd 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -453,6 +453,8 @@
     // PowerManager.BRIGHTNESS_INVALID_FLOAT when there's no temporary adjustment set.
     private float mTemporaryAutoBrightnessAdjustment;
 
+    private boolean mUseAutoBrightness;
+
     private boolean mIsRbcActive;
 
     // Whether there's a callback to tell listeners the display has changed scheduled to run. When
@@ -683,6 +685,7 @@
     @Override
     public void onSwitchUser(@UserIdInt int newUserId) {
         handleSettingsChange(true /* userSwitch */);
+        handleBrightnessModeChange();
         if (mBrightnessTracker != null) {
             mBrightnessTracker.onSwitchUser(newUserId);
         }
@@ -930,6 +933,10 @@
         mContext.getContentResolver().registerContentObserver(
                 Settings.System.getUriFor(Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ),
                 false /*notifyForDescendants*/, mSettingsObserver, UserHandle.USER_ALL);
+        mContext.getContentResolver().registerContentObserver(
+                Settings.System.getUriFor(Settings.System.SCREEN_BRIGHTNESS_MODE),
+                false /*notifyForDescendants*/, mSettingsObserver, UserHandle.USER_ALL);
+        handleBrightnessModeChange();
     }
 
     private void setUpAutoBrightness(Resources resources, Handler handler) {
@@ -1335,11 +1342,11 @@
 
         final boolean autoBrightnessEnabledInDoze =
                 mAllowAutoBrightnessWhileDozingConfig && Display.isDozeState(state);
-        final boolean autoBrightnessEnabled = mPowerRequest.useAutoBrightness
+        final boolean autoBrightnessEnabled = mUseAutoBrightness
                 && (state == Display.STATE_ON || autoBrightnessEnabledInDoze)
                 && Float.isNaN(brightnessState)
                 && mAutomaticBrightnessController != null;
-        final boolean autoBrightnessDisabledDueToDisplayOff = mPowerRequest.useAutoBrightness
+        final boolean autoBrightnessDisabledDueToDisplayOff = mUseAutoBrightness
                 && !(state == Display.STATE_ON || autoBrightnessEnabledInDoze);
         final int autoBrightnessState = autoBrightnessEnabled
                 ? AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED
@@ -1691,7 +1698,7 @@
                 || brightnessAdjustmentFlags != 0) {
             float lastBrightness = mLastBrightnessEvent.getBrightness();
             mTempBrightnessEvent.setInitialBrightness(lastBrightness);
-            mTempBrightnessEvent.setAutomaticBrightnessEnabled(mPowerRequest.useAutoBrightness);
+            mTempBrightnessEvent.setAutomaticBrightnessEnabled(mUseAutoBrightness);
             mLastBrightnessEvent.copyFrom(mTempBrightnessEvent);
             BrightnessEvent newEvent = new BrightnessEvent(mTempBrightnessEvent);
             // Adjustment flags (and user-set flag) only get added after the equality checks since
@@ -2341,6 +2348,18 @@
         sendUpdatePowerState();
     }
 
+    private void handleBrightnessModeChange() {
+        final int screenBrightnessModeSetting = Settings.System.getIntForUser(
+                mContext.getContentResolver(),
+                Settings.System.SCREEN_BRIGHTNESS_MODE,
+                Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL, UserHandle.USER_CURRENT);
+        mHandler.post(() -> {
+            mUseAutoBrightness = screenBrightnessModeSetting
+                    == Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC;
+            updatePowerState();
+        });
+    }
+
     private float getAutoBrightnessAdjustmentSetting() {
         final float adj = Settings.System.getFloatForUser(mContext.getContentResolver(),
                 Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, 0.0f, UserHandle.USER_CURRENT);
@@ -2425,7 +2444,7 @@
     private void notifyBrightnessTrackerChanged(float brightness, boolean userInitiated,
             boolean hadUserDataPoint) {
         final float brightnessInNits = convertToNits(brightness);
-        if (mPowerRequest.useAutoBrightness && brightnessInNits >= 0.0f
+        if (mUseAutoBrightness && brightnessInNits >= 0.0f
                 && mAutomaticBrightnessController != null && mBrightnessTracker != null) {
             // We only want to track changes on devices that can actually map the display backlight
             // values into a physical brightness unit since the value provided by the API is in
@@ -2897,7 +2916,11 @@
 
         @Override
         public void onChange(boolean selfChange, Uri uri) {
-            handleSettingsChange(false /* userSwitch */);
+            if (uri.equals(Settings.System.getUriFor(Settings.System.SCREEN_BRIGHTNESS_MODE))) {
+                handleBrightnessModeChange();
+            } else {
+                handleSettingsChange(false /* userSwitch */);
+            }
         }
     }
 
diff --git a/services/core/java/com/android/server/display/DisplayPowerController2.java b/services/core/java/com/android/server/display/DisplayPowerController2.java
index 346b340..84b6da8 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController2.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController2.java
@@ -396,6 +396,8 @@
     // PowerManager.BRIGHTNESS_INVALID_FLOAT when there's no temporary adjustment set.
     private float mTemporaryAutoBrightnessAdjustment;
 
+    private boolean mUseAutoBrightness;
+
     private boolean mIsRbcActive;
 
     // Animators.
@@ -600,6 +602,7 @@
     @Override
     public void onSwitchUser(@UserIdInt int newUserId) {
         handleSettingsChange(true /* userSwitch */);
+        handleBrightnessModeChange();
         if (mBrightnessTracker != null) {
             mBrightnessTracker.onSwitchUser(newUserId);
         }
@@ -842,6 +845,10 @@
         mContext.getContentResolver().registerContentObserver(
                 Settings.System.getUriFor(Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ),
                 false /*notifyForDescendants*/, mSettingsObserver, UserHandle.USER_ALL);
+        mContext.getContentResolver().registerContentObserver(
+                Settings.System.getUriFor(Settings.System.SCREEN_BRIGHTNESS_MODE),
+                false /*notifyForDescendants*/, mSettingsObserver, UserHandle.USER_ALL);
+        handleBrightnessModeChange();
     }
 
     private void setUpAutoBrightness(Resources resources, Handler handler) {
@@ -1174,11 +1181,11 @@
         final boolean autoBrightnessEnabledInDoze =
                 mDisplayBrightnessController.isAllowAutoBrightnessWhileDozingConfig()
                         && Display.isDozeState(state);
-        final boolean autoBrightnessEnabled = mPowerRequest.useAutoBrightness
+        final boolean autoBrightnessEnabled = mUseAutoBrightness
                 && (state == Display.STATE_ON || autoBrightnessEnabledInDoze)
                 && Float.isNaN(brightnessState)
                 && mAutomaticBrightnessController != null;
-        final boolean autoBrightnessDisabledDueToDisplayOff = mPowerRequest.useAutoBrightness
+        final boolean autoBrightnessDisabledDueToDisplayOff = mUseAutoBrightness
                 && !(state == Display.STATE_ON || autoBrightnessEnabledInDoze);
         final int autoBrightnessState = autoBrightnessEnabled
                 ? AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED
@@ -1510,7 +1517,7 @@
                 || brightnessAdjustmentFlags != 0) {
             float lastBrightness = mLastBrightnessEvent.getBrightness();
             mTempBrightnessEvent.setInitialBrightness(lastBrightness);
-            mTempBrightnessEvent.setAutomaticBrightnessEnabled(mPowerRequest.useAutoBrightness);
+            mTempBrightnessEvent.setAutomaticBrightnessEnabled(mUseAutoBrightness);
             mLastBrightnessEvent.copyFrom(mTempBrightnessEvent);
             BrightnessEvent newEvent = new BrightnessEvent(mTempBrightnessEvent);
             // Adjustment flags (and user-set flag) only get added after the equality checks since
@@ -2045,6 +2052,18 @@
         sendUpdatePowerState();
     }
 
+    private void handleBrightnessModeChange() {
+        final int screenBrightnessModeSetting = Settings.System.getIntForUser(
+                mContext.getContentResolver(),
+                Settings.System.SCREEN_BRIGHTNESS_MODE,
+                Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL, UserHandle.USER_CURRENT);
+        mHandler.post(() -> {
+            mUseAutoBrightness = screenBrightnessModeSetting
+                    == Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC;
+            updatePowerState();
+        });
+    }
+
     private float getAutoBrightnessAdjustmentSetting() {
         final float adj = Settings.System.getFloatForUser(mContext.getContentResolver(),
                 Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, 0.0f, UserHandle.USER_CURRENT);
@@ -2131,7 +2150,7 @@
     private void notifyBrightnessTrackerChanged(float brightness, boolean userInitiated,
             boolean hadUserDataPoint) {
         final float brightnessInNits = convertToNits(brightness);
-        if (mPowerRequest.useAutoBrightness && brightnessInNits >= 0.0f
+        if (mUseAutoBrightness && brightnessInNits >= 0.0f
                 && mAutomaticBrightnessController != null && mBrightnessTracker != null) {
             // We only want to track changes on devices that can actually map the display backlight
             // values into a physical brightness unit since the value provided by the API is in
@@ -2499,7 +2518,11 @@
 
         @Override
         public void onChange(boolean selfChange, Uri uri) {
-            handleSettingsChange(false /* userSwitch */);
+            if (uri.equals(Settings.System.getUriFor(Settings.System.SCREEN_BRIGHTNESS_MODE))) {
+                handleBrightnessModeChange();
+            } else {
+                handleSettingsChange(false /* userSwitch */);
+            }
         }
     }
 
diff --git a/services/core/java/com/android/server/display/LogicalDisplayMapper.java b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
index 80f47a1..d7983ae 100644
--- a/services/core/java/com/android/server/display/LogicalDisplayMapper.java
+++ b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
@@ -19,6 +19,7 @@
 import static android.view.Display.DEFAULT_DISPLAY;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.content.Context;
 import android.hardware.devicestate.DeviceStateManager;
 import android.os.Handler;
@@ -29,7 +30,6 @@
 import android.os.SystemProperties;
 import android.text.TextUtils;
 import android.util.ArrayMap;
-import android.util.ArraySet;
 import android.util.IndentingPrintWriter;
 import android.util.Slog;
 import android.util.SparseArray;
@@ -45,7 +45,6 @@
 
 import java.io.PrintWriter;
 import java.util.Arrays;
-import java.util.Set;
 import java.util.function.Consumer;
 
 /**
@@ -324,58 +323,44 @@
     }
 
     /**
-     * Returns the set of {@link DisplayInfo} for this device state, only fetching the info that is
-     * part of the same display group as the provided display id. The DisplayInfo represent the
-     * logical display layouts possible for the given device state.
+     * Returns the {@link DisplayInfo} for this device state, indicated by the given display id. The
+     * DisplayInfo represents the attributes of the indicated display in the layout associated with
+     * this state. This is used to get display information for various displays in various states;
+     * e.g. to help apps preload resources for the possible display states.
      *
      * @param deviceState the state to query possible layouts for
-     * @param displayId   the display id to apply to all displays within the group
-     * @param groupId     the display group to filter display info for. Must be the same group as
-     *                    the display with the provided display id.
+     * @param displayId   the display id to retrieve
+     * @return {@code null} if no corresponding {@link DisplayInfo} could be found, or the
+     * {@link DisplayInfo} with a matching display id.
      */
-    public Set<DisplayInfo> getDisplayInfoForStateLocked(int deviceState, int displayId,
-            int groupId) {
-        Set<DisplayInfo> displayInfos = new ArraySet<>();
+    @Nullable
+    public DisplayInfo getDisplayInfoForStateLocked(int deviceState, int displayId) {
+        // Retrieve the layout for this particular state.
         final Layout layout = mDeviceStateToLayoutMap.get(deviceState);
-        final int layoutSize = layout.size();
-        for (int i = 0; i < layoutSize; i++) {
-            Layout.Display displayLayout = layout.getAt(i);
-            if (displayLayout == null) {
-                continue;
-            }
-
-            // If the underlying display-device we want to use for this display
-            // doesn't exist, then skip it. This can happen at startup as display-devices
-            // trickle in one at a time. When the new display finally shows up, the layout is
-            // recalculated so that the display is properly added to the current layout.
-            final DisplayAddress address = displayLayout.getAddress();
-            final DisplayDevice device = mDisplayDeviceRepo.getByAddressLocked(address);
-            if (device == null) {
-                Slog.w(TAG, "The display device (" + address + "), is not available"
-                        + " for the display state " + deviceState);
-                continue;
-            }
-
-            // Find or create the LogicalDisplay to map the DisplayDevice to.
-            final int logicalDisplayId = displayLayout.getLogicalDisplayId();
-            final LogicalDisplay logicalDisplay = getDisplayLocked(logicalDisplayId);
-            if (logicalDisplay == null) {
-                Slog.w(TAG, "The logical display (" + address + "), is not available"
-                        + " for the display state " + deviceState);
-                continue;
-            }
-            final DisplayInfo temp = logicalDisplay.getDisplayInfoLocked();
-            DisplayInfo displayInfo = new DisplayInfo(temp);
-            if (displayInfo.displayGroupId != groupId) {
-                // Ignore any displays not in the provided group.
-                continue;
-            }
-            // A display in the same group can be swapped out at any point, so set the display id
-            // for all results to the provided display id.
-            displayInfo.displayId = displayId;
-            displayInfos.add(displayInfo);
+        if (layout == null) {
+            return null;
         }
-        return displayInfos;
+        // Retrieve the details of the given display within this layout.
+        Layout.Display display = layout.getById(displayId);
+        if (display == null) {
+            return null;
+        }
+        // Retrieve the display info for the display that matches the display id.
+        final DisplayDevice device = mDisplayDeviceRepo.getByAddressLocked(display.getAddress());
+        if (device == null) {
+            Slog.w(TAG, "The display device (" + display.getAddress() + "), is not available"
+                    + " for the display state " + mDeviceState);
+            return null;
+        }
+        LogicalDisplay logicalDisplay = getDisplayLocked(device, /* includeDisabled= */ true);
+        if (logicalDisplay == null) {
+            Slog.w(TAG, "The logical display associated with address (" + display.getAddress()
+                    + "), is not available for the display state " + mDeviceState);
+            return null;
+        }
+        DisplayInfo displayInfo = new DisplayInfo(logicalDisplay.getDisplayInfoLocked());
+        displayInfo.displayId = displayId;
+        return displayInfo;
     }
 
     public void dumpLocked(PrintWriter pw) {
diff --git a/services/core/java/com/android/server/incident/IncidentCompanionService.java b/services/core/java/com/android/server/incident/IncidentCompanionService.java
index b8e7d49..87fe785 100644
--- a/services/core/java/com/android/server/incident/IncidentCompanionService.java
+++ b/services/core/java/com/android/server/incident/IncidentCompanionService.java
@@ -34,6 +34,7 @@
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.UserHandle;
+import android.os.UserManager;
 import android.util.Log;
 
 import com.android.internal.util.DumpUtils;
@@ -127,21 +128,21 @@
             try {
                 final Context context = getContext();
 
-                // Get the current admin user. Only they can do incident reports.
-                final int currentAdminUser = getCurrentUserIfAdmin();
-                if (currentAdminUser == UserHandle.USER_NULL) {
+                final int primaryUser = getAndValidateUser(context);
+                if (primaryUser == UserHandle.USER_NULL) {
                     return;
                 }
 
                 final Intent intent = new Intent(Intent.ACTION_INCIDENT_REPORT_READY);
                 intent.setComponent(new ComponentName(pkg, cls));
 
-                Log.d(TAG, "sendReportReadyBroadcast sending currentUser=" + currentAdminUser
-                        + " userHandle=" + UserHandle.of(currentAdminUser)
+                Log.d(TAG, "sendReportReadyBroadcast sending primaryUser=" + primaryUser
+                        + " userHandle=" + UserHandle.getUserHandleForUid(primaryUser)
                         + " intent=" + intent);
 
+                // Send it to the primary user.  Only they can do incident reports.
                 context.sendBroadcastAsUserMultiplePermissions(intent,
-                        UserHandle.of(currentAdminUser),
+                        UserHandle.getUserHandleForUid(primaryUser),
                         DUMP_AND_USAGE_STATS_PERMISSIONS);
             } finally {
                 Binder.restoreCallingIdentity(ident);
@@ -413,10 +414,10 @@
     }
 
     /**
-     * Check whether the current user is an admin user, and return the user id if they are.
+     * Check whether the current user is the primary user, and return the user id if they are.
      * Returns UserHandle.USER_NULL if not valid.
      */
-    public static int getCurrentUserIfAdmin() {
+    public static int getAndValidateUser(Context context) {
         // Current user
         UserInfo currentUser;
         try {
@@ -426,21 +427,28 @@
             throw new RuntimeException(ex);
         }
 
+        // Primary user
+        final UserManager um = UserManager.get(context);
+        final UserInfo primaryUser = um.getPrimaryUser();
+
         // Check that we're using the right user.
         if (currentUser == null) {
             Log.w(TAG, "No current user.  Nobody to approve the report."
                     + " The report will be denied.");
             return UserHandle.USER_NULL;
         }
-
-        if (!currentUser.isAdmin()) {
-            Log.w(TAG, "Only an admin user running in foreground can approve "
-                    + "bugreports, but the current foreground user is not an admin user. "
-                    + "The report will be denied.");
+        if (primaryUser == null) {
+            Log.w(TAG, "No primary user.  Nobody to approve the report."
+                    + " The report will be denied.");
+            return UserHandle.USER_NULL;
+        }
+        if (primaryUser.id != currentUser.id) {
+            Log.w(TAG, "Only the primary user can approve bugreports, but they are not"
+                    + " the current user. The report will be denied.");
             return UserHandle.USER_NULL;
         }
 
-        return currentUser.id;
+        return primaryUser.id;
     }
 }
 
diff --git a/services/core/java/com/android/server/incident/PendingReports.java b/services/core/java/com/android/server/incident/PendingReports.java
index 6285bc3..f39bebf 100644
--- a/services/core/java/com/android/server/incident/PendingReports.java
+++ b/services/core/java/com/android/server/incident/PendingReports.java
@@ -16,7 +16,6 @@
 
 package com.android.server.incident;
 
-import android.annotation.UserIdInt;
 import android.app.AppOpsManager;
 import android.app.BroadcastOptions;
 import android.content.ComponentName;
@@ -273,19 +272,15 @@
             return;
         }
 
-        // Find the current user of the device and check if they are an admin.
-        final int currentAdminUser = getCurrentUserIfAdmin();
-
-        // Deny the report if the current admin user is null
-        // or not the user who requested the report.
-        if (currentAdminUser == UserHandle.USER_NULL
-                || currentAdminUser != UserHandle.getUserId(callingUid)) {
+        // Find the primary user of this device.
+        final int primaryUser = getAndValidateUser();
+        if (primaryUser == UserHandle.USER_NULL) {
             denyReportBeforeAddingRec(listener, callingPackage);
             return;
         }
 
         // Find the approver app (hint: it's PermissionController).
-        final ComponentName receiver = getApproverComponent(currentAdminUser);
+        final ComponentName receiver = getApproverComponent(primaryUser);
         if (receiver == null) {
             // We couldn't find an approver... so deny the request here and now, before we
             // do anything else.
@@ -303,26 +298,26 @@
         try {
             listener.asBinder().linkToDeath(() -> {
                 Log.i(TAG, "Got death notification listener=" + listener);
-                cancelReportImpl(listener, receiver, currentAdminUser);
+                cancelReportImpl(listener, receiver, primaryUser);
             }, 0);
         } catch (RemoteException ex) {
             Log.e(TAG, "Remote died while trying to register death listener: " + rec.getUri());
             // First, remove from our list.
-            cancelReportImpl(listener, receiver, currentAdminUser);
+            cancelReportImpl(listener, receiver, primaryUser);
         }
 
         // Go tell Permission controller to start asking the user.
-        sendBroadcast(receiver, currentAdminUser);
+        sendBroadcast(receiver, primaryUser);
     }
 
     /**
      * Cancel a pending report request (because of an explicit call to cancel)
      */
     private void cancelReportImpl(IIncidentAuthListener listener) {
-        final int currentAdminUser = getCurrentUserIfAdmin();
-        final ComponentName receiver = getApproverComponent(currentAdminUser);
-        if (currentAdminUser != UserHandle.USER_NULL && receiver != null) {
-            cancelReportImpl(listener, receiver, currentAdminUser);
+        final int primaryUser = getAndValidateUser();
+        final ComponentName receiver = getApproverComponent(primaryUser);
+        if (primaryUser != UserHandle.USER_NULL && receiver != null) {
+            cancelReportImpl(listener, receiver, primaryUser);
         }
     }
 
@@ -331,13 +326,13 @@
      * by the calling app, or because of a binder death).
      */
     private void cancelReportImpl(IIncidentAuthListener listener, ComponentName receiver,
-            @UserIdInt int user) {
+            int primaryUser) {
         // First, remove from our list.
         synchronized (mLock) {
             removePendingReportRecLocked(listener);
         }
         // Second, call back to PermissionController to say it's canceled.
-        sendBroadcast(receiver, user);
+        sendBroadcast(receiver, primaryUser);
     }
 
     /**
@@ -347,21 +342,21 @@
      * cleanup cases to keep the apps' list in sync with ours.
      */
     private void sendBroadcast() {
-        final int currentAdminUser = getCurrentUserIfAdmin();
-        if (currentAdminUser == UserHandle.USER_NULL) {
+        final int primaryUser = getAndValidateUser();
+        if (primaryUser == UserHandle.USER_NULL) {
             return;
         }
-        final ComponentName receiver = getApproverComponent(currentAdminUser);
+        final ComponentName receiver = getApproverComponent(primaryUser);
         if (receiver == null) {
             return;
         }
-        sendBroadcast(receiver, currentAdminUser);
+        sendBroadcast(receiver, primaryUser);
     }
 
     /**
      * Send the confirmation broadcast.
      */
-    private void sendBroadcast(ComponentName receiver, int currentUser) {
+    private void sendBroadcast(ComponentName receiver, int primaryUser) {
         final Intent intent = new Intent(Intent.ACTION_PENDING_INCIDENT_REPORTS_CHANGED);
         intent.setComponent(receiver);
         intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
@@ -369,8 +364,8 @@
         final BroadcastOptions options = BroadcastOptions.makeBasic();
         options.setBackgroundActivityStartsAllowed(true);
 
-        // Send it to the current user.
-        mContext.sendBroadcastAsUser(intent, UserHandle.of(currentUser),
+        // Send it to the primary user.
+        mContext.sendBroadcastAsUser(intent, UserHandle.getUserHandleForUid(primaryUser),
                 android.Manifest.permission.APPROVE_INCIDENT_REPORTS, options.toBundle());
     }
 
@@ -425,11 +420,11 @@
     }
 
     /**
-     * Check whether the current user is an admin user, and return the user id if they are.
+     * Check whether the current user is the primary user, and return the user id if they are.
      * Returns UserHandle.USER_NULL if not valid.
      */
-    private int getCurrentUserIfAdmin() {
-        return IncidentCompanionService.getCurrentUserIfAdmin();
+    private int getAndValidateUser() {
+        return IncidentCompanionService.getAndValidateUser(mContext);
     }
 
     /**
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index 2dc1e1c..ab69de9 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -1027,11 +1027,18 @@
     }
 
     private void enforceFrpResolved() {
+        final int mainUserId = mInjector.getUserManagerInternal().getMainUserId();
+        if (mainUserId < 0) {
+            Slog.i(TAG, "No Main user on device; skip enforceFrpResolved");
+            return;
+        }
         final ContentResolver cr = mContext.getContentResolver();
+
         final boolean inSetupWizard = Settings.Secure.getIntForUser(cr,
-                Settings.Secure.USER_SETUP_COMPLETE, 0, UserHandle.USER_SYSTEM) == 0;
-        final boolean secureFrp = Settings.Secure.getIntForUser(cr,
-                Settings.Secure.SECURE_FRP_MODE, 0, UserHandle.USER_SYSTEM) == 1;
+                Settings.Secure.USER_SETUP_COMPLETE, 0, mainUserId) == 0;
+        final boolean secureFrp = Settings.Global.getInt(cr,
+                Settings.Global.SECURE_FRP_MODE, 0) == 1;
+
         if (inSetupWizard && secureFrp) {
             throw new SecurityException("Cannot change credential in SUW while factory reset"
                     + " protection is not resolved yet");
diff --git a/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java b/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
index 2fdc4cd..58428ca 100644
--- a/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
+++ b/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
@@ -33,8 +33,8 @@
 import android.os.SystemClock;
 import android.os.SystemProperties;
 import android.os.UserHandle;
+import android.os.UserManager;
 import android.telephony.TelephonyManager;
-import android.text.TextUtils;
 import android.util.ArraySet;
 import android.util.Slog;
 
@@ -189,10 +189,10 @@
     }
 
     /**
-     * Validates that the current user is an admin user or, when bugreport is requested remotely
-     * that the current user is an affiliated user.
+     * Validates that the current user is the primary user or when bugreport is requested remotely
+     * and current user is affiliated user.
      *
-     * @throws IllegalArgumentException if the current user is not an admin user
+     * @throws IllegalArgumentException if the current user is not the primary user
      */
     private void ensureUserCanTakeBugReport(int bugreportMode) {
         UserInfo currentUser = null;
@@ -202,17 +202,20 @@
             // Impossible to get RemoteException for an in-process call.
         }
 
+        UserInfo primaryUser = UserManager.get(mContext).getPrimaryUser();
         if (currentUser == null) {
-            logAndThrow("There is no current user, so no bugreport can be requested.");
+            logAndThrow("No current user. Only primary user is allowed to take bugreports.");
         }
-
-        if (!currentUser.isAdmin()) {
+        if (primaryUser == null) {
+            logAndThrow("No primary user. Only primary user is allowed to take bugreports.");
+        }
+        if (primaryUser.id != currentUser.id) {
             if (bugreportMode == BugreportParams.BUGREPORT_MODE_REMOTE
                     && isCurrentUserAffiliated(currentUser.id)) {
                 return;
             }
-            logAndThrow(TextUtils.formatSimple("Current user %s is not an admin user."
-                    + " Only admin users are allowed to take bugreport.", currentUser.id));
+            logAndThrow("Current user not primary user. Only primary user"
+                    + " is allowed to take bugreports.");
         }
     }
 
diff --git a/services/core/java/com/android/server/pm/GentleUpdateHelper.java b/services/core/java/com/android/server/pm/GentleUpdateHelper.java
index 247ac90..7cb096d 100644
--- a/services/core/java/com/android/server/pm/GentleUpdateHelper.java
+++ b/services/core/java/com/android/server/pm/GentleUpdateHelper.java
@@ -16,7 +16,12 @@
 
 package com.android.server.pm;
 
+import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
+import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE;
+
 import android.annotation.WorkerThread;
+import android.app.ActivityManager;
+import android.app.ActivityManager.RunningAppProcessInfo;
 import android.app.ActivityThread;
 import android.app.job.JobInfo;
 import android.app.job.JobParameters;
@@ -28,6 +33,9 @@
 import android.content.pm.PackageInstaller.InstallConstraintsResult;
 import android.os.Handler;
 import android.os.Looper;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.text.format.DateUtils;
 import android.util.Slog;
 
 import java.util.ArrayDeque;
@@ -73,12 +81,24 @@
         public final List<String> packageNames;
         public final InstallConstraints constraints;
         public final CompletableFuture<InstallConstraintsResult> future;
+        private final long mFinishTime;
+
+        /**
+         * Note {@code timeoutMillis} will be clamped to 0 ~ one week to avoid overflow.
+         */
         PendingInstallConstraintsCheck(List<String> packageNames,
                 InstallConstraints constraints,
-                CompletableFuture<InstallConstraintsResult> future) {
+                CompletableFuture<InstallConstraintsResult> future,
+                long timeoutMillis) {
             this.packageNames = packageNames;
             this.constraints = constraints;
             this.future = future;
+
+            timeoutMillis = Math.max(0, Math.min(DateUtils.WEEK_IN_MILLIS, timeoutMillis));
+            mFinishTime = SystemClock.elapsedRealtime() + timeoutMillis;
+        }
+        public boolean isTimedOut() {
+            return SystemClock.elapsedRealtime() >= mFinishTime;
         }
     }
 
@@ -95,15 +115,31 @@
         mAppStateHelper = appStateHelper;
     }
 
+    void systemReady() {
+        var am = mContext.getSystemService(ActivityManager.class);
+        // Monitor top-visible apps
+        am.addOnUidImportanceListener(this::onUidImportance, IMPORTANCE_FOREGROUND);
+        // Monitor foreground apps
+        am.addOnUidImportanceListener(this::onUidImportance, IMPORTANCE_FOREGROUND_SERVICE);
+    }
+
     /**
      * Checks if install constraints are satisfied for the given packages.
      */
     CompletableFuture<InstallConstraintsResult> checkInstallConstraints(
-            List<String> packageNames, InstallConstraints constraints) {
+            List<String> packageNames, InstallConstraints constraints,
+            long timeoutMillis) {
         var future = new CompletableFuture<InstallConstraintsResult>();
         mHandler.post(() -> {
+            long clampedTimeoutMillis = timeoutMillis;
+            if (constraints.isRequireDeviceIdle()) {
+                // Device-idle-constraint is required. Clamp the timeout to ensure
+                // timeout-check happens after device-idle-check.
+                clampedTimeoutMillis = Math.max(timeoutMillis, PENDING_CHECK_MILLIS);
+            }
+
             var pendingCheck = new PendingInstallConstraintsCheck(
-                    packageNames, constraints, future);
+                    packageNames, constraints, future, clampedTimeoutMillis);
             if (constraints.isRequireDeviceIdle()) {
                 mPendingChecks.add(pendingCheck);
                 // JobScheduler doesn't provide queries about whether the device is idle.
@@ -113,8 +149,17 @@
                 scheduleIdleJob();
                 mHandler.postDelayed(() -> processPendingCheck(pendingCheck, false),
                         PENDING_CHECK_MILLIS);
-            } else {
-                processPendingCheck(pendingCheck, false);
+            } else if (!processPendingCheck(pendingCheck, false)) {
+                // Not resolved. Schedule a job for re-check
+                mPendingChecks.add(pendingCheck);
+                scheduleIdleJob();
+            }
+
+            if (!future.isDone()) {
+                // Ensure the pending check is resolved after timeout, no matter constraints
+                // satisfied or not.
+                mHandler.postDelayed(() -> processPendingCheck(pendingCheck, false),
+                        clampedTimeoutMillis);
             }
         });
         return future;
@@ -143,29 +188,77 @@
     }
 
     @WorkerThread
-    private void processPendingCheck(PendingInstallConstraintsCheck pendingCheck, boolean isIdle) {
+    private boolean areConstraintsSatisfied(List<String> packageNames,
+            InstallConstraints constraints, boolean isIdle) {
+        return (!constraints.isRequireDeviceIdle() || isIdle)
+                && (!constraints.isRequireAppNotForeground()
+                || !mAppStateHelper.hasForegroundApp(packageNames))
+                && (!constraints.isRequireAppNotInteracting()
+                || !mAppStateHelper.hasInteractingApp(packageNames))
+                && (!constraints.isRequireAppNotTopVisible()
+                || !mAppStateHelper.hasTopVisibleApp(packageNames))
+                && (!constraints.isRequireNotInCall()
+                || !mAppStateHelper.isInCall());
+    }
+
+    @WorkerThread
+    private boolean processPendingCheck(
+            PendingInstallConstraintsCheck pendingCheck, boolean isIdle) {
         var future = pendingCheck.future;
         if (future.isDone()) {
-            return;
+            return true;
         }
         var constraints = pendingCheck.constraints;
         var packageNames = mAppStateHelper.getDependencyPackages(pendingCheck.packageNames);
-        var constraintsSatisfied = (!constraints.isRequireDeviceIdle() || isIdle)
-                && (!constraints.isRequireAppNotForeground()
-                        || !mAppStateHelper.hasForegroundApp(packageNames))
-                && (!constraints.isRequireAppNotInteracting()
-                        || !mAppStateHelper.hasInteractingApp(packageNames))
-                && (!constraints.isRequireAppNotTopVisible()
-                        || !mAppStateHelper.hasTopVisibleApp(packageNames))
-                && (!constraints.isRequireNotInCall()
-                        || !mAppStateHelper.isInCall());
-        future.complete(new InstallConstraintsResult((constraintsSatisfied)));
+        var satisfied = areConstraintsSatisfied(packageNames, constraints, isIdle);
+        if (satisfied || pendingCheck.isTimedOut()) {
+            future.complete(new InstallConstraintsResult((satisfied)));
+            return true;
+        }
+        return false;
     }
 
     @WorkerThread
     private void processPendingChecksInIdle() {
-        while (!mPendingChecks.isEmpty()) {
-            processPendingCheck(mPendingChecks.remove(), true);
+        int size = mPendingChecks.size();
+        for (int i = 0; i < size; ++i) {
+            var pendingCheck = mPendingChecks.remove();
+            if (!processPendingCheck(pendingCheck, true)) {
+                // Not resolved. Put it back in the queue.
+                mPendingChecks.add(pendingCheck);
+            }
+        }
+        if (!mPendingChecks.isEmpty()) {
+            // Schedule a job for remaining pending checks
+            scheduleIdleJob();
+        }
+    }
+
+    @WorkerThread
+    private void onUidImportance(String packageName,
+            @RunningAppProcessInfo.Importance int importance) {
+        int size = mPendingChecks.size();
+        for (int i = 0; i < size; ++i) {
+            var pendingCheck = mPendingChecks.remove();
+            var dependencyPackages =
+                    mAppStateHelper.getDependencyPackages(pendingCheck.packageNames);
+            if (!dependencyPackages.contains(packageName)
+                    || !processPendingCheck(pendingCheck, false)) {
+                mPendingChecks.add(pendingCheck);
+            }
+        }
+        if (!mPendingChecks.isEmpty()) {
+            // Schedule a job for remaining pending checks
+            scheduleIdleJob();
+        }
+    }
+
+    private void onUidImportance(int uid, @RunningAppProcessInfo.Importance int importance) {
+        var pm = ActivityThread.getPackageManager();
+        try {
+            var packageName = pm.getNameForUid(uid);
+            mHandler.post(() -> onUidImportance(packageName, importance));
+        } catch (RemoteException ignore) {
         }
     }
 }
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index 4803c5e..9c60795 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -45,6 +45,7 @@
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageInstaller;
 import android.content.pm.PackageInstaller.InstallConstraints;
+import android.content.pm.PackageInstaller.InstallConstraintsResult;
 import android.content.pm.PackageInstaller.SessionInfo;
 import android.content.pm.PackageInstaller.SessionParams;
 import android.content.pm.PackageItemInfo;
@@ -91,7 +92,6 @@
 import com.android.internal.notification.SystemNotificationChannels;
 import com.android.internal.util.ImageUtils;
 import com.android.internal.util.IndentingPrintWriter;
-import com.android.internal.util.Preconditions;
 import com.android.modules.utils.TypedXmlPullParser;
 import com.android.modules.utils.TypedXmlSerializer;
 import com.android.server.IoThread;
@@ -123,6 +123,7 @@
 import java.util.Random;
 import java.util.TreeMap;
 import java.util.TreeSet;
+import java.util.concurrent.CompletableFuture;
 import java.util.function.IntPredicate;
 import java.util.function.Supplier;
 
@@ -153,6 +154,8 @@
     private static final long MAX_HISTORICAL_SESSIONS = 1048576;
     /** Destroy sessions older than this on storage free request */
     private static final long MAX_SESSION_AGE_ON_LOW_STORAGE_MILLIS = 8 * DateUtils.HOUR_IN_MILLIS;
+    /** Maximum time to wait for install constraints to be satisfied */
+    private static final long MAX_INSTALL_CONSTRAINTS_TIMEOUT_MILLIS = DateUtils.WEEK_IN_MILLIS;
 
     /** Threshold of historical sessions size */
     private static final int HISTORICAL_SESSIONS_THRESHOLD = 500;
@@ -300,6 +303,7 @@
     public void systemReady() {
         mAppOps = mContext.getSystemService(AppOpsManager.class);
         mStagingManager.systemReady();
+        mGentleUpdateHelper.systemReady();
 
         synchronized (mSessions) {
             readSessionsLocked();
@@ -1248,12 +1252,11 @@
         }
     }
 
-    @Override
-    public void checkInstallConstraints(String installerPackageName, List<String> packageNames,
-            InstallConstraints constraints, RemoteCallback callback) {
-        Preconditions.checkArgument(packageNames != null);
-        Preconditions.checkArgument(constraints != null);
-        Preconditions.checkArgument(callback != null);
+    private CompletableFuture<InstallConstraintsResult> checkInstallConstraintsInternal(
+            String installerPackageName, List<String> packageNames,
+            InstallConstraints constraints, long timeoutMillis) {
+        Objects.requireNonNull(packageNames);
+        Objects.requireNonNull(constraints);
 
         final var snapshot = mPm.snapshotComputer();
         final int callingUid = Binder.getCallingUid();
@@ -1267,7 +1270,16 @@
             }
         }
 
-        var future = mGentleUpdateHelper.checkInstallConstraints(packageNames, constraints);
+        return mGentleUpdateHelper.checkInstallConstraints(
+                packageNames, constraints, timeoutMillis);
+    }
+
+    @Override
+    public void checkInstallConstraints(String installerPackageName, List<String> packageNames,
+            InstallConstraints constraints, RemoteCallback callback) {
+        Objects.requireNonNull(callback);
+        var future = checkInstallConstraintsInternal(
+                installerPackageName, packageNames, constraints, /*timeoutMillis=*/0);
         future.thenAccept(result -> {
             var b = new Bundle();
             b.putParcelable("result", result);
@@ -1276,6 +1288,27 @@
     }
 
     @Override
+    public void waitForInstallConstraints(String installerPackageName, List<String> packageNames,
+            InstallConstraints constraints, IntentSender callback, long timeoutMillis) {
+        Objects.requireNonNull(callback);
+        if (timeoutMillis < 0 || timeoutMillis > MAX_INSTALL_CONSTRAINTS_TIMEOUT_MILLIS) {
+            throw new IllegalArgumentException("Invalid timeoutMillis=" + timeoutMillis);
+        }
+        var future = checkInstallConstraintsInternal(
+                installerPackageName, packageNames, constraints, timeoutMillis);
+        future.thenAccept(result -> {
+            final var intent = new Intent();
+            intent.putExtra(Intent.EXTRA_PACKAGES, packageNames.toArray(new String[0]));
+            intent.putExtra(PackageInstaller.EXTRA_INSTALL_CONSTRAINTS, constraints);
+            intent.putExtra(PackageInstaller.EXTRA_INSTALL_CONSTRAINTS_RESULT, result);
+            try {
+                callback.sendIntent(mContext, 0, intent, null, null);
+            } catch (SendIntentException ignore) {
+            }
+        });
+    }
+
+    @Override
     public void registerCallback(IPackageInstallerCallback callback, int userId) {
         final Computer snapshot = mPm.snapshotComputer();
         snapshot.enforceCrossUserPermission(Binder.getCallingUid(), userId, true, false,
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 3983acf..9aaf685 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -125,7 +125,7 @@
 import android.os.incremental.PerUidReadTimeouts;
 import android.os.incremental.StorageHealthCheckParams;
 import android.os.storage.StorageManager;
-import android.provider.Settings.Secure;
+import android.provider.Settings.Global;
 import android.stats.devicepolicy.DevicePolicyEnums;
 import android.system.ErrnoException;
 import android.system.Int64Ref;
@@ -1842,7 +1842,8 @@
             assertNoWriteFileTransfersOpenLocked();
 
             final boolean isSecureFrpEnabled =
-                    (Secure.getInt(mContext.getContentResolver(), Secure.SECURE_FRP_MODE, 0) == 1);
+                    Global.getInt(mContext.getContentResolver(), Global.SECURE_FRP_MODE, 0) == 1;
+
             if (isSecureFrpEnabled
                     && !isSecureFrpInstallAllowed(mContext, Binder.getCallingUid())) {
                 throw new SecurityException("Can't install packages while in secure FRP");
@@ -2785,6 +2786,7 @@
         // Default to require only if existing base apk has fs-verity signature.
         mVerityFoundForApks = PackageManagerServiceUtils.isApkVerityEnabled()
                 && params.mode == SessionParams.MODE_INHERIT_EXISTING
+                && VerityUtils.hasFsverity(pkgInfo.applicationInfo.getBaseCodePath())
                 && (new File(VerityUtils.getFsveritySignatureFilePath(
                         pkgInfo.applicationInfo.getBaseCodePath()))).exists();
 
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index e6e2f79..f38f000 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -3266,6 +3266,7 @@
         return isPackageDeviceAdmin(packageName, UserHandle.USER_ALL);
     }
 
+    // TODO(b/261957226): centralise this logic in DPM
     boolean isPackageDeviceAdmin(String packageName, int userId) {
         final IDevicePolicyManager dpm = getDevicePolicyManager();
         try {
@@ -3292,6 +3293,9 @@
                     if (dpm.packageHasActiveAdmins(packageName, users[i])) {
                         return true;
                     }
+                    if (isDeviceManagementRoleHolder(packageName, users[i])) {
+                        return true;
+                    }
                 }
             }
         } catch (RemoteException e) {
@@ -3299,6 +3303,24 @@
         return false;
     }
 
+    private boolean isDeviceManagementRoleHolder(String packageName, int userId) {
+        return Objects.equals(packageName, getDevicePolicyManagementRoleHolderPackageName(userId));
+    }
+
+    @Nullable
+    private String getDevicePolicyManagementRoleHolderPackageName(int userId) {
+        return Binder.withCleanCallingIdentity(() -> {
+            RoleManager roleManager = mContext.getSystemService(RoleManager.class);
+            List<String> roleHolders =
+                    roleManager.getRoleHoldersAsUser(
+                            RoleManager.ROLE_DEVICE_POLICY_MANAGEMENT, UserHandle.of(userId));
+            if (roleHolders.isEmpty()) {
+                return null;
+            }
+            return roleHolders.get(0);
+        });
+    }
+
     /** Returns the device policy manager interface. */
     private IDevicePolicyManager getDevicePolicyManager() {
         if (mDevicePolicyManager == null) {
diff --git a/services/core/java/com/android/server/power/PowerGroup.java b/services/core/java/com/android/server/power/PowerGroup.java
index 1c4e143..522c6c8 100644
--- a/services/core/java/com/android/server/power/PowerGroup.java
+++ b/services/core/java/com/android/server/power/PowerGroup.java
@@ -407,16 +407,14 @@
         return mDisplayPowerRequest.policy;
     }
 
-    boolean updateLocked(float screenBrightnessOverride, boolean autoBrightness,
-            boolean useProximitySensor, boolean boostScreenBrightness, int dozeScreenState,
-            float dozeScreenBrightness, boolean overrideDrawWakeLock,
-            PowerSaveState powerSaverState, boolean quiescent, boolean dozeAfterScreenOff,
-            boolean bootCompleted, boolean screenBrightnessBoostInProgress,
-            boolean waitForNegativeProximity) {
+    boolean updateLocked(float screenBrightnessOverride, boolean useProximitySensor,
+            boolean boostScreenBrightness, int dozeScreenState, float dozeScreenBrightness,
+            boolean overrideDrawWakeLock, PowerSaveState powerSaverState, boolean quiescent,
+            boolean dozeAfterScreenOff, boolean bootCompleted,
+            boolean screenBrightnessBoostInProgress, boolean waitForNegativeProximity) {
         mDisplayPowerRequest.policy = getDesiredScreenPolicyLocked(quiescent, dozeAfterScreenOff,
                 bootCompleted, screenBrightnessBoostInProgress);
         mDisplayPowerRequest.screenBrightnessOverride = screenBrightnessOverride;
-        mDisplayPowerRequest.useAutoBrightness = autoBrightness;
         mDisplayPowerRequest.useProximitySensor = useProximitySensor;
         mDisplayPowerRequest.boostScreenBrightness = boostScreenBrightness;
 
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index 6e3c827..b5ddc06 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -581,10 +581,6 @@
     private boolean mIsFaceDown = false;
     private long mLastFlipTime = 0L;
 
-    // The screen brightness mode.
-    // One of the Settings.System.SCREEN_BRIGHTNESS_MODE_* constants.
-    private int mScreenBrightnessModeSetting;
-
     // The screen brightness setting override from the window manager
     // to allow the current foreground activity to override the brightness.
     private float mScreenBrightnessOverrideFromWindowManager =
@@ -1457,10 +1453,6 @@
             mSystemProperties.set(SYSTEM_PROPERTY_RETAIL_DEMO_ENABLED, retailDemoValue);
         }
 
-        mScreenBrightnessModeSetting = Settings.System.getIntForUser(resolver,
-                Settings.System.SCREEN_BRIGHTNESS_MODE,
-                Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL, UserHandle.USER_CURRENT);
-
         mDirty |= DIRTY_SETTINGS;
     }
 
@@ -3432,23 +3424,18 @@
                 final PowerGroup powerGroup = mPowerGroups.valueAt(idx);
                 final int groupId = powerGroup.getGroupId();
 
-                // Determine appropriate screen brightness and auto-brightness adjustments.
-                final boolean autoBrightness;
+                // Determine appropriate screen brightness.
                 final float screenBrightnessOverride;
                 if (!mBootCompleted) {
                     // Keep the brightness steady during boot. This requires the
                     // bootloader brightness and the default brightness to be identical.
-                    autoBrightness = false;
                     screenBrightnessOverride = mScreenBrightnessDefault;
                 } else if (isValidBrightness(mScreenBrightnessOverrideFromWindowManager)) {
-                    autoBrightness = false;
                     screenBrightnessOverride = mScreenBrightnessOverrideFromWindowManager;
                 } else {
-                    autoBrightness = (mScreenBrightnessModeSetting
-                            == Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
                     screenBrightnessOverride = PowerManager.BRIGHTNESS_INVALID_FLOAT;
                 }
-                boolean ready = powerGroup.updateLocked(screenBrightnessOverride, autoBrightness,
+                boolean ready = powerGroup.updateLocked(screenBrightnessOverride,
                         shouldUseProximitySensorLocked(), shouldBoostScreenBrightness(),
                         mDozeScreenStateOverrideFromDreamManager,
                         mDozeScreenBrightnessOverrideFromDreamManagerFloat,
@@ -3469,7 +3456,6 @@
                             powerGroup.getUserActivitySummaryLocked())
                             + ", mBootCompleted=" + mBootCompleted
                             + ", screenBrightnessOverride=" + screenBrightnessOverride
-                            + ", useAutoBrightness=" + autoBrightness
                             + ", mScreenBrightnessBoostInProgress="
                             + mScreenBrightnessBoostInProgress
                             + ", sQuiescent=" + sQuiescent);
@@ -4488,7 +4474,6 @@
                     + mMaximumScreenOffTimeoutFromDeviceAdmin + " (enforced="
                     + isMaximumScreenOffTimeoutFromDeviceAdminEnforcedLocked() + ")");
             pw.println("  mStayOnWhilePluggedInSetting=" + mStayOnWhilePluggedInSetting);
-            pw.println("  mScreenBrightnessModeSetting=" + mScreenBrightnessModeSetting);
             pw.println("  mScreenBrightnessOverrideFromWindowManager="
                     + mScreenBrightnessOverrideFromWindowManager);
             pw.println("  mUserActivityTimeoutOverrideFromWindowManager="
@@ -4866,9 +4851,6 @@
             proto.end(stayOnWhilePluggedInToken);
 
             proto.write(
-                    PowerServiceSettingsAndConfigurationDumpProto.SCREEN_BRIGHTNESS_MODE_SETTING,
-                    mScreenBrightnessModeSetting);
-            proto.write(
                     PowerServiceSettingsAndConfigurationDumpProto
                             .SCREEN_BRIGHTNESS_OVERRIDE_FROM_WINDOW_MANAGER,
                     mScreenBrightnessOverrideFromWindowManager);
diff --git a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
index 2a88473..855fcaf 100644
--- a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
+++ b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
@@ -763,8 +763,6 @@
     @NonNull
     private final BatteryStatsHistory mHistory;
 
-    private BatteryStatsHistoryIterator mBatteryStatsHistoryIterator;
-
     int mStartCount;
 
     /**
@@ -11242,17 +11240,11 @@
         return mHistory.getHistoryUsedSize();
     }
 
-    @Override
-    public boolean startIteratingHistoryLocked() {
-        mBatteryStatsHistoryIterator = createBatteryStatsHistoryIterator();
-        return true;
-    }
-
     /**
      * Creates an iterator for battery stats history.
      */
-    @VisibleForTesting
-    public BatteryStatsHistoryIterator createBatteryStatsHistoryIterator() {
+    @Override
+    public BatteryStatsHistoryIterator iterateBatteryStatsHistory() {
         return mHistory.iterate();
     }
 
@@ -11277,17 +11269,6 @@
     }
 
     @Override
-    public boolean getNextHistoryLocked(HistoryItem out) {
-        return mBatteryStatsHistoryIterator.next(out);
-    }
-
-    @Override
-    public void finishIteratingHistoryLocked() {
-        mBatteryStatsHistoryIterator.close();
-        mBatteryStatsHistoryIterator = null;
-    }
-
-    @Override
     public int getStartCount() {
         return mStartCount;
     }
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 4480d52..37450ac 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -603,6 +603,13 @@
      * for display.
      */
     void generateCrop(WallpaperData wallpaper) {
+        TimingsTraceAndSlog t = new TimingsTraceAndSlog(TAG);
+        t.traceBegin("WPMS.generateCrop");
+        generateCropInternal(wallpaper);
+        t.traceEnd();
+    }
+
+    private void generateCropInternal(WallpaperData wallpaper) {
         boolean success = false;
 
         // Only generate crop for default display.
diff --git a/services/core/java/com/android/server/wm/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java
index af430f9..b70c8b0 100644
--- a/services/core/java/com/android/server/wm/ActivityClientController.java
+++ b/services/core/java/com/android/server/wm/ActivityClientController.java
@@ -17,7 +17,15 @@
 package com.android.server.wm;
 
 import static android.Manifest.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS;
+import static android.app.Activity.FULLSCREEN_MODE_REQUEST_ENTER;
 import static android.app.ActivityTaskManager.INVALID_TASK_ID;
+import static android.app.ActivityTaskManager.INVALID_WINDOWING_MODE;
+import static android.app.FullscreenRequestHandler.REMOTE_CALLBACK_RESULT_KEY;
+import static android.app.FullscreenRequestHandler.RESULT_APPROVED;
+import static android.app.FullscreenRequestHandler.RESULT_FAILED_NOT_DEFAULT_FREEFORM;
+import static android.app.FullscreenRequestHandler.RESULT_FAILED_NOT_IN_FREEFORM;
+import static android.app.FullscreenRequestHandler.RESULT_FAILED_NOT_IN_FULLSCREEN_WITH_HISTORY;
+import static android.app.FullscreenRequestHandler.RESULT_FAILED_NOT_TOP_FOCUSED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
@@ -27,6 +35,7 @@
 import static android.service.voice.VoiceInteractionSession.SHOW_SOURCE_APPLICATION;
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.Display.INVALID_DISPLAY;
+import static android.view.WindowManager.TRANSIT_CHANGE;
 import static android.view.WindowManager.TRANSIT_TO_BACK;
 import static android.view.WindowManager.TRANSIT_TO_FRONT;
 
@@ -52,6 +61,7 @@
 import android.app.Activity;
 import android.app.ActivityManager;
 import android.app.ActivityTaskManager;
+import android.app.FullscreenRequestHandler;
 import android.app.IActivityClientController;
 import android.app.ICompatCameraControlCallback;
 import android.app.IRequestFinishCallback;
@@ -71,6 +81,7 @@
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.IBinder;
+import android.os.IRemoteCallback;
 import android.os.Parcel;
 import android.os.PersistableBundle;
 import android.os.RemoteException;
@@ -85,6 +96,7 @@
 
 import com.android.internal.app.AssistUtils;
 import com.android.internal.policy.IKeyguardDismissCallback;
+import com.android.internal.protolog.ProtoLogGroup;
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.internal.util.FrameworkStatsLog;
 import com.android.server.LocalServices;
@@ -1056,6 +1068,133 @@
         }
     }
 
+    private @FullscreenRequestHandler.RequestResult int validateMultiwindowFullscreenRequestLocked(
+            Task topFocusedRootTask, int fullscreenRequest, ActivityRecord requesterActivity) {
+        // If the mode is not by default freeform, the freeform will be a user-driven event.
+        if (topFocusedRootTask.getParent().getWindowingMode() != WINDOWING_MODE_FREEFORM) {
+            return RESULT_FAILED_NOT_DEFAULT_FREEFORM;
+        }
+        // If this is not coming from the currently top-most activity, reject the request.
+        if (requesterActivity != topFocusedRootTask.getTopMostActivity()) {
+            return RESULT_FAILED_NOT_TOP_FOCUSED;
+        }
+        if (fullscreenRequest == FULLSCREEN_MODE_REQUEST_ENTER) {
+            if (topFocusedRootTask.getWindowingMode() != WINDOWING_MODE_FREEFORM) {
+                return RESULT_FAILED_NOT_IN_FREEFORM;
+            }
+        } else {
+            if (topFocusedRootTask.getWindowingMode() != WINDOWING_MODE_FULLSCREEN) {
+                return RESULT_FAILED_NOT_IN_FULLSCREEN_WITH_HISTORY;
+            }
+            if (topFocusedRootTask.mMultiWindowRestoreWindowingMode == INVALID_WINDOWING_MODE) {
+                return RESULT_FAILED_NOT_IN_FULLSCREEN_WITH_HISTORY;
+            }
+        }
+        return RESULT_APPROVED;
+    }
+
+    @Override
+    public void requestMultiwindowFullscreen(IBinder callingActivity, int fullscreenRequest,
+            IRemoteCallback callback) {
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            synchronized (mGlobalLock) {
+                requestMultiwindowFullscreenLocked(callingActivity, fullscreenRequest, callback);
+            }
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
+    private void requestMultiwindowFullscreenLocked(IBinder callingActivity, int fullscreenRequest,
+            IRemoteCallback callback) {
+        final ActivityRecord r = ActivityRecord.forTokenLocked(callingActivity);
+        if (r == null) {
+            return;
+        }
+
+        // If the shell transition is not enabled, just execute and done.
+        final TransitionController controller = r.mTransitionController;
+        if (!controller.isShellTransitionsEnabled()) {
+            final @FullscreenRequestHandler.RequestResult int validateResult;
+            final Task topFocusedRootTask;
+            topFocusedRootTask = mService.getTopDisplayFocusedRootTask();
+            validateResult = validateMultiwindowFullscreenRequestLocked(topFocusedRootTask,
+                    fullscreenRequest, r);
+            reportMultiwindowFullscreenRequestValidatingResult(callback, validateResult);
+            if (validateResult == RESULT_APPROVED) {
+                executeMultiWindowFullscreenRequest(fullscreenRequest, topFocusedRootTask);
+            }
+            return;
+        }
+        // Initiate the transition.
+        final Transition transition = new Transition(TRANSIT_CHANGE, 0 /* flags */, controller,
+                mService.mWindowManager.mSyncEngine);
+        if (mService.mWindowManager.mSyncEngine.hasActiveSync()) {
+            ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
+                    "Creating Pending Multiwindow Fullscreen Request: %s", transition);
+            mService.mWindowManager.mSyncEngine.queueSyncSet(
+                    () -> r.mTransitionController.moveToCollecting(transition),
+                    () -> {
+                        executeFullscreenRequestTransition(fullscreenRequest, callback, r,
+                                transition, true /* queued */);
+                    });
+        } else {
+            executeFullscreenRequestTransition(fullscreenRequest, callback, r, transition,
+                    false /* queued */);
+        }
+    }
+
+    private void executeFullscreenRequestTransition(int fullscreenRequest, IRemoteCallback callback,
+            ActivityRecord r, Transition transition, boolean queued) {
+        final @FullscreenRequestHandler.RequestResult int validateResult;
+        final Task topFocusedRootTask;
+        topFocusedRootTask = mService.getTopDisplayFocusedRootTask();
+        validateResult = validateMultiwindowFullscreenRequestLocked(topFocusedRootTask,
+                fullscreenRequest, r);
+        reportMultiwindowFullscreenRequestValidatingResult(callback, validateResult);
+        if (validateResult != RESULT_APPROVED) {
+            if (queued) {
+                transition.abort();
+            }
+            return;
+        }
+        r.mTransitionController.requestStartTransition(transition, topFocusedRootTask,
+                null /* remoteTransition */, null /* displayChange */);
+        executeMultiWindowFullscreenRequest(fullscreenRequest, topFocusedRootTask);
+        transition.setReady(topFocusedRootTask, true);
+    }
+
+    private static void reportMultiwindowFullscreenRequestValidatingResult(IRemoteCallback callback,
+            @FullscreenRequestHandler.RequestResult int result) {
+        if (callback == null) {
+            return;
+        }
+        Bundle res = new Bundle();
+        res.putInt(REMOTE_CALLBACK_RESULT_KEY, result);
+        try {
+            callback.sendResult(res);
+        } catch (RemoteException e) {
+            Slog.w(TAG, "client throws an exception back to the server, ignore it");
+        }
+    }
+
+    private static void executeMultiWindowFullscreenRequest(int fullscreenRequest, Task requester) {
+        final int targetWindowingMode;
+        if (fullscreenRequest == FULLSCREEN_MODE_REQUEST_ENTER) {
+            requester.mMultiWindowRestoreWindowingMode =
+                    requester.getRequestedOverrideWindowingMode();
+            targetWindowingMode = WINDOWING_MODE_FULLSCREEN;
+        } else {
+            targetWindowingMode = requester.mMultiWindowRestoreWindowingMode;
+            requester.mMultiWindowRestoreWindowingMode = INVALID_WINDOWING_MODE;
+        }
+        requester.setWindowingMode(targetWindowingMode);
+        if (targetWindowingMode == WINDOWING_MODE_FULLSCREEN) {
+            requester.setBounds(null);
+        }
+    }
+
     @Override
     public void startLockTaskModeByToken(IBinder token) {
         synchronized (mGlobalLock) {
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index f725d1a..0307b79 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -670,7 +670,7 @@
     private boolean mCurrentLaunchCanTurnScreenOn = true;
 
     /** Whether our surface was set to be showing in the last call to {@link #prepareSurfaces} */
-    private boolean mLastSurfaceShowing = true;
+    private boolean mLastSurfaceShowing;
 
     /**
      * The activity is opaque and fills the entire space of this task.
@@ -1233,8 +1233,8 @@
                 pw.println(prefix + "supportsEnterPipOnTaskSwitch: "
                         + supportsEnterPipOnTaskSwitch);
             }
-            if (info.getMaxAspectRatio() != 0) {
-                pw.println(prefix + "maxAspectRatio=" + info.getMaxAspectRatio());
+            if (getMaxAspectRatio() != 0) {
+                pw.println(prefix + "maxAspectRatio=" + getMaxAspectRatio());
             }
             final float minAspectRatio = getMinAspectRatio();
             if (minAspectRatio != 0) {
@@ -1574,6 +1574,7 @@
                 newParent.setResumedActivity(this, "onParentChanged");
                 mImeInsetsFrozenUntilStartInput = false;
             }
+            mLetterboxUiController.onActivityParentChanged(newParent);
         }
 
         if (rootTask != null && rootTask.topRunningActivity() == this) {
@@ -5470,7 +5471,8 @@
         // no animation but there will still be a transition set.
         // We still need to delay hiding the surface such that it
         // can be synchronized with showing the next surface in the transition.
-        if (!isVisible() && !delayed && !displayContent.mAppTransition.isTransitionSet()) {
+        if (!usingShellTransitions && !isVisible() && !delayed
+                && !displayContent.mAppTransition.isTransitionSet()) {
             SurfaceControl.openTransaction();
             try {
                 forAllWindows(win -> {
@@ -7400,6 +7402,11 @@
     }
 
     @Override
+    boolean showSurfaceOnCreation() {
+        return false;
+    }
+
+    @Override
     void prepareSurfaces() {
         final boolean show = isVisible() || isAnimating(PARENTS,
                 ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_RECENTS);
@@ -7638,6 +7645,15 @@
     @Configuration.Orientation
     @Override
     int getRequestedConfigurationOrientation(boolean forDisplay) {
+        if (mLetterboxUiController.hasInheritedOrientation()) {
+            final RootDisplayArea root = getRootDisplayArea();
+            if (forDisplay && root != null && root.isOrientationDifferentFromDisplay()) {
+                return ActivityInfo.reverseOrientation(
+                        mLetterboxUiController.getInheritedOrientation());
+            } else {
+                return mLetterboxUiController.getInheritedOrientation();
+            }
+        }
         if (mOrientation == SCREEN_ORIENTATION_BEHIND && task != null) {
             // We use Task here because we want to be consistent with what happens in
             // multi-window mode where other tasks orientations are ignored.
@@ -7765,6 +7781,9 @@
 
     @Nullable
     CompatDisplayInsets getCompatDisplayInsets() {
+        if (mLetterboxUiController.hasInheritedLetterboxBehavior()) {
+            return mLetterboxUiController.getInheritedCompatDisplayInsets();
+        }
         return mCompatDisplayInsets;
     }
 
@@ -7847,6 +7866,10 @@
 
     // 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()) {
             // The override configuration is set only once in size compatibility mode.
             return;
@@ -7908,6 +7931,9 @@
 
     @Override
     float getCompatScale() {
+        if (mLetterboxUiController.hasInheritedLetterboxBehavior()) {
+            return mLetterboxUiController.getInheritedSizeCompatScale();
+        }
         return hasSizeCompatBounds() ? mSizeCompatScale : super.getCompatScale();
     }
 
@@ -8017,6 +8043,16 @@
     }
 
     /**
+     * @return The orientation to use to understand if reachability is enabled.
+     */
+    @ActivityInfo.ScreenOrientation
+    int getOrientationForReachability() {
+        return mLetterboxUiController.hasInheritedLetterboxBehavior()
+                ? mLetterboxUiController.getInheritedOrientation()
+                : getRequestedConfigurationOrientation();
+    }
+
+    /**
      * Returns whether activity bounds are letterboxed.
      *
      * <p>Note that letterbox UI may not be shown even when this returns {@code true}. See {@link
@@ -8056,6 +8092,10 @@
         if (!ignoreVisibility && !mVisibleRequested) {
             return APP_COMPAT_STATE_CHANGED__STATE__NOT_VISIBLE;
         }
+        // TODO(b/256564921): Investigate if we need new metrics for translucent activities
+        if (mLetterboxUiController.hasInheritedLetterboxBehavior()) {
+            return mLetterboxUiController.getInheritedAppCompatState();
+        }
         if (mInSizeCompatModeForBounds) {
             return APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_SIZE_COMPAT_MODE;
         }
@@ -8526,6 +8566,11 @@
     }
 
     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
+            // is letterboxed.
+            return false;
+        }
         final int appWidth = appBounds.width();
         final int appHeight = appBounds.height();
         final int containerAppWidth = containerBounds.width();
@@ -8546,10 +8591,11 @@
 
         // The rest of the condition is that only one side is smaller than the container, but it
         // still needs to exclude the cases where the size is limited by the fixed aspect ratio.
-        if (info.getMaxAspectRatio() > 0) {
+        final float maxAspectRatio = getMaxAspectRatio();
+        if (maxAspectRatio > 0) {
             final float aspectRatio = (0.5f + Math.max(appWidth, appHeight))
                     / Math.min(appWidth, appHeight);
-            if (aspectRatio >= info.getMaxAspectRatio()) {
+            if (aspectRatio >= maxAspectRatio) {
                 // The current size has reached the max aspect ratio.
                 return false;
             }
@@ -8771,7 +8817,7 @@
     // TODO(b/36505427): Consider moving this method and similar ones to ConfigurationContainer.
     private boolean applyAspectRatio(Rect outBounds, Rect containingAppBounds,
             Rect containingBounds, float desiredAspectRatio) {
-        final float maxAspectRatio = info.getMaxAspectRatio();
+        final float maxAspectRatio = getMaxAspectRatio();
         final Task rootTask = getRootTask();
         final float minAspectRatio = getMinAspectRatio();
         final TaskFragment organizedTf = getOrganizedTaskFragment();
@@ -8878,6 +8924,9 @@
      * Returns the min aspect ratio of this activity.
      */
     float getMinAspectRatio() {
+        if (mLetterboxUiController.hasInheritedLetterboxBehavior()) {
+            return mLetterboxUiController.getInheritedMinAspectRatio();
+        }
         if (info.applicationInfo == null) {
             return info.getMinAspectRatio();
         }
@@ -8922,11 +8971,18 @@
                 && parent.getWindowConfiguration().getWindowingMode() == WINDOWING_MODE_FULLSCREEN;
     }
 
+    float getMaxAspectRatio() {
+        if (mLetterboxUiController.hasInheritedLetterboxBehavior()) {
+            return mLetterboxUiController.getInheritedMaxAspectRatio();
+        }
+        return info.getMaxAspectRatio();
+    }
+
     /**
      * Returns true if the activity has maximum or minimum aspect ratio.
      */
     private boolean hasFixedAspectRatio() {
-        return info.getMaxAspectRatio() != 0 || getMinAspectRatio() != 0;
+        return getMaxAspectRatio() != 0 || getMinAspectRatio() != 0;
     }
 
     /**
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 2f4d154..2762c45 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -1823,7 +1823,7 @@
         if (mPreferredTaskDisplayArea != null) {
             final DisplayContent displayContent = mRootWindowContainer.getDisplayContentOrCreate(
                     mPreferredTaskDisplayArea.getDisplayId());
-            if (displayContent != null && displayContent.mDwpcHelper.hasController()) {
+            if (displayContent != null) {
                 final int targetWindowingMode = (targetTask != null)
                         ? targetTask.getWindowingMode() : displayContent.getWindowingMode();
                 final int launchingFromDisplayId =
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 866fef7..eb04687 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -3616,6 +3616,19 @@
     }
 
     @Override
+    public void setSplitScreenResizing(boolean resizing) {
+        enforceTaskPermission("setSplitScreenResizing()");
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            synchronized (mGlobalLock) {
+                mTaskSupervisor.setSplitScreenResizing(resizing);
+            }
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
+    @Override
     public IWindowOrganizerController getWindowOrganizerController() {
         return mWindowOrganizerController;
     }
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index 034089b..33c90a0 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -207,6 +207,9 @@
     // Used to indicate that a task is removed it should also be removed from recents.
     static final boolean REMOVE_FROM_RECENTS = true;
 
+    /** True if the docked root task is currently being resized. */
+    private boolean mDockedRootTaskResizing;
+
     // Activity actions an app cannot start if it uses a permission which is not granted.
     private static final ArrayMap<String, String> ACTION_TO_RUNTIME_PERMISSION =
             new ArrayMap<>();
@@ -392,7 +395,7 @@
 
         final DisplayContent displayContent =
                 mRootWindowContainer.getDisplayContentOrCreate(displayId);
-        if (displayContent != null && displayContent.mDwpcHelper.hasController()) {
+        if (displayContent != null) {
             final ArrayList<ActivityInfo> activities = new ArrayList<>();
             if (activityInfo != null) {
                 activities.add(activityInfo);
@@ -402,10 +405,8 @@
                     activities.add(r.info);
                 });
             }
-            if (!displayContent.mDwpcHelper.canContainActivities(activities,
-                    displayContent.getWindowingMode())) {
-                return false;
-            }
+            return displayContent.mDwpcHelper.canContainActivities(activities,
+                        displayContent.getWindowingMode());
         }
 
         return true;
@@ -1525,6 +1526,15 @@
         return mLaunchParamsController;
     }
 
+    void setSplitScreenResizing(boolean resizing) {
+        if (resizing == mDockedRootTaskResizing) {
+            return;
+        }
+
+        mDockedRootTaskResizing = resizing;
+        mWindowManager.setDockedRootTaskResizing(resizing);
+    }
+
     private void removePinnedRootTaskInSurfaceTransaction(Task rootTask) {
         /**
          * Workaround: Force-stop all the activities in the root pinned task before we reparent them
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 169e770..c6dc24f 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -525,6 +525,7 @@
     /** Remove this display when animation on it has completed. */
     private boolean mDeferredRemoval;
 
+    final DockedTaskDividerController mDividerControllerLocked;
     final PinnedTaskController mPinnedTaskController;
 
     final ArrayList<WindowState> mTapExcludedWindows = new ArrayList<>();
@@ -1162,6 +1163,7 @@
             mDisplayPolicy.systemReady();
         }
         mWindowCornerRadius = mDisplayPolicy.getWindowCornerRadius();
+        mDividerControllerLocked = new DockedTaskDividerController(this);
         mPinnedTaskController = new PinnedTaskController(mWmService, this);
 
         final Transaction pendingTransaction = getPendingTransaction();
@@ -2592,6 +2594,10 @@
         }
     }
 
+    DockedTaskDividerController getDockedDividerController() {
+        return mDividerControllerLocked;
+    }
+
     PinnedTaskController getPinnedTaskController() {
         return mPinnedTaskController;
     }
diff --git a/services/core/java/com/android/server/wm/DisplayWindowPolicyControllerHelper.java b/services/core/java/com/android/server/wm/DisplayWindowPolicyControllerHelper.java
index 69fd00c..72ed108 100644
--- a/services/core/java/com/android/server/wm/DisplayWindowPolicyControllerHelper.java
+++ b/services/core/java/com/android/server/wm/DisplayWindowPolicyControllerHelper.java
@@ -22,12 +22,14 @@
 import android.content.pm.ActivityInfo;
 import android.os.UserHandle;
 import android.util.ArraySet;
+import android.util.Slog;
 import android.window.DisplayWindowPolicyController;
 
 import java.io.PrintWriter;
 import java.util.List;
 
 class DisplayWindowPolicyControllerHelper {
+    private static final String TAG = "DisplayWindowPolicyControllerHelper";
 
     private final DisplayContent mDisplayContent;
 
@@ -69,6 +71,17 @@
     public boolean canContainActivities(@NonNull List<ActivityInfo> activities,
             @WindowConfiguration.WindowingMode int windowingMode) {
         if (mDisplayWindowPolicyController == null) {
+            for (int i = activities.size() - 1; i >= 0; i--) {
+                final ActivityInfo aInfo = activities.get(i);
+                if (aInfo.requiredDisplayCategory != null) {
+                    Slog.e(TAG,
+                            String.format("Activity with requiredDisplayCategory='%s' cannot be"
+                                            + " displayed on display %d because that display does"
+                                            + " not have a matching category",
+                                    aInfo.requiredDisplayCategory, mDisplayContent.mDisplayId));
+                    return false;
+                }
+            }
             return true;
         }
         return mDisplayWindowPolicyController.canContainActivities(activities, windowingMode);
@@ -81,6 +94,14 @@
             @WindowConfiguration.WindowingMode int windowingMode, int launchingFromDisplayId,
             boolean isNewTask) {
         if (mDisplayWindowPolicyController == null) {
+            if (activityInfo.requiredDisplayCategory != null) {
+                Slog.e(TAG,
+                        String.format("Activity with requiredDisplayCategory='%s' cannot be"
+                                + " launched on display %d because that display does"
+                                + " not have a matching category",
+                                activityInfo.requiredDisplayCategory, mDisplayContent.mDisplayId));
+                return false;
+            }
             return true;
         }
         return mDisplayWindowPolicyController.canActivityBeLaunched(activityInfo, windowingMode,
diff --git a/services/core/java/com/android/server/wm/DockedTaskDividerController.java b/services/core/java/com/android/server/wm/DockedTaskDividerController.java
new file mode 100644
index 0000000..925a6d8
--- /dev/null
+++ b/services/core/java/com/android/server/wm/DockedTaskDividerController.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import android.graphics.Rect;
+
+/**
+ * Keeps information about the docked task divider.
+ */
+public class DockedTaskDividerController {
+
+    private final DisplayContent mDisplayContent;
+    private boolean mResizing;
+
+    private final Rect mTouchRegion = new Rect();
+
+    DockedTaskDividerController(DisplayContent displayContent) {
+        mDisplayContent = displayContent;
+    }
+
+    boolean isResizing() {
+        return mResizing;
+    }
+
+    void setResizing(boolean resizing) {
+        if (mResizing != resizing) {
+            mResizing = resizing;
+            resetDragResizingChangeReported();
+        }
+    }
+
+    void setTouchRegion(Rect touchRegion) {
+        mTouchRegion.set(touchRegion);
+        // We need to report touchable region changes to accessibility.
+        if (mDisplayContent.mWmService.mAccessibilityController.hasCallbacks()) {
+            mDisplayContent.mWmService.mAccessibilityController.onSomeWindowResizedOrMoved(
+                    mDisplayContent.getDisplayId());
+        }
+    }
+
+    void getTouchRegion(Rect outRegion) {
+        outRegion.set(mTouchRegion);
+    }
+
+    private void resetDragResizingChangeReported() {
+        mDisplayContent.forAllWindows(WindowState::resetDragResizingChangeReported,
+                true /* traverseTopToBottom */);
+    }
+}
diff --git a/services/core/java/com/android/server/wm/DragResizeMode.java b/services/core/java/com/android/server/wm/DragResizeMode.java
new file mode 100644
index 0000000..684cf06
--- /dev/null
+++ b/services/core/java/com/android/server/wm/DragResizeMode.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.wm;
+
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+
+/**
+ * Describes the mode in which a window is drag resizing.
+ */
+class DragResizeMode {
+
+    /**
+     * Freeform mode: Client surface is fullscreen, and client is responsible to draw window at
+     * the correct position.
+     */
+    static final int DRAG_RESIZE_MODE_FREEFORM = 0;
+
+    /**
+     * Mode for resizing the docked (and adjacent) root task: Client surface is fullscreen, but
+     * window is drawn at (0, 0), window manager is responsible for positioning the surface when
+     * dragging.
+     */
+    static final int DRAG_RESIZE_MODE_DOCKED_DIVIDER = 1;
+
+    static boolean isModeAllowedForRootTask(Task rootTask, int mode) {
+        switch (mode) {
+            case DRAG_RESIZE_MODE_FREEFORM:
+                return rootTask.getWindowingMode() == WINDOWING_MODE_FREEFORM;
+            default:
+                return false;
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/wm/LetterboxConfiguration.java b/services/core/java/com/android/server/wm/LetterboxConfiguration.java
index c19353c..127a7bf 100644
--- a/services/core/java/com/android/server/wm/LetterboxConfiguration.java
+++ b/services/core/java/com/android/server/wm/LetterboxConfiguration.java
@@ -21,6 +21,7 @@
 import android.annotation.Nullable;
 import android.content.Context;
 import android.graphics.Color;
+import android.provider.DeviceConfig;
 
 import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
@@ -103,6 +104,10 @@
 
     final Context mContext;
 
+    // Responsible for the persistence of letterbox[Horizontal|Vertical]PositionMultiplier
+    @NonNull
+    private final LetterboxConfigurationPersister mLetterboxConfigurationPersister;
+
     // Aspect ratio of letterbox for fixed orientation, values <=
     // MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO will be ignored.
     private float mFixedOrientationLetterboxAspectRatio;
@@ -165,9 +170,12 @@
     // Whether using split screen aspect ratio as a default aspect ratio for unresizable apps.
     private boolean mIsSplitScreenAspectRatioForUnresizableAppsEnabled;
 
-    // Responsible for the persistence of letterbox[Horizontal|Vertical]PositionMultiplier
-    @NonNull
-    private final LetterboxConfigurationPersister mLetterboxConfigurationPersister;
+    // Whether letterboxing strategy is enabled for translucent activities. If {@value false}
+    // all the feature is disabled
+    private boolean mTranslucentLetterboxingEnabled;
+
+    // Allows to enable letterboxing strategy for translucent activities ignoring flags.
+    private boolean mTranslucentLetterboxingOverrideEnabled;
 
     LetterboxConfiguration(Context systemUiContext) {
         this(systemUiContext, new LetterboxConfigurationPersister(systemUiContext,
@@ -206,6 +214,8 @@
                 R.dimen.config_letterboxDefaultMinAspectRatioForUnresizableApps));
         mIsSplitScreenAspectRatioForUnresizableAppsEnabled = mContext.getResources().getBoolean(
                 R.bool.config_letterboxIsSplitScreenAspectRatioForUnresizableAppsEnabled);
+        mTranslucentLetterboxingEnabled = mContext.getResources().getBoolean(
+                R.bool.config_letterboxIsEnabledForTranslucentActivities);
         mLetterboxConfigurationPersister = letterboxConfigurationPersister;
         mLetterboxConfigurationPersister.start();
     }
@@ -817,6 +827,32 @@
                 R.bool.config_letterboxIsSplitScreenAspectRatioForUnresizableAppsEnabled);
     }
 
+    boolean isTranslucentLetterboxingEnabled() {
+        return mTranslucentLetterboxingOverrideEnabled || (mTranslucentLetterboxingEnabled
+                && isTranslucentLetterboxingAllowed());
+    }
+
+    void setTranslucentLetterboxingEnabled(boolean translucentLetterboxingEnabled) {
+        mTranslucentLetterboxingEnabled = translucentLetterboxingEnabled;
+    }
+
+    void setTranslucentLetterboxingOverrideEnabled(
+            boolean translucentLetterboxingOverrideEnabled) {
+        mTranslucentLetterboxingOverrideEnabled = translucentLetterboxingOverrideEnabled;
+        setTranslucentLetterboxingEnabled(translucentLetterboxingOverrideEnabled);
+    }
+
+    /**
+     * Resets whether we use the constraints override strategy for letterboxing when dealing
+     * with translucent activities {@link R.bool.config_letterboxIsEnabledForTranslucentActivities}.
+     */
+    void resetTranslucentLetterboxingEnabled() {
+        final boolean newValue = mContext.getResources().getBoolean(
+                R.bool.config_letterboxIsEnabledForTranslucentActivities);
+        setTranslucentLetterboxingEnabled(newValue);
+        setTranslucentLetterboxingOverrideEnabled(false);
+    }
+
     /** Calculates a new letterboxPositionForHorizontalReachability value and updates the store */
     private void updatePositionForHorizontalReachability(
             Function<Integer, Integer> newHorizonalPositionFun) {
@@ -839,4 +875,9 @@
                 nextVerticalPosition);
     }
 
+    // TODO(b/262378106): Cache runtime flag and implement DeviceConfig.OnPropertiesChangedListener
+    static boolean isTranslucentLetterboxingAllowed() {
+        return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_WINDOW_MANAGER,
+                "enable_translucent_activity_letterbox", false);
+    }
 }
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index bcea6f4..a53a5fc 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -17,6 +17,7 @@
 package com.android.server.wm;
 
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
 import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
 import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
@@ -27,6 +28,7 @@
 import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__RIGHT;
 import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__TOP;
 import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__UNKNOWN_POSITION;
+import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__STATE__UNKNOWN;
 import static com.android.internal.util.FrameworkStatsLog.LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__BOTTOM_TO_CENTER;
 import static com.android.internal.util.FrameworkStatsLog.LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__CENTER_TO_BOTTOM;
 import static com.android.internal.util.FrameworkStatsLog.LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__CENTER_TO_LEFT;
@@ -82,13 +84,44 @@
 
     private static final String TAG = TAG_WITH_CLASS_NAME ? "LetterboxUiController" : TAG_ATM;
 
+    private static final float UNDEFINED_ASPECT_RATIO = 0f;
+
     private final Point mTmpPoint = new Point();
 
     private final LetterboxConfiguration mLetterboxConfiguration;
+
     private final ActivityRecord mActivityRecord;
 
+    /*
+     * WindowContainerListener responsible to make translucent activities inherit
+     * constraints from the first opaque activity beneath them. It's null for not
+     * translucent activities.
+     */
+    @Nullable
+    private WindowContainerListener mLetterboxConfigListener;
+
     private boolean mShowWallpaperForLetterboxBackground;
 
+    // In case of transparent activities we might need to access the aspectRatio of the
+    // first opaque activity beneath.
+    private float mInheritedMinAspectRatio = UNDEFINED_ASPECT_RATIO;
+    private float mInheritedMaxAspectRatio = UNDEFINED_ASPECT_RATIO;
+
+    @Configuration.Orientation
+    private int mInheritedOrientation = Configuration.ORIENTATION_UNDEFINED;
+
+    // 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;
+
     @Nullable
     private Letterbox mLetterbox;
 
@@ -220,7 +253,9 @@
                     : mActivityRecord.inMultiWindowMode()
                             ? mActivityRecord.getTask().getBounds()
                             : mActivityRecord.getRootTask().getParent().getBounds();
-            mLetterbox.layout(spaceToFill, w.getFrame(), mTmpPoint);
+            final Rect innerFrame = hasInheritedLetterboxBehavior()
+                    ? mActivityRecord.getWindowConfiguration().getBounds() : w.getFrame();
+            mLetterbox.layout(spaceToFill, innerFrame, mTmpPoint);
         } else if (mLetterbox != null) {
             mLetterbox.hide();
         }
@@ -305,7 +340,9 @@
     }
 
     private void handleHorizontalDoubleTap(int x) {
-        if (!isHorizontalReachabilityEnabled() || mActivityRecord.isInTransition()) {
+        // TODO(b/260857308): Investigate if enabling reachability for translucent activity
+        if (hasInheritedLetterboxBehavior() || !isHorizontalReachabilityEnabled()
+                || mActivityRecord.isInTransition()) {
             return;
         }
 
@@ -341,7 +378,9 @@
     }
 
     private void handleVerticalDoubleTap(int y) {
-        if (!isVerticalReachabilityEnabled() || mActivityRecord.isInTransition()) {
+        // TODO(b/260857308): Investigate if enabling reachability for translucent activity
+        if (hasInheritedLetterboxBehavior() || !isVerticalReachabilityEnabled()
+                || mActivityRecord.isInTransition()) {
             return;
         }
 
@@ -390,7 +429,7 @@
                 && parentConfiguration.windowConfiguration.getWindowingMode()
                         == WINDOWING_MODE_FULLSCREEN
                 && (parentConfiguration.orientation == ORIENTATION_LANDSCAPE
-                && mActivityRecord.getRequestedConfigurationOrientation() == ORIENTATION_PORTRAIT);
+                && mActivityRecord.getOrientationForReachability() == ORIENTATION_PORTRAIT);
     }
 
     private boolean isHorizontalReachabilityEnabled() {
@@ -412,7 +451,7 @@
                 && parentConfiguration.windowConfiguration.getWindowingMode()
                         == WINDOWING_MODE_FULLSCREEN
                 && (parentConfiguration.orientation == ORIENTATION_PORTRAIT
-                && mActivityRecord.getRequestedConfigurationOrientation() == ORIENTATION_LANDSCAPE);
+                && mActivityRecord.getOrientationForReachability() == ORIENTATION_LANDSCAPE);
     }
 
     private boolean isVerticalReachabilityEnabled() {
@@ -576,9 +615,7 @@
         // Rounded corners should be displayed above the taskbar.
         bounds.bottom =
                 Math.min(bounds.bottom, getTaskbarInsetsSource(mainWindow).getFrame().top);
-        if (mActivityRecord.inSizeCompatMode() && mActivityRecord.getCompatScale() < 1.0f) {
-            bounds.scale(1.0f / mActivityRecord.getCompatScale());
-        }
+        scaleIfNeeded(bounds);
     }
 
     private int getInsetsStateCornerRadius(
@@ -788,4 +825,144 @@
                 w.mAttrs.insetsFlags.appearance
         );
     }
+
+    /**
+     * Handles translucent activities letterboxing inheriting constraints from the
+     * first opaque activity beneath.
+     * @param parent The parent container.
+     */
+    void onActivityParentChanged(WindowContainer<?> parent) {
+        if (!mLetterboxConfiguration.isTranslucentLetterboxingEnabled()) {
+            return;
+        }
+        if (mLetterboxConfigListener != null) {
+            mLetterboxConfigListener.onRemoved();
+            clearInheritedConfig();
+        }
+        // In case mActivityRecord.getCompatDisplayInsets() is not null 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()) {
+            return;
+        }
+        final ActivityRecord firstOpaqueActivityBeneath = mActivityRecord.getTask().getActivity(
+                ActivityRecord::fillsParent, mActivityRecord, false /* includeBoundary */,
+                true /* traverseTopToBottom */);
+        if (firstOpaqueActivityBeneath == null
+                || mActivityRecord.launchedFromUid != firstOpaqueActivityBeneath.getUid()) {
+            // We skip letterboxing if the translucent activity doesn't have any opaque
+            // activities beneath of if it's launched from a different user (e.g. notification)
+            return;
+        }
+        inheritConfiguration(firstOpaqueActivityBeneath);
+        mLetterboxConfigListener = WindowContainer.overrideConfigurationPropagation(
+                mActivityRecord, firstOpaqueActivityBeneath,
+                (opaqueConfig, transparentConfig) -> {
+                    final Configuration mutatedConfiguration = new Configuration();
+                    final Rect parentBounds = parent.getWindowConfiguration().getBounds();
+                    final Rect bounds = mutatedConfiguration.windowConfiguration.getBounds();
+                    final Rect letterboxBounds = opaqueConfig.windowConfiguration.getBounds();
+                    // We cannot use letterboxBounds directly here because the position relies on
+                    // letterboxing. Using letterboxBounds directly, would produce a double offset.
+                    bounds.set(parentBounds.left, parentBounds.top,
+                            parentBounds.left + letterboxBounds.width(),
+                            parentBounds.top + letterboxBounds.height());
+                    // 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());
+                    return mutatedConfiguration;
+                });
+    }
+
+    /**
+     * @return {@code true} if the current activity is translucent with an opaque activity
+     * beneath. In this case it will inherit bounds, orientation and aspect ratios from
+     * the first opaque activity beneath.
+     */
+    boolean hasInheritedLetterboxBehavior() {
+        return mLetterboxConfigListener != null && !mActivityRecord.matchParentBounds();
+    }
+
+    /**
+     * @return {@code true} if the current activity is translucent with an opaque activity
+     * beneath and needs to inherit its orientation.
+     */
+    boolean hasInheritedOrientation() {
+        // To force a different orientation, the transparent one needs to have an explicit one
+        // otherwise the existing one is fine and the actual orientation will depend on the
+        // bounds.
+        // To avoid wrong behaviour, we're not forcing orientation for activities with not
+        // fixed orientation (e.g. permission dialogs).
+        return hasInheritedLetterboxBehavior()
+                && mActivityRecord.mOrientation != SCREEN_ORIENTATION_UNSPECIFIED;
+    }
+
+    float getInheritedMinAspectRatio() {
+        return mInheritedMinAspectRatio;
+    }
+
+    float getInheritedMaxAspectRatio() {
+        return mInheritedMaxAspectRatio;
+    }
+
+    int getInheritedAppCompatState() {
+        return mInheritedAppCompatState;
+    }
+
+    float getInheritedSizeCompatScale() {
+        return mInheritedSizeCompatScale;
+    }
+
+    @Configuration.Orientation
+    int getInheritedOrientation() {
+        return mInheritedOrientation;
+    }
+
+    public ActivityRecord.CompatDisplayInsets getInheritedCompatDisplayInsets() {
+        return mInheritedCompatDisplayInsets;
+    }
+
+    private void inheritConfiguration(ActivityRecord firstOpaque) {
+        // To avoid wrong behaviour, we're not forcing a specific aspet ratio to activities
+        // which are not already providing one (e.g. permission dialogs) and presumably also
+        // not resizable.
+        if (mActivityRecord.getMinAspectRatio() != UNDEFINED_ASPECT_RATIO) {
+            mInheritedMinAspectRatio = firstOpaque.getMinAspectRatio();
+        }
+        if (mActivityRecord.getMaxAspectRatio() != UNDEFINED_ASPECT_RATIO) {
+            mInheritedMaxAspectRatio = firstOpaque.getMaxAspectRatio();
+        }
+        mInheritedOrientation = firstOpaque.getRequestedConfigurationOrientation();
+        mInheritedAppCompatState = firstOpaque.getAppCompatState();
+        mIsInheritedInSizeCompatMode = firstOpaque.inSizeCompatMode();
+        mInheritedSizeCompatScale = firstOpaque.getCompatScale();
+        mInheritedCompatDisplayInsets = firstOpaque.getCompatDisplayInsets();
+    }
+
+    private void clearInheritedConfig() {
+        mLetterboxConfigListener = null;
+        mInheritedMinAspectRatio = UNDEFINED_ASPECT_RATIO;
+        mInheritedMaxAspectRatio = UNDEFINED_ASPECT_RATIO;
+        mInheritedOrientation = Configuration.ORIENTATION_UNDEFINED;
+        mInheritedAppCompatState = APP_COMPAT_STATE_CHANGED__STATE__UNKNOWN;
+        mIsInheritedInSizeCompatMode = false;
+        mInheritedSizeCompatScale = 1f;
+        mInheritedCompatDisplayInsets = null;
+    }
+
+    private void scaleIfNeeded(Rect bounds) {
+        if (boundsNeedToScale()) {
+            bounds.scale(1.0f / mActivityRecord.getCompatScale());
+        }
+    }
+
+    private boolean boundsNeedToScale() {
+        if (hasInheritedLetterboxBehavior()) {
+            return mIsInheritedInSizeCompatMode
+                    && mInheritedSizeCompatScale < 1.0f;
+        } else {
+            return mActivityRecord.inSizeCompatMode()
+                    && mActivityRecord.getCompatScale() < 1.0f;
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 36747c8..07e3b83 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -18,6 +18,7 @@
 
 import static android.app.ActivityManager.isStartResultSuccessful;
 import static android.app.ActivityTaskManager.INVALID_TASK_ID;
+import static android.app.ActivityTaskManager.INVALID_WINDOWING_MODE;
 import static android.app.ActivityTaskManager.RESIZE_MODE_FORCED;
 import static android.app.ActivityTaskManager.RESIZE_MODE_SYSTEM_SCREEN_ROTATION;
 import static android.app.ITaskStackListener.FORCED_RESIZEABLE_REASON_SPLIT_SCREEN;
@@ -443,6 +444,8 @@
     @Surface.Rotation
     private int mRotation;
 
+    int mMultiWindowRestoreWindowingMode = INVALID_WINDOWING_MODE;
+
     /**
      * Last requested orientation reported to DisplayContent. This is different from {@link
      * #mOrientation} in the sense that this takes activities' requested orientation into
@@ -466,6 +469,7 @@
 
     // Whether the task is currently being drag-resized
     private boolean mDragResizing;
+    private int mDragResizeMode;
 
     // This represents the last resolved activity values for this task
     // NOTE: This value needs to be persisted with each task
@@ -2806,6 +2810,11 @@
         }
 
         final Task rootTask = getRootTask();
+        final DisplayContent displayContent = rootTask.getDisplayContent();
+        // It doesn't matter if we in particular are part of the resize, since we couldn't have
+        // a DimLayer anyway if we weren't visible.
+        final boolean dockedResizing = displayContent != null
+                && displayContent.mDividerControllerLocked.isResizing();
         if (inFreeformWindowingMode()) {
             boolean[] foundTop = { false };
             forAllActivities(a -> { getMaxVisibleBounds(a, out, foundTop); });
@@ -2816,10 +2825,18 @@
 
         if (!matchParentBounds()) {
             // When minimizing the root docked task when going home, we don't adjust the task bounds
-            // so we need to intersect the task bounds with the root task bounds here..
-            rootTask.getBounds(mTmpRect);
-            mTmpRect.intersect(getBounds());
-            out.set(mTmpRect);
+            // so we need to intersect the task bounds with the root task bounds here.
+            //
+            // If we are Docked Resizing with snap points, the task bounds could be smaller than the
+            // root task bounds and so we don't even want to use them. Even if the app should not be
+            // resized the Dim should keep up with the divider.
+            if (dockedResizing) {
+                rootTask.getBounds(out);
+            } else {
+                rootTask.getBounds(mTmpRect);
+                mTmpRect.intersect(getBounds());
+                out.set(mTmpRect);
+            }
         } else {
             out.set(getBounds());
         }
@@ -2850,15 +2867,16 @@
         }
     }
 
-    void setDragResizing(boolean dragResizing) {
+    void setDragResizing(boolean dragResizing, int dragResizeMode) {
         if (mDragResizing != dragResizing) {
-            // No need to check if allowed if it's leaving dragResize
+            // No need to check if the mode is allowed if it's leaving dragResize
             if (dragResizing
-                    && !(getRootTask().getWindowingMode() == WINDOWING_MODE_FREEFORM)) {
-                throw new IllegalArgumentException("Drag resize not allow for root task id="
-                        + getRootTaskId());
+                    && !DragResizeMode.isModeAllowedForRootTask(getRootTask(), dragResizeMode)) {
+                throw new IllegalArgumentException("Drag resize mode not allow for root task id="
+                        + getRootTaskId() + " dragResizeMode=" + dragResizeMode);
             }
             mDragResizing = dragResizing;
+            mDragResizeMode = dragResizeMode;
             resetDragResizingChangeReported();
         }
     }
@@ -2867,6 +2885,10 @@
         return mDragResizing;
     }
 
+    int getDragResizeMode() {
+        return mDragResizeMode;
+    }
+
     void adjustBoundsForDisplayChangeIfNeeded(final DisplayContent displayContent) {
         if (displayContent == null) {
             return;
diff --git a/services/core/java/com/android/server/wm/TaskPositioner.java b/services/core/java/com/android/server/wm/TaskPositioner.java
index 9b3fb6b..5b32149 100644
--- a/services/core/java/com/android/server/wm/TaskPositioner.java
+++ b/services/core/java/com/android/server/wm/TaskPositioner.java
@@ -27,6 +27,7 @@
 import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_RIGHT;
 import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_TOP;
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION;
+import static com.android.server.wm.DragResizeMode.DRAG_RESIZE_MODE_FREEFORM;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_TASK_POSITIONING;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
@@ -367,7 +368,7 @@
 
     private void endDragLocked() {
         mResizing = false;
-        mTask.setDragResizing(false);
+        mTask.setDragResizing(false, DRAG_RESIZE_MODE_FREEFORM);
     }
 
     /** Returns true if the move operation should be ended. */
@@ -379,7 +380,7 @@
 
         if (mCtrlType != CTRL_NONE) {
             resizeDrag(x, y);
-            mTask.setDragResizing(true);
+            mTask.setDragResizing(true, DRAG_RESIZE_MODE_FREEFORM);
             return false;
         }
 
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 1d17cd4..64574a7 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -642,7 +642,6 @@
         if (showSurfaceOnCreation()) {
             getSyncTransaction().show(mSurfaceControl);
         }
-        onSurfaceShown(getSyncTransaction());
         updateSurfacePositionNonOrganized();
         if (mLastMagnificationSpec != null) {
             applyMagnificationSpec(getSyncTransaction(), mLastMagnificationSpec);
@@ -697,13 +696,6 @@
         scheduleAnimation();
     }
 
-    /**
-     * Called when the surface is shown for the first time.
-     */
-    void onSurfaceShown(Transaction t) {
-        // do nothing
-    }
-
     // Temp. holders for a chain of containers we are currently processing.
     private final LinkedList<WindowContainer> mTmpChain1 = new LinkedList<>();
     private final LinkedList<WindowContainer> mTmpChain2 = new LinkedList<>();
@@ -3989,27 +3981,54 @@
         unregisterConfigurationChangeListener(listener);
     }
 
+    static void overrideConfigurationPropagation(WindowContainer<?> receiver,
+            WindowContainer<?> supplier) {
+        overrideConfigurationPropagation(receiver, supplier, null /* configurationMerger */);
+    }
+
     /**
      * Forces the receiver container to always use the configuration of the supplier container as
      * its requested override configuration. It allows to propagate configuration without changing
      * the relationship between child and parent.
+     *
+     * @param receiver            The {@link WindowContainer<?>} which will receive the {@link
+     *                            Configuration} result of the merging operation.
+     * @param supplier            The {@link WindowContainer<?>} which provides the initial {@link
+     *                            Configuration}.
+     * @param configurationMerger A {@link ConfigurationMerger} which combines the {@link
+     *                            Configuration} of the receiver and the supplier.
      */
-    static void overrideConfigurationPropagation(WindowContainer<?> receiver,
-            WindowContainer<?> supplier) {
+    static WindowContainerListener overrideConfigurationPropagation(WindowContainer<?> receiver,
+            WindowContainer<?> supplier, @Nullable ConfigurationMerger configurationMerger) {
         final ConfigurationContainerListener listener = new ConfigurationContainerListener() {
             @Override
             public void onMergedOverrideConfigurationChanged(Configuration mergedOverrideConfig) {
-                receiver.onRequestedOverrideConfigurationChanged(supplier.getConfiguration());
+                final Configuration mergedConfiguration =
+                        configurationMerger != null
+                                ? configurationMerger.merge(mergedOverrideConfig,
+                                receiver.getConfiguration())
+                                : supplier.getConfiguration();
+                receiver.onRequestedOverrideConfigurationChanged(mergedConfiguration);
             }
         };
         supplier.registerConfigurationChangeListener(listener);
-        receiver.registerWindowContainerListener(new WindowContainerListener() {
+        final WindowContainerListener wcListener = new WindowContainerListener() {
             @Override
             public void onRemoved() {
                 receiver.unregisterWindowContainerListener(this);
                 supplier.unregisterConfigurationChangeListener(listener);
             }
-        });
+        };
+        receiver.registerWindowContainerListener(wcListener);
+        return wcListener;
+    }
+
+    /**
+     * Abstraction for functions merging two {@link Configuration} objects into one.
+     */
+    @FunctionalInterface
+    interface ConfigurationMerger {
+        Configuration merge(Configuration first, Configuration second);
     }
 
     /**
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 23bce36..be1e7e6 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -7128,6 +7128,20 @@
         return 0;
     }
 
+    void setDockedRootTaskResizing(boolean resizing) {
+        getDefaultDisplayContentLocked().getDockedDividerController().setResizing(resizing);
+        requestTraversal();
+    }
+
+    @Override
+    public void setDockedTaskDividerTouchRegion(Rect touchRegion) {
+        synchronized (mGlobalLock) {
+            final DisplayContent dc = getDefaultDisplayContentLocked();
+            dc.getDockedDividerController().setTouchRegion(touchRegion);
+            dc.updateTouchExcludeRegion();
+        }
+    }
+
     void setForceDesktopModeOnExternalDisplays(boolean forceDesktopModeOnExternalDisplays) {
         synchronized (mGlobalLock) {
             mForceDesktopModeOnExternalDisplays = forceDesktopModeOnExternalDisplays;
diff --git a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
index 46a30fb..060784d 100644
--- a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
+++ b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
@@ -978,6 +978,29 @@
         return 0;
     }
 
+    private int runSetTranslucentLetterboxingEnabled(PrintWriter pw) {
+        String arg = getNextArg();
+        final boolean enabled;
+        switch (arg) {
+            case "true":
+            case "1":
+                enabled = true;
+                break;
+            case "false":
+            case "0":
+                enabled = false;
+                break;
+            default:
+                getErrPrintWriter().println("Error: expected true, 1, false, 0, but got " + arg);
+                return -1;
+        }
+
+        synchronized (mInternal.mGlobalLock) {
+            mLetterboxConfiguration.setTranslucentLetterboxingOverrideEnabled(enabled);
+        }
+        return 0;
+    }
+
     private int runSetLetterboxStyle(PrintWriter pw) throws RemoteException {
         if (peekNextArg() == null) {
             getErrPrintWriter().println("Error: No arguments provided.");
@@ -1033,6 +1056,9 @@
                 case "--isSplitScreenAspectRatioForUnresizableAppsEnabled":
                     runSetLetterboxIsSplitScreenAspectRatioForUnresizableAppsEnabled(pw);
                     break;
+                case "--isTranslucentLetterboxingEnabled":
+                    runSetTranslucentLetterboxingEnabled(pw);
+                    break;
                 default:
                     getErrPrintWriter().println(
                             "Error: Unrecognized letterbox style option: " + arg);
@@ -1096,6 +1122,9 @@
                         mLetterboxConfiguration
                                 .getIsSplitScreenAspectRatioForUnresizableAppsEnabled();
                         break;
+                    case "isTranslucentLetterboxingEnabled":
+                        mLetterboxConfiguration.resetTranslucentLetterboxingEnabled();
+                        break;
                     default:
                         getErrPrintWriter().println(
                                 "Error: Unrecognized letterbox style option: " + arg);
@@ -1196,6 +1225,7 @@
             mLetterboxConfiguration.resetDefaultPositionForVerticalReachability();
             mLetterboxConfiguration.resetIsEducationEnabled();
             mLetterboxConfiguration.resetIsSplitScreenAspectRatioForUnresizableAppsEnabled();
+            mLetterboxConfiguration.resetTranslucentLetterboxingEnabled();
         }
     }
 
@@ -1232,7 +1262,6 @@
             pw.println("Is using split screen aspect ratio as aspect ratio for unresizable apps: "
                     + mLetterboxConfiguration
                             .getIsSplitScreenAspectRatioForUnresizableAppsEnabled());
-
             pw.println("Background type: "
                     + LetterboxConfiguration.letterboxBackgroundTypeToString(
                             mLetterboxConfiguration.getLetterboxBackgroundType()));
@@ -1242,6 +1271,12 @@
                     + mLetterboxConfiguration.getLetterboxBackgroundWallpaperBlurRadius());
             pw.println("    Wallpaper dark scrim alpha: "
                     + mLetterboxConfiguration.getLetterboxBackgroundWallpaperDarkScrimAlpha());
+
+            if (mLetterboxConfiguration.isTranslucentLetterboxingEnabled()) {
+                pw.println("Letterboxing for translucent activities: enabled");
+            } else {
+                pw.println("Letterboxing for translucent activities: disabled");
+            }
         }
         return 0;
     }
@@ -1434,12 +1469,16 @@
         pw.println("      --isSplitScreenAspectRatioForUnresizableAppsEnabled [true|1|false|0]");
         pw.println("        Whether using split screen aspect ratio as a default aspect ratio for");
         pw.println("        unresizable apps.");
+        pw.println("      --isTranslucentLetterboxingEnabled [true|1|false|0]");
+        pw.println("        Whether letterboxing for translucent activities is enabled.");
+
         pw.println("  reset-letterbox-style [aspectRatio|cornerRadius|backgroundType");
         pw.println("      |backgroundColor|wallpaperBlurRadius|wallpaperDarkScrimAlpha");
         pw.println("      |horizontalPositionMultiplier|verticalPositionMultiplier");
         pw.println("      |isHorizontalReachabilityEnabled|isVerticalReachabilityEnabled");
-        pw.println("      isEducationEnabled||defaultPositionMultiplierForHorizontalReachability");
-        pw.println("      ||defaultPositionMultiplierForVerticalReachability]");
+        pw.println("      |isEducationEnabled||defaultPositionMultiplierForHorizontalReachability");
+        pw.println("      |isTranslucentLetterboxingEnabled");
+        pw.println("      |defaultPositionMultiplierForVerticalReachability]");
         pw.println("    Resets overrides to default values for specified properties separated");
         pw.println("    by space, e.g. 'reset-letterbox-style aspectRatio cornerRadius'.");
         pw.println("    If no arguments provided, all values will be reset.");
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 9b69369..7241172 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -51,6 +51,7 @@
 import static com.android.server.wm.ActivityTaskManagerService.LAYOUT_REASON_CONFIG_CHANGED;
 import static com.android.server.wm.ActivityTaskManagerService.enforceTaskPermission;
 import static com.android.server.wm.ActivityTaskSupervisor.PRESERVE_WINDOWS;
+import static com.android.server.wm.DragResizeMode.DRAG_RESIZE_MODE_FREEFORM;
 import static com.android.server.wm.Task.FLAG_FORCE_HIDDEN_FOR_PINNED_TASK;
 import static com.android.server.wm.Task.FLAG_FORCE_HIDDEN_FOR_TASK_ORG;
 import static com.android.server.wm.TaskFragment.EMBEDDING_ALLOWED;
@@ -721,7 +722,7 @@
         }
 
         if ((c.getChangeMask() & WindowContainerTransaction.Change.CHANGE_DRAG_RESIZING) != 0) {
-            tr.setDragResizing(c.getDragResizing());
+            tr.setDragResizing(c.getDragResizing(), DRAG_RESIZE_MODE_FREEFORM);
         }
 
         final int childWindowingMode = c.getActivityWindowingMode();
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 4d6f7bc..1b7bd9e 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -36,6 +36,9 @@
 import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_FRAME;
 import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION;
 import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_VISIBLE;
+import static android.view.WindowCallbacks.RESIZE_MODE_DOCKED_DIVIDER;
+import static android.view.WindowCallbacks.RESIZE_MODE_FREEFORM;
+import static android.view.WindowCallbacks.RESIZE_MODE_INVALID;
 import static android.view.WindowInsets.Type.navigationBars;
 import static android.view.WindowInsets.Type.systemBars;
 import static android.view.WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
@@ -120,6 +123,8 @@
 import static com.android.server.wm.AnimationSpecProto.MOVE;
 import static com.android.server.wm.DisplayContent.IME_TARGET_LAYERING;
 import static com.android.server.wm.DisplayContent.logsGestureExclusionRestrictions;
+import static com.android.server.wm.DragResizeMode.DRAG_RESIZE_MODE_DOCKED_DIVIDER;
+import static com.android.server.wm.DragResizeMode.DRAG_RESIZE_MODE_FREEFORM;
 import static com.android.server.wm.IdentifierProto.HASH_CODE;
 import static com.android.server.wm.IdentifierProto.TITLE;
 import static com.android.server.wm.IdentifierProto.USER_ID;
@@ -365,6 +370,7 @@
     boolean mHidden = true;    // Used to determine if to show child windows.
     private boolean mDragResizing;
     private boolean mDragResizingChangeReported = true;
+    private int mResizeMode;
     private boolean mRedrawForSyncReported;
 
     /**
@@ -3938,14 +3944,24 @@
         if (isDragResizeChanged) {
             setDragResizing();
         }
-        final boolean isDragResizing = isDragResizing();
+        int resizeMode = RESIZE_MODE_INVALID;
+        if (isDragResizing()) {
+            switch (getResizeMode()) {
+                case DRAG_RESIZE_MODE_FREEFORM:
+                    resizeMode = RESIZE_MODE_FREEFORM;
+                    break;
+                case DRAG_RESIZE_MODE_DOCKED_DIVIDER:
+                    resizeMode = RESIZE_MODE_DOCKED_DIVIDER;
+                    break;
+            }
+        }
 
         markRedrawForSyncReported();
 
         try {
             mClient.resized(mClientWindowFrames, reportDraw, mLastReportedConfiguration,
                     getCompatInsetsState(), forceRelayout, alwaysConsumeSystemBars, displayId,
-                    syncWithBuffers ? mSyncSeqId : -1, isDragResizing);
+                    syncWithBuffers ? mSyncSeqId : -1, resizeMode);
             if (drawPending && prevRotation >= 0 && prevRotation != mLastReportedConfiguration
                     .getMergedConfiguration().windowConfiguration.getRotation()) {
                 mOrientationChangeRedrawRequestTime = SystemClock.elapsedRealtime();
@@ -4188,6 +4204,10 @@
         super.resetDragResizingChangeReported();
     }
 
+    int getResizeMode() {
+        return mResizeMode;
+    }
+
     private boolean computeDragResizing() {
         final Task task = getTask();
         if (task == null) {
@@ -4210,7 +4230,8 @@
             return true;
         }
 
-        return false;
+        return getDisplayContent().mDividerControllerLocked.isResizing()
+                && !task.inFreeformWindowingMode() && !isGoneForLayout();
     }
 
     void setDragResizing() {
@@ -4219,12 +4240,25 @@
             return;
         }
         mDragResizing = resizing;
+        final Task task = getTask();
+        if (task != null && task.isDragResizing()) {
+            mResizeMode = task.getDragResizeMode();
+        } else {
+            mResizeMode = mDragResizing && getDisplayContent().mDividerControllerLocked.isResizing()
+                    ? DRAG_RESIZE_MODE_DOCKED_DIVIDER
+                    : DRAG_RESIZE_MODE_FREEFORM;
+        }
     }
 
     boolean isDragResizing() {
         return mDragResizing;
     }
 
+    boolean isDockedResizing() {
+        return (mDragResizing && getResizeMode() == DRAG_RESIZE_MODE_DOCKED_DIVIDER)
+                || (isChildWindow() && getParentWindow().isDockedResizing());
+    }
+
     @CallSuper
     @Override
     public void dumpDebug(ProtoOutputStream proto, long fieldId,
@@ -5900,6 +5934,10 @@
             //    level. Because the animation runs before display is rotated, task bounds should
             //    represent the frames in display space coordinates.
             outFrame.set(getTask().getBounds());
+        } else if (isDockedResizing()) {
+            // If we are animating while docked resizing, then use the root task bounds as the
+            // animation target (which will be different than the task bounds)
+            outFrame.set(getTask().getParent().getBounds());
         } else {
             outFrame.set(getParentFrame());
         }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java b/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java
index 9af30ba..4634ff5 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java
@@ -163,6 +163,7 @@
             "preferential_network_service_config";
     private static final String TAG_PROTECTED_PACKAGES = "protected_packages";
     private static final String TAG_SUSPENDED_PACKAGES = "suspended-packages";
+    private static final String TAG_MTE_POLICY = "mte-policy";
     private static final String ATTR_VALUE = "value";
     private static final String ATTR_LAST_NETWORK_LOGGING_NOTIFICATION = "last-notification";
     private static final String ATTR_NUM_NETWORK_LOGGING_NOTIFICATIONS = "num-notifications";
@@ -222,6 +223,8 @@
     int numNetworkLoggingNotifications = 0;
     long lastNetworkLoggingNotificationTimeMs = 0; // Time in milliseconds since epoch
 
+    @DevicePolicyManager.MtePolicy int mtePolicy = DevicePolicyManager.MTE_NOT_CONTROLLED_BY_POLICY;
+
     ActiveAdmin parentAdmin;
     final boolean isParent;
 
@@ -620,6 +623,9 @@
             }
             out.endTag(null, TAG_PREFERENTIAL_NETWORK_SERVICE_CONFIGS);
         }
+        if (mtePolicy != DevicePolicyManager.MTE_NOT_CONTROLLED_BY_POLICY) {
+            writeAttributeValueToXml(out, TAG_MTE_POLICY, mtePolicy);
+        }
     }
 
     private List<String> ssidsToStrings(Set<WifiSsid> ssids) {
@@ -906,6 +912,8 @@
                 if (!configs.isEmpty()) {
                     mPreferentialNetworkServiceConfigs = configs;
                 }
+            } else if (TAG_MTE_POLICY.equals(tag)) {
+                mtePolicy = parser.getAttributeInt(null, ATTR_VALUE);
             } else {
                 Slogf.w(LOG_TAG, "Unknown admin tag: %s", tag);
                 XmlUtils.skipCurrentTag(parser);
@@ -1338,5 +1346,8 @@
             }
             pw.decreaseIndent();
         }
+
+        pw.print("mtePolicy=");
+        pw.println(mtePolicy);
     }
 }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index e93809d..c42ddf8 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -5387,7 +5387,8 @@
                         }
                         if (!mInjector.storageManagerIsFileBasedEncryptionEnabled()) {
                             throw new UnsupportedOperationException(
-                                    "FLAG_EVICT_CREDENTIAL_ENCRYPTION_KEY only applies to FBE devices");
+                                    "FLAG_EVICT_CREDENTIAL_ENCRYPTION_KEY only applies to FBE"
+                                        + " devices");
                         }
                         mUserManager.evictCredentialEncryptionKey(callingUserId);
                     }
@@ -19240,4 +19241,55 @@
                 KEEP_PROFILES_RUNNING_FLAG,
                 DEFAULT_KEEP_PROFILES_RUNNING_FLAG);
     }
+
+    @Override
+    public void setMtePolicy(int flags) {
+        final Set<Integer> allowedModes =
+                Set.of(
+                        DevicePolicyManager.MTE_NOT_CONTROLLED_BY_POLICY,
+                        DevicePolicyManager.MTE_DISABLED,
+                        DevicePolicyManager.MTE_ENABLED);
+        Preconditions.checkArgument(
+                allowedModes.contains(flags), "Provided mode is not one of the allowed values.");
+        final CallerIdentity caller = getCallerIdentity();
+        if (flags == DevicePolicyManager.MTE_DISABLED) {
+            Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller));
+        } else {
+            Preconditions.checkCallAuthorization(
+                    isDefaultDeviceOwner(caller)
+                            || isProfileOwnerOfOrganizationOwnedDevice(caller));
+        }
+        synchronized (getLockObject()) {
+            ActiveAdmin admin =
+                    getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked(
+                            UserHandle.USER_SYSTEM);
+            if (admin != null) {
+                final String memtagProperty = "arm64.memtag.bootctl";
+                if (flags == DevicePolicyManager.MTE_ENABLED) {
+                    mInjector.systemPropertiesSet(memtagProperty, "memtag");
+                } else if (flags == DevicePolicyManager.MTE_DISABLED) {
+                    mInjector.systemPropertiesSet(memtagProperty, "memtag-off");
+                }
+                admin.mtePolicy = flags;
+                saveSettingsLocked(caller.getUserId());
+            }
+        }
+    }
+
+    @Override
+    public int getMtePolicy() {
+        final CallerIdentity caller = getCallerIdentity();
+        Preconditions.checkCallAuthorization(
+                isDefaultDeviceOwner(caller)
+                        || isProfileOwnerOfOrganizationOwnedDevice(caller)
+                        || isSystemUid(caller));
+        synchronized (getLockObject()) {
+            ActiveAdmin admin =
+                    getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked(
+                            UserHandle.USER_SYSTEM);
+            return admin != null
+                    ? admin.mtePolicy
+                    : DevicePolicyManager.MTE_NOT_CONTROLLED_BY_POLICY;
+        }
+    }
 }
diff --git a/services/java/com/android/server/BootUserInitializer.java b/services/java/com/android/server/BootUserInitializer.java
index c3329795..deebfc7 100644
--- a/services/java/com/android/server/BootUserInitializer.java
+++ b/services/java/com/android/server/BootUserInitializer.java
@@ -102,6 +102,7 @@
         switchToInitialUser(initialUserId);
     }
 
+    /* TODO(b/261791491): STOPSHIP - SUW should be responsible for this. */
     private void provisionHeadlessSystemUser() {
         if (isDeviceProvisioned()) {
             Slogf.d(TAG, "provisionHeadlessSystemUser(): already provisioned");
diff --git a/services/permission/java/com/android/server/permission/access/AccessCheckingService.kt b/services/permission/java/com/android/server/permission/access/AccessCheckingService.kt
index 73c2ccc..f648be9 100644
--- a/services/permission/java/com/android/server/permission/access/AccessCheckingService.kt
+++ b/services/permission/java/com/android/server/permission/access/AccessCheckingService.kt
@@ -20,6 +20,7 @@
 import com.android.internal.annotations.Keep
 import com.android.server.LocalManagerRegistry
 import com.android.server.LocalServices
+import com.android.server.SystemConfig
 import com.android.server.SystemService
 import com.android.server.appop.AppOpsCheckingServiceInterface
 import com.android.server.permission.access.appop.AppOpService
@@ -28,7 +29,6 @@
 import com.android.server.pm.PackageManagerLocal
 import com.android.server.pm.UserManagerService
 import com.android.server.pm.permission.PermissionManagerServiceInterface
-import com.android.server.pm.permission.PermissionManagerServiceInternal
 import com.android.server.pm.pkg.PackageState
 
 @Keep
@@ -46,6 +46,7 @@
 
     private lateinit var packageManagerLocal: PackageManagerLocal
     private lateinit var userManagerService: UserManagerService
+    private lateinit var systemConfig: SystemConfig
 
     override fun onStart() {
         appOpService = AppOpService(this)
@@ -59,12 +60,16 @@
         packageManagerLocal =
             LocalManagerRegistry.getManagerOrThrow(PackageManagerLocal::class.java)
         userManagerService = UserManagerService.getInstance()
+        systemConfig = SystemConfig.getInstance()
 
         val userIds = IntSet(userManagerService.userIdsIncludingPreCreated)
-        val packageStates = packageManagerLocal.packageStates
+        val (packageStates, disabledSystemPackageStates) = packageManagerLocal.allPackageStates
+        val permissionAllowlist = systemConfig.permissionAllowlist
 
         val state = AccessState()
-        policy.initialize(state, userIds, packageStates)
+        policy.initialize(
+            state, userIds, packageStates, disabledSystemPackageStates, permissionAllowlist
+        )
         persistence.read(state)
         this.state = state
 
@@ -96,51 +101,60 @@
     }
 
     internal fun onStorageVolumeMounted(volumeUuid: String?, isSystemUpdated: Boolean) {
-        val packageStates = packageManagerLocal.packageStates
+        val (packageStates, disabledSystemPackageStates) = packageManagerLocal.allPackageStates
         mutateState {
-            with(policy) { onStorageVolumeMounted(packageStates, volumeUuid, isSystemUpdated) }
+            with(policy) {
+                onStorageVolumeMounted(
+                    packageStates, disabledSystemPackageStates, volumeUuid, isSystemUpdated
+                )
+            }
         }
     }
 
     internal fun onPackageAdded(packageName: String) {
-        val packageStates = packageManagerLocal.packageStates
+        val (packageStates, disabledSystemPackageStates) = packageManagerLocal.allPackageStates
         mutateState {
-            with(policy) { onPackageAdded(packageStates, packageName) }
+            with(policy) { onPackageAdded(packageStates, disabledSystemPackageStates, packageName) }
         }
     }
 
     internal fun onPackageRemoved(packageName: String, appId: Int) {
-        val packageStates = packageManagerLocal.packageStates
+        val (packageStates, disabledSystemPackageStates) = packageManagerLocal.allPackageStates
         mutateState {
-            with(policy) { onPackageRemoved(packageStates, packageName, appId) }
+            with(policy) {
+                onPackageRemoved(packageStates, disabledSystemPackageStates, packageName, appId)
+            }
         }
     }
 
-    internal fun onPackageInstalled(
-        packageName: String,
-        params: PermissionManagerServiceInternal.PackageInstalledParams,
-        userId: Int
-    ) {
-        val packageStates = packageManagerLocal.packageStates
+    internal fun onPackageInstalled(packageName: String, userId: Int) {
+        val (packageStates, disabledSystemPackageStates) = packageManagerLocal.allPackageStates
         mutateState {
-            with(policy) { onPackageInstalled(packageStates, packageName, params, userId) }
+            with(policy) {
+                onPackageInstalled(packageStates, disabledSystemPackageStates, packageName, userId)
+            }
         }
     }
 
     internal fun onPackageUninstalled(packageName: String, appId: Int, userId: Int) {
-        val packageStates = packageManagerLocal.packageStates
+        val (packageStates, disabledSystemPackageStates) = packageManagerLocal.allPackageStates
         mutateState {
-            with(policy) { onPackageUninstalled(packageStates, packageName, appId, userId) }
+            with(policy) {
+                onPackageUninstalled(
+                    packageStates, disabledSystemPackageStates, packageName, appId, userId
+                )
+            }
         }
     }
 
-    private val PackageManagerLocal.packageStates: Map<String, PackageState>
-        get() = withUnfilteredSnapshot().use { it.packageStates }
+    private val PackageManagerLocal.allPackageStates:
+        Pair<Map<String, PackageState>, Map<String, PackageState>>
+        get() = withUnfilteredSnapshot().use { it.packageStates to it.disabledSystemPackageStates }
 
     internal inline fun <T> getState(action: GetStateScope.() -> T): T =
         GetStateScope(state).action()
 
-    internal inline fun mutateState(action: MutateStateScope.() -> Unit) {
+    internal inline fun mutateState(crossinline action: MutateStateScope.() -> Unit) {
         synchronized(stateLock) {
             val oldState = state
             val newState = oldState.copy()
diff --git a/services/permission/java/com/android/server/permission/access/AccessPolicy.kt b/services/permission/java/com/android/server/permission/access/AccessPolicy.kt
index 0201dd0..89316c2 100644
--- a/services/permission/java/com/android/server/permission/access/AccessPolicy.kt
+++ b/services/permission/java/com/android/server/permission/access/AccessPolicy.kt
@@ -26,7 +26,7 @@
 import com.android.server.permission.access.util.forEachTag
 import com.android.server.permission.access.util.tag
 import com.android.server.permission.access.util.tagName
-import com.android.server.pm.permission.PermissionManagerServiceInternal
+import com.android.server.pm.permission.PermissionAllowlist
 import com.android.server.pm.pkg.PackageState
 
 class AccessPolicy private constructor(
@@ -54,14 +54,22 @@
         with(getSchemePolicy(subject, `object`)) { setDecision(subject, `object`, decision) }
     }
 
-    fun initialize(state: AccessState, userIds: IntSet, packageStates: Map<String, PackageState>) {
+    fun initialize(
+        state: AccessState,
+        userIds: IntSet,
+        packageStates: Map<String, PackageState>,
+        disabledSystemPackageStates: Map<String, PackageState>,
+        permissionAllowlist: PermissionAllowlist
+    ) {
         state.systemState.apply {
             this.userIds += userIds
             this.packageStates = packageStates
+            this.disabledSystemPackageStates = disabledSystemPackageStates
             packageStates.forEach { (_, packageState) ->
                 appIds.getOrPut(packageState.appId) { IndexedListSet() }
                     .add(packageState.packageName)
             }
+            this.permissionAllowlist = permissionAllowlist
         }
     }
 
@@ -83,10 +91,14 @@
 
     fun MutateStateScope.onStorageVolumeMounted(
         packageStates: Map<String, PackageState>,
+        disabledSystemPackageStates: Map<String, PackageState>,
         volumeUuid: String?,
         isSystemUpdated: Boolean
     ) {
-        newState.systemState.packageStates = packageStates
+        newState.systemState.apply {
+            this.packageStates = packageStates
+            this.disabledSystemPackageStates = disabledSystemPackageStates
+        }
         forEachSchemePolicy {
             with(it) { onStorageVolumeMounted(volumeUuid, isSystemUpdated) }
         }
@@ -94,18 +106,24 @@
 
     fun MutateStateScope.onPackageAdded(
         packageStates: Map<String, PackageState>,
+        disabledSystemPackageStates: Map<String, PackageState>,
         packageName: String
     ) {
-        newState.systemState.packageStates = packageStates
-        var isAppIdAdded = false
         val packageState = packageStates[packageName]
-        // TODO(zhanghai): Remove check before submission.
-        checkNotNull(packageState)
+        // TODO(zhanghai): STOPSHIP: Remove check before feature enable.
+        checkNotNull(packageState) {
+            "Added package $packageName isn't found in packageStates in onPackageAdded()"
+        }
         val appId = packageState.appId
-        newState.systemState.appIds.getOrPut(appId) {
-            isAppIdAdded = true
-            IndexedListSet()
-        }.add(packageName)
+        var isAppIdAdded = false
+        newState.systemState.apply {
+            this.packageStates = packageStates
+            this.disabledSystemPackageStates = disabledSystemPackageStates
+            appIds.getOrPut(appId) {
+                isAppIdAdded = true
+                IndexedListSet()
+            }.add(packageName)
+        }
         if (isAppIdAdded) {
             forEachSchemePolicy {
                 with(it) { onAppIdAdded(appId) }
@@ -118,18 +136,22 @@
 
     fun MutateStateScope.onPackageRemoved(
         packageStates: Map<String, PackageState>,
+        disabledSystemPackageStates: Map<String, PackageState>,
         packageName: String,
         appId: Int
     ) {
-        newState.systemState.packageStates = packageStates
+        // TODO(zhanghai): STOPSHIP: Remove check before feature enable.
+        check(packageName !in packageStates) {
+            "Removed package $packageName is still in packageStates in onPackageRemoved()"
+        }
         var isAppIdRemoved = false
-        // TODO(zhanghai): Remove check before submission.
-        check(packageName !in packageStates)
-        newState.systemState.appIds.apply appIds@{
-            this[appId]?.apply {
+        newState.systemState.apply {
+            this.packageStates = packageStates
+            this.disabledSystemPackageStates = disabledSystemPackageStates
+            appIds[appId]?.apply {
                 this -= packageName
                 if (isEmpty()) {
-                    this@appIds -= appId
+                    appIds -= appId
                     isAppIdRemoved = true
                 }
             }
@@ -146,26 +168,35 @@
 
     fun MutateStateScope.onPackageInstalled(
         packageStates: Map<String, PackageState>,
+        disabledSystemPackageStates: Map<String, PackageState>,
         packageName: String,
-        params: PermissionManagerServiceInternal.PackageInstalledParams,
         userId: Int
     ) {
-        newState.systemState.packageStates = packageStates
+        newState.systemState.apply {
+            this.packageStates = packageStates
+            this.disabledSystemPackageStates = disabledSystemPackageStates
+        }
         val packageState = packageStates[packageName]
-        // TODO(zhanghai): Remove check before submission.
-        checkNotNull(packageState)
+        // TODO(zhanghai): STOPSHIP: Remove check before feature enable.
+        checkNotNull(packageState) {
+            "Installed package $packageName isn't found in packageStates in onPackageInstalled()"
+        }
         forEachSchemePolicy {
-            with(it) { onPackageInstalled(packageState, params, userId) }
+            with(it) { onPackageInstalled(packageState, userId) }
         }
     }
 
     fun MutateStateScope.onPackageUninstalled(
         packageStates: Map<String, PackageState>,
+        disabledSystemPackageStates: Map<String, PackageState>,
         packageName: String,
         appId: Int,
         userId: Int
     ) {
-        newState.systemState.packageStates = packageStates
+        newState.systemState.apply {
+            this.packageStates = packageStates
+            this.disabledSystemPackageStates = disabledSystemPackageStates
+        }
         forEachSchemePolicy {
             with(it) { onPackageUninstalled(packageName, appId, userId) }
         }
@@ -241,10 +272,6 @@
 }
 
 abstract class SchemePolicy {
-    @Volatile
-    private var onDecisionChangedListeners = IndexedListSet<OnDecisionChangedListener>()
-    private val onDecisionChangedListenersLock = Any()
-
     abstract val subjectScheme: String
 
     abstract val objectScheme: String
@@ -257,30 +284,6 @@
         decision: Int
     )
 
-    fun addOnDecisionChangedListener(listener: OnDecisionChangedListener) {
-        synchronized(onDecisionChangedListenersLock) {
-            onDecisionChangedListeners = onDecisionChangedListeners + listener
-        }
-    }
-
-    fun removeOnDecisionChangedListener(listener: OnDecisionChangedListener) {
-        synchronized(onDecisionChangedListenersLock) {
-            onDecisionChangedListeners = onDecisionChangedListeners - listener
-        }
-    }
-
-    protected fun notifyOnDecisionChangedListeners(
-        subject: AccessUri,
-        `object`: AccessUri,
-        oldDecision: Int,
-        newDecision: Int
-    ) {
-        val listeners = onDecisionChangedListeners
-        listeners.forEachIndexed { _, it ->
-            it.onDecisionChanged(subject, `object`, oldDecision, newDecision)
-        }
-    }
-
     open fun MutateStateScope.onUserAdded(userId: Int) {}
 
     open fun MutateStateScope.onUserRemoved(userId: Int) {}
@@ -298,11 +301,7 @@
 
     open fun MutateStateScope.onPackageRemoved(packageName: String, appId: Int) {}
 
-    open fun MutateStateScope.onPackageInstalled(
-        packageState: PackageState,
-        params: PermissionManagerServiceInternal.PackageInstalledParams,
-        userId: Int
-    ) {}
+    open fun MutateStateScope.onPackageInstalled(packageState: PackageState, userId: Int) {}
 
     open fun MutateStateScope.onPackageUninstalled(packageName: String, appId: Int, userId: Int) {}
 
@@ -313,13 +312,4 @@
     open fun BinaryXmlPullParser.parseUserState(userId: Int, userState: UserState) {}
 
     open fun BinaryXmlSerializer.serializeUserState(userId: Int, userState: UserState) {}
-
-    fun interface OnDecisionChangedListener {
-        fun onDecisionChanged(
-            subject: AccessUri,
-            `object`: AccessUri,
-            oldDecision: Int,
-            newDecision: Int
-        )
-    }
 }
diff --git a/services/permission/java/com/android/server/permission/access/AccessState.kt b/services/permission/java/com/android/server/permission/access/AccessState.kt
index 4a2c78a..fae1ec3 100644
--- a/services/permission/java/com/android/server/permission/access/AccessState.kt
+++ b/services/permission/java/com/android/server/permission/access/AccessState.kt
@@ -19,15 +19,22 @@
 import android.content.pm.PermissionGroupInfo
 import com.android.server.permission.access.collection.* // ktlint-disable no-wildcard-imports
 import com.android.server.permission.access.permission.Permission
+import com.android.server.pm.permission.PermissionAllowlist
 import com.android.server.pm.pkg.PackageState
 
 class AccessState private constructor(
     val systemState: SystemState,
     val userStates: IntMap<UserState>
 ) {
-    constructor() : this(SystemState(), IntMap())
+    constructor() : this(
+        SystemState(),
+        IntMap()
+    )
 
-    fun copy(): AccessState = AccessState(systemState.copy(), userStates.copy { it.copy() })
+    fun copy(): AccessState = AccessState(
+        systemState.copy(),
+        userStates.copy { it.copy() }
+    )
 }
 
 class SystemState private constructor(
@@ -39,30 +46,26 @@
     val knownPackages: IntMap<IndexedListSet<String>>,
     // A map of userId to packageName
     val deviceAndProfileOwners: IntMap<String>,
-    // A map of packageName to (A map of oem permission name to whether it's granted)
-    val oemPermissions: IndexedMap<String, IndexedMap<String, Boolean>>,
     val privilegedPermissionAllowlistSourcePackageNames: IndexedListSet<String>,
-    // A map of packageName to a set of vendor priv app permission names
-    val vendorPrivAppPermissions: Map<String, Set<String>>,
-    val productPrivAppPermissions: Map<String, Set<String>>,
-    val systemExtPrivAppPermissions: Map<String, Set<String>>,
-    val privAppPermissions: Map<String, Set<String>>,
-    val apexPrivAppPermissions: Map<String, Map<String, Set<String>>>,
-    val vendorPrivAppDenyPermissions: Map<String, Set<String>>,
-    val productPrivAppDenyPermissions: Map<String, Set<String>>,
-    val systemExtPrivAppDenyPermissions: Map<String, Set<String>>,
-    val apexPrivAppDenyPermissions: Map<String, Map<String, Set<String>>>,
-    val privAppDenyPermissions: Map<String, Set<String>>,
+    var permissionAllowlist: PermissionAllowlist,
     val implicitToSourcePermissions: Map<String, Set<String>>,
     val permissionGroups: IndexedMap<String, PermissionGroupInfo>,
     val permissionTrees: IndexedMap<String, Permission>,
     val permissions: IndexedMap<String, Permission>
 ) : WritableState() {
     constructor() : this(
-        IntSet(), emptyMap(), emptyMap(), IntMap(), IntMap(), IntMap(), IndexedMap(),
-        IndexedListSet(), IndexedMap(), IndexedMap(), IndexedMap(), IndexedMap(), IndexedMap(),
-        IndexedMap(), IndexedMap(), IndexedMap(), IndexedMap(), IndexedMap(), IndexedMap(),
-        IndexedMap(), IndexedMap(), IndexedMap()
+        IntSet(),
+        emptyMap(),
+        emptyMap(),
+        IntMap(),
+        IntMap(),
+        IntMap(),
+        IndexedListSet(),
+        PermissionAllowlist(),
+        IndexedMap(),
+        IndexedMap(),
+        IndexedMap(),
+        IndexedMap()
     )
 
     fun copy(): SystemState =
@@ -73,18 +76,8 @@
             appIds.copy { it.copy() },
             knownPackages.copy { it.copy() },
             deviceAndProfileOwners.copy { it },
-            oemPermissions.copy { it.copy { it } },
             privilegedPermissionAllowlistSourcePackageNames.copy(),
-            vendorPrivAppPermissions,
-            productPrivAppPermissions,
-            systemExtPrivAppPermissions,
-            privAppPermissions,
-            apexPrivAppPermissions,
-            vendorPrivAppDenyPermissions,
-            productPrivAppDenyPermissions,
-            systemExtPrivAppDenyPermissions,
-            apexPrivAppDenyPermissions,
-            privAppDenyPermissions,
+            permissionAllowlist,
             implicitToSourcePermissions,
             permissionGroups.copy { it },
             permissionTrees.copy { it },
@@ -98,7 +91,11 @@
     val uidAppOpModes: IntMap<IndexedMap<String, Int>>,
     val packageAppOpModes: IndexedMap<String, IndexedMap<String, Int>>
 ) : WritableState() {
-    constructor() : this(IntMap(), IntMap(), IndexedMap())
+    constructor() : this(
+        IntMap(),
+        IntMap(),
+        IndexedMap()
+    )
 
     fun copy(): UserState = UserState(
         uidPermissionFlags.copy { it.copy { it } },
diff --git a/services/permission/java/com/android/server/permission/access/appop/BaseAppOpPolicy.kt b/services/permission/java/com/android/server/permission/access/appop/BaseAppOpPolicy.kt
index a1a5e2d..7f4e0f7 100644
--- a/services/permission/java/com/android/server/permission/access/appop/BaseAppOpPolicy.kt
+++ b/services/permission/java/com/android/server/permission/access/appop/BaseAppOpPolicy.kt
@@ -16,50 +16,17 @@
 
 package com.android.server.permission.access.appop
 
-import android.app.AppOpsManager
 import com.android.modules.utils.BinaryXmlPullParser
 import com.android.modules.utils.BinaryXmlSerializer
-import com.android.server.permission.access.AccessUri
 import com.android.server.permission.access.AppOpUri
-import com.android.server.permission.access.GetStateScope
-import com.android.server.permission.access.MutateStateScope
 import com.android.server.permission.access.SchemePolicy
 import com.android.server.permission.access.UserState
-import com.android.server.permission.access.collection.* // ktlint-disable no-wildcard-imports
 
-abstract class BaseAppOpPolicy(private val persistence: BaseAppOpPersistence) : SchemePolicy() {
-    override fun GetStateScope.getDecision(subject: AccessUri, `object`: AccessUri): Int {
-        `object` as AppOpUri
-        return getModes(subject)
-            .getWithDefault(`object`.appOpName, opToDefaultMode(`object`.appOpName))
-    }
-
-    override fun MutateStateScope.setDecision(
-        subject: AccessUri,
-        `object`: AccessUri,
-        decision: Int
-    ) {
-        `object` as AppOpUri
-        val modes = getOrCreateModes(subject)
-        val oldMode = modes.putWithDefault(`object`.appOpName, decision,
-            opToDefaultMode(`object`.appOpName))
-        if (modes.isEmpty()) {
-            removeModes(subject)
-        }
-        if (oldMode != decision) {
-            notifyOnDecisionChangedListeners(subject, `object`, oldMode, decision)
-        }
-    }
-
-    abstract fun GetStateScope.getModes(subject: AccessUri): IndexedMap<String, Int>?
-
-    abstract fun MutateStateScope.getOrCreateModes(subject: AccessUri): IndexedMap<String, Int>
-
-    abstract fun MutateStateScope.removeModes(subject: AccessUri)
-
-    // TODO need to check that [AppOpsManager.getSystemAlertWindowDefault] works; likely no issue
-    //  since running in system process.
-    private fun opToDefaultMode(appOpName: String) = AppOpsManager.opToDefaultMode(appOpName)
+abstract class BaseAppOpPolicy(
+    private val persistence: BaseAppOpPersistence
+) : SchemePolicy() {
+    override val objectScheme: String
+        get() = AppOpUri.SCHEME
 
     override fun BinaryXmlPullParser.parseUserState(userId: Int, userState: UserState) {
         with(persistence) { this@parseUserState.parseUserState(userId, userState) }
diff --git a/services/permission/java/com/android/server/permission/access/appop/PackageAppOpPolicy.kt b/services/permission/java/com/android/server/permission/access/appop/PackageAppOpPolicy.kt
index f4d6bfd..607e512 100644
--- a/services/permission/java/com/android/server/permission/access/appop/PackageAppOpPolicy.kt
+++ b/services/permission/java/com/android/server/permission/access/appop/PackageAppOpPolicy.kt
@@ -16,40 +16,101 @@
 
 package com.android.server.permission.access.appop
 
+import android.app.AppOpsManager
 import com.android.server.permission.access.AccessUri
 import com.android.server.permission.access.AppOpUri
 import com.android.server.permission.access.GetStateScope
 import com.android.server.permission.access.MutateStateScope
 import com.android.server.permission.access.PackageUri
-import com.android.server.permission.access.UserState
 import com.android.server.permission.access.collection.* // ktlint-disable no-wildcard-imports
 
 class PackageAppOpPolicy : BaseAppOpPolicy(PackageAppOpPersistence()) {
+    @Volatile
+    private var onAppOpModeChangedListeners = IndexedListSet<OnAppOpModeChangedListener>()
+    private val onAppOpModeChangedListenersLock = Any()
+
     override val subjectScheme: String
         get() = PackageUri.SCHEME
 
-    override val objectScheme: String
-        get() = AppOpUri.SCHEME
-
-    override fun GetStateScope.getModes(subject: AccessUri): IndexedMap<String, Int>? {
+    override fun GetStateScope.getDecision(subject: AccessUri, `object`: AccessUri): Int {
         subject as PackageUri
-        return state.userStates[subject.userId]?.packageAppOpModes?.get(subject.packageName)
+        `object` as AppOpUri
+        return getAppOpMode(subject.packageName, subject.userId, `object`.appOpName)
     }
 
-    override fun MutateStateScope.getOrCreateModes(subject: AccessUri): IndexedMap<String, Int> {
+    override fun MutateStateScope.setDecision(
+        subject: AccessUri,
+        `object`: AccessUri,
+        decision: Int
+    ) {
         subject as PackageUri
-        return newState.userStates.getOrPut(subject.userId) { UserState() }
-            .packageAppOpModes.getOrPut(subject.packageName) { IndexedMap() }
-    }
-
-    override fun MutateStateScope.removeModes(subject: AccessUri) {
-        subject as PackageUri
-        newState.userStates[subject.userId]?.packageAppOpModes?.remove(subject.packageName)
+        `object` as AppOpUri
+        setAppOpMode(subject.packageName, subject.userId, `object`.appOpName, decision)
     }
 
     override fun MutateStateScope.onPackageRemoved(packageName: String, appId: Int) {
         newState.userStates.forEachIndexed { _, _, userState ->
             userState.packageAppOpModes -= packageName
+            userState.requestWrite()
+            // Skip notifying the change listeners since the package no longer exists.
         }
     }
+
+    fun MutateStateScope.removeAppOpModes(packageName: String, userId: Int): Boolean =
+        newState.userStates[userId].packageAppOpModes.remove(packageName) != null
+
+    fun GetStateScope.getAppOpMode(packageName: String, userId: Int, appOpName: String): Int =
+        state.userStates[userId].packageAppOpModes[packageName]
+            .getWithDefault(appOpName, AppOpsManager.opToDefaultMode(appOpName))
+
+    fun MutateStateScope.setAppOpMode(
+        packageName: String,
+        userId: Int,
+        appOpName: String,
+        mode: Int
+    ): Boolean {
+        val userState = newState.userStates[userId]
+        val packageAppOpModes = userState.packageAppOpModes
+        var appOpModes = packageAppOpModes[packageName]
+        val defaultMode = AppOpsManager.opToDefaultMode(appOpName)
+        val oldMode = appOpModes.getWithDefault(appOpName, defaultMode)
+        if (oldMode == mode) {
+            return false
+        }
+        if (appOpModes == null) {
+            appOpModes = IndexedMap()
+            packageAppOpModes[packageName] = appOpModes
+        }
+        appOpModes.putWithDefault(appOpName, mode, defaultMode)
+        if (appOpModes.isEmpty()) {
+            packageAppOpModes -= packageName
+        }
+        userState.requestWrite()
+        onAppOpModeChangedListeners.forEachIndexed { _, it ->
+            it.onAppOpModeChanged(packageName, userId, appOpName, oldMode, mode)
+        }
+        return true
+    }
+
+    fun addOnAppOpModeChangedListener(listener: OnAppOpModeChangedListener) {
+        synchronized(onAppOpModeChangedListenersLock) {
+            onAppOpModeChangedListeners = onAppOpModeChangedListeners + listener
+        }
+    }
+
+    fun removeOnAppOpModeChangedListener(listener: OnAppOpModeChangedListener) {
+        synchronized(onAppOpModeChangedListenersLock) {
+            onAppOpModeChangedListeners = onAppOpModeChangedListeners - listener
+        }
+    }
+
+    fun interface OnAppOpModeChangedListener {
+        fun onAppOpModeChanged(
+            packageName: String,
+            userId: Int,
+            appOpName: String,
+            oldMode: Int,
+            newMode: Int
+        )
+    }
 }
diff --git a/services/permission/java/com/android/server/permission/access/appop/UidAppOpPolicy.kt b/services/permission/java/com/android/server/permission/access/appop/UidAppOpPolicy.kt
index 862db8f..0b01038 100644
--- a/services/permission/java/com/android/server/permission/access/appop/UidAppOpPolicy.kt
+++ b/services/permission/java/com/android/server/permission/access/appop/UidAppOpPolicy.kt
@@ -16,40 +16,104 @@
 
 package com.android.server.permission.access.appop
 
+import android.app.AppOpsManager
 import com.android.server.permission.access.AccessUri
 import com.android.server.permission.access.AppOpUri
 import com.android.server.permission.access.GetStateScope
 import com.android.server.permission.access.MutateStateScope
 import com.android.server.permission.access.UidUri
-import com.android.server.permission.access.UserState
 import com.android.server.permission.access.collection.* // ktlint-disable no-wildcard-imports
 
 class UidAppOpPolicy : BaseAppOpPolicy(UidAppOpPersistence()) {
+    @Volatile
+    private var onAppOpModeChangedListeners = IndexedListSet<OnAppOpModeChangedListener>()
+    private val onAppOpModeChangedListenersLock = Any()
+
     override val subjectScheme: String
         get() = UidUri.SCHEME
 
-    override val objectScheme: String
-        get() = AppOpUri.SCHEME
-
-    override fun GetStateScope.getModes(subject: AccessUri): IndexedMap<String, Int>? {
+    override fun GetStateScope.getDecision(subject: AccessUri, `object`: AccessUri): Int {
         subject as UidUri
-        return state.userStates[subject.userId]?.uidAppOpModes?.get(subject.appId)
+        `object` as AppOpUri
+        return getAppOpMode(subject.appId, subject.userId, `object`.appOpName)
     }
 
-    override fun MutateStateScope.getOrCreateModes(subject: AccessUri): IndexedMap<String, Int> {
+    override fun MutateStateScope.setDecision(
+        subject: AccessUri,
+        `object`: AccessUri,
+        decision: Int
+    ) {
         subject as UidUri
-        return newState.userStates.getOrPut(subject.userId) { UserState() }
-            .uidAppOpModes.getOrPut(subject.appId) { IndexedMap() }
-    }
-
-    override fun MutateStateScope.removeModes(subject: AccessUri) {
-        subject as UidUri
-        newState.userStates[subject.userId]?.uidAppOpModes?.remove(subject.appId)
+        `object` as AppOpUri
+        setAppOpMode(subject.appId, subject.userId, `object`.appOpName, decision)
     }
 
     override fun MutateStateScope.onAppIdRemoved(appId: Int) {
         newState.userStates.forEachIndexed { _, _, userState ->
             userState.uidAppOpModes -= appId
+            userState.requestWrite()
+            // Skip notifying the change listeners since the app ID no longer exists.
         }
     }
+
+    fun GetStateScope.getAppOpModes(appId: Int, userId: Int): IndexedMap<String, Int>? =
+        state.userStates[userId].uidAppOpModes[appId]
+
+    fun MutateStateScope.removeAppOpModes(appId: Int, userId: Int): Boolean =
+        newState.userStates[userId].uidAppOpModes.removeReturnOld(appId) != null
+
+    fun GetStateScope.getAppOpMode(appId: Int, userId: Int, appOpName: String): Int =
+        state.userStates[userId].uidAppOpModes[appId]
+            .getWithDefault(appOpName, AppOpsManager.opToDefaultMode(appOpName))
+
+    fun MutateStateScope.setAppOpMode(
+        appId: Int,
+        userId: Int,
+        appOpName: String,
+        mode: Int
+    ): Boolean {
+        val userState = newState.userStates[userId]
+        val uidAppOpModes = userState.uidAppOpModes
+        var appOpModes = uidAppOpModes[appId]
+        val defaultMode = AppOpsManager.opToDefaultMode(appOpName)
+        val oldMode = appOpModes.getWithDefault(appOpName, defaultMode)
+        if (oldMode == mode) {
+            return false
+        }
+        if (appOpModes == null) {
+            appOpModes = IndexedMap()
+            uidAppOpModes[appId] = appOpModes
+        }
+        appOpModes.putWithDefault(appOpName, mode, defaultMode)
+        if (appOpModes.isEmpty()) {
+            uidAppOpModes -= appId
+        }
+        userState.requestWrite()
+        onAppOpModeChangedListeners.forEachIndexed { _, it ->
+            it.onAppOpModeChanged(appId, userId, appOpName, oldMode, mode)
+        }
+        return true
+    }
+
+    fun addOnAppOpModeChangedListener(listener: OnAppOpModeChangedListener) {
+        synchronized(onAppOpModeChangedListenersLock) {
+            onAppOpModeChangedListeners = onAppOpModeChangedListeners + listener
+        }
+    }
+
+    fun removeOnAppOpModeChangedListener(listener: OnAppOpModeChangedListener) {
+        synchronized(onAppOpModeChangedListenersLock) {
+            onAppOpModeChangedListeners = onAppOpModeChangedListeners - listener
+        }
+    }
+
+    fun interface OnAppOpModeChangedListener {
+        fun onAppOpModeChanged(
+            appId: Int,
+            userId: Int,
+            appOpName: String,
+            oldMode: Int,
+            newMode: Int
+        )
+    }
 }
diff --git a/services/permission/java/com/android/server/permission/access/collection/IndexedList.kt b/services/permission/java/com/android/server/permission/access/collection/IndexedList.kt
index 9cb2e86..c4d07fe 100644
--- a/services/permission/java/com/android/server/permission/access/collection/IndexedList.kt
+++ b/services/permission/java/com/android/server/permission/access/collection/IndexedList.kt
@@ -19,8 +19,8 @@
 typealias IndexedList<T> = ArrayList<T>
 
 inline fun <T> IndexedList<T>.allIndexed(predicate: (Int, T) -> Boolean): Boolean {
-    for (index in 0 until size) {
-        if (!predicate(index, this[index])) {
+    forEachIndexed { index, element ->
+        if (!predicate(index, element)) {
             return false
         }
     }
@@ -28,8 +28,8 @@
 }
 
 inline fun <T> IndexedList<T>.anyIndexed(predicate: (Int, T) -> Boolean): Boolean {
-    for (index in 0 until size) {
-        if (predicate(index, this[index])) {
+    forEachIndexed { index, element ->
+        if (predicate(index, element)) {
             return true
         }
     }
@@ -45,6 +45,12 @@
     }
 }
 
+inline fun <T> IndexedList<T>.forEachReversedIndexed(action: (Int, T) -> Unit) {
+    for (index in lastIndex downTo 0) {
+        action(index, this[index])
+    }
+}
+
 @Suppress("NOTHING_TO_INLINE")
 inline operator fun <T> IndexedList<T>.minus(element: T): IndexedList<T> =
     copy().apply { this -= element }
@@ -55,8 +61,8 @@
 }
 
 inline fun <T> IndexedList<T>.noneIndexed(predicate: (Int, T) -> Boolean): Boolean {
-    for (index in 0 until size) {
-        if (predicate(index, this[index])) {
+    forEachIndexed { index, element ->
+        if (predicate(index, element)) {
             return false
         }
     }
@@ -74,8 +80,8 @@
 
 inline fun <T> IndexedList<T>.removeAllIndexed(predicate: (Int, T) -> Boolean): Boolean {
     var isChanged = false
-    for (index in lastIndex downTo 0) {
-        if (predicate(index, this[index])) {
+    forEachReversedIndexed { index, element ->
+        if (predicate(index, element)) {
             removeAt(index)
             isChanged = true
         }
@@ -85,8 +91,8 @@
 
 inline fun <T> IndexedList<T>.retainAllIndexed(predicate: (Int, T) -> Boolean): Boolean {
     var isChanged = false
-    for (index in lastIndex downTo 0) {
-        if (!predicate(index, this[index])) {
+    forEachReversedIndexed { index, element ->
+        if (!predicate(index, element)) {
             removeAt(index)
             isChanged = true
         }
diff --git a/services/permission/java/com/android/server/permission/access/collection/IndexedListSet.kt b/services/permission/java/com/android/server/permission/access/collection/IndexedListSet.kt
index 1c42c50..c40f7ee 100644
--- a/services/permission/java/com/android/server/permission/access/collection/IndexedListSet.kt
+++ b/services/permission/java/com/android/server/permission/access/collection/IndexedListSet.kt
@@ -70,8 +70,8 @@
 }
 
 inline fun <T> IndexedListSet<T>.allIndexed(predicate: (Int, T) -> Boolean): Boolean {
-    for (index in 0 until size) {
-        if (!predicate(index, elementAt(index))) {
+    forEachIndexed { index, element ->
+        if (!predicate(index, element)) {
             return false
         }
     }
@@ -79,8 +79,8 @@
 }
 
 inline fun <T> IndexedListSet<T>.anyIndexed(predicate: (Int, T) -> Boolean): Boolean {
-    for (index in 0 until size) {
-        if (predicate(index, elementAt(index))) {
+    forEachIndexed { index, element ->
+        if (predicate(index, element)) {
             return true
         }
     }
@@ -93,6 +93,12 @@
     }
 }
 
+inline fun <T> IndexedListSet<T>.forEachReversedIndexed(action: (Int, T) -> Unit) {
+    for (index in lastIndex downTo 0) {
+        action(index, elementAt(index))
+    }
+}
+
 inline val <T> IndexedListSet<T>.lastIndex: Int
     get() = size - 1
 
@@ -106,8 +112,8 @@
 }
 
 inline fun <T> IndexedListSet<T>.noneIndexed(predicate: (Int, T) -> Boolean): Boolean {
-    for (index in 0 until size) {
-        if (predicate(index, elementAt(index))) {
+    forEachIndexed { index, element ->
+        if (predicate(index, element)) {
             return false
         }
     }
@@ -125,8 +131,8 @@
 
 inline fun <T> IndexedListSet<T>.removeAllIndexed(predicate: (Int, T) -> Boolean): Boolean {
     var isChanged = false
-    for (index in lastIndex downTo 0) {
-        if (predicate(index, elementAt(index))) {
+    forEachReversedIndexed { index, element ->
+        if (predicate(index, element)) {
             removeAt(index)
             isChanged = true
         }
@@ -136,8 +142,8 @@
 
 inline fun <T> IndexedListSet<T>.retainAllIndexed(predicate: (Int, T) -> Boolean): Boolean {
     var isChanged = false
-    for (index in lastIndex downTo 0) {
-        if (!predicate(index, elementAt(index))) {
+    forEachReversedIndexed { index, element ->
+        if (!predicate(index, element)) {
             removeAt(index)
             isChanged = true
         }
diff --git a/services/permission/java/com/android/server/permission/access/collection/IndexedMap.kt b/services/permission/java/com/android/server/permission/access/collection/IndexedMap.kt
index 2448ff0..43f18e2 100644
--- a/services/permission/java/com/android/server/permission/access/collection/IndexedMap.kt
+++ b/services/permission/java/com/android/server/permission/access/collection/IndexedMap.kt
@@ -21,8 +21,8 @@
 typealias IndexedMap<K, V> = ArrayMap<K, V>
 
 inline fun <K, V> IndexedMap<K, V>.allIndexed(predicate: (Int, K, V) -> Boolean): Boolean {
-    for (index in 0 until size) {
-        if (!predicate(index, keyAt(index), valueAt(index))) {
+    forEachIndexed { index, key, value ->
+        if (!predicate(index, key, value)) {
             return false
         }
     }
@@ -30,8 +30,8 @@
 }
 
 inline fun <K, V> IndexedMap<K, V>.anyIndexed(predicate: (Int, K, V) -> Boolean): Boolean {
-    for (index in 0 until size) {
-        if (predicate(index, keyAt(index), valueAt(index))) {
+    forEachIndexed { index, key, value ->
+        if (predicate(index, key, value)) {
             return true
         }
     }
@@ -46,8 +46,8 @@
     }
 
 inline fun <K, V, R> IndexedMap<K, V>.firstNotNullOfOrNullIndexed(transform: (Int, K, V) -> R): R? {
-    for (index in 0 until size) {
-        transform(index, keyAt(index), valueAt(index))?.let { return it }
+    forEachIndexed { index, key, value ->
+        transform(index, key, value)?.let { return it }
     }
     return null
 }
@@ -64,6 +64,12 @@
     }
 }
 
+inline fun <K, V> IndexedMap<K, V>.forEachReversedIndexed(action: (Int, K, V) -> Unit) {
+    for (index in lastIndex downTo 0) {
+        action(index, keyAt(index), valueAt(index))
+    }
+}
+
 inline fun <K, V> IndexedMap<K, V>.forEachValueIndexed(action: (Int, V) -> Unit) {
     for (index in 0 until size) {
         action(index, valueAt(index))
@@ -90,6 +96,15 @@
     remove(key)
 }
 
+inline fun <K, V> IndexedMap<K, V>.noneIndexed(predicate: (Int, K, V) -> Boolean): Boolean {
+    forEachIndexed { index, key, value ->
+        if (predicate(index, key, value)) {
+            return false
+        }
+    }
+    return true
+}
+
 @Suppress("NOTHING_TO_INLINE")
 inline fun <K, V> IndexedMap<K, V>.putWithDefault(key: K, value: V, defaultValue: V): V {
     val index = indexOfKey(key)
@@ -113,8 +128,8 @@
 
 inline fun <K, V> IndexedMap<K, V>.removeAllIndexed(predicate: (Int, K, V) -> Boolean): Boolean {
     var isChanged = false
-    for (index in lastIndex downTo 0) {
-        if (predicate(index, keyAt(index), valueAt(index))) {
+    forEachReversedIndexed { index, key, value ->
+        if (predicate(index, key, value)) {
             removeAt(index)
             isChanged = true
         }
@@ -124,8 +139,8 @@
 
 inline fun <K, V> IndexedMap<K, V>.retainAllIndexed(predicate: (Int, K, V) -> Boolean): Boolean {
     var isChanged = false
-    for (index in lastIndex downTo 0) {
-        if (!predicate(index, keyAt(index), valueAt(index))) {
+    forEachReversedIndexed { index, key, value ->
+        if (!predicate(index, key, value)) {
             removeAt(index)
             isChanged = true
         }
diff --git a/services/permission/java/com/android/server/permission/access/collection/IndexedSet.kt b/services/permission/java/com/android/server/permission/access/collection/IndexedSet.kt
index faaa6d3..13fa31f 100644
--- a/services/permission/java/com/android/server/permission/access/collection/IndexedSet.kt
+++ b/services/permission/java/com/android/server/permission/access/collection/IndexedSet.kt
@@ -21,8 +21,8 @@
 typealias IndexedSet<T> = ArraySet<T>
 
 inline fun <T> IndexedSet<T>.allIndexed(predicate: (Int, T) -> Boolean): Boolean {
-    for (index in 0 until size) {
-        if (!predicate(index, elementAt(index))) {
+    forEachIndexed { index, element ->
+        if (!predicate(index, element)) {
             return false
         }
     }
@@ -30,8 +30,8 @@
 }
 
 inline fun <T> IndexedSet<T>.anyIndexed(predicate: (Int, T) -> Boolean): Boolean {
-    for (index in 0 until size) {
-        if (predicate(index, elementAt(index))) {
+    forEachIndexed { index, element ->
+        if (predicate(index, element)) {
             return true
         }
     }
@@ -50,6 +50,12 @@
     }
 }
 
+inline fun <T> IndexedSet<T>.forEachReversedIndexed(action: (Int, T) -> Unit) {
+    for (index in lastIndex downTo 0) {
+        action(index, elementAt(index))
+    }
+}
+
 inline val <T> IndexedSet<T>.lastIndex: Int
     get() = size - 1
 
@@ -63,8 +69,8 @@
 }
 
 inline fun <T> IndexedSet<T>.noneIndexed(predicate: (Int, T) -> Boolean): Boolean {
-    for (index in 0 until size) {
-        if (predicate(index, elementAt(index))) {
+    forEachIndexed { index, element ->
+        if (predicate(index, element)) {
             return false
         }
     }
@@ -82,8 +88,8 @@
 
 inline fun <T> IndexedSet<T>.removeAllIndexed(predicate: (Int, T) -> Boolean): Boolean {
     var isChanged = false
-    for (index in lastIndex downTo 0) {
-        if (predicate(index, elementAt(index))) {
+    forEachReversedIndexed { index, element ->
+        if (predicate(index, element)) {
             removeAt(index)
             isChanged = true
         }
@@ -93,8 +99,8 @@
 
 inline fun <T> IndexedSet<T>.retainAllIndexed(predicate: (Int, T) -> Boolean): Boolean {
     var isChanged = false
-    for (index in lastIndex downTo 0) {
-        if (!predicate(index, elementAt(index))) {
+    forEachReversedIndexed { index, element ->
+        if (!predicate(index, element)) {
             removeAt(index)
             isChanged = true
         }
diff --git a/services/permission/java/com/android/server/permission/access/collection/IntMap.kt b/services/permission/java/com/android/server/permission/access/collection/IntMap.kt
index 0044b73..e905567 100644
--- a/services/permission/java/com/android/server/permission/access/collection/IntMap.kt
+++ b/services/permission/java/com/android/server/permission/access/collection/IntMap.kt
@@ -21,8 +21,8 @@
 typealias IntMap<T> = SparseArray<T>
 
 inline fun <T> IntMap<T>.allIndexed(predicate: (Int, Int, T) -> Boolean): Boolean {
-    for (index in 0 until size) {
-        if (!predicate(index, keyAt(index), valueAt(index))) {
+    forEachIndexed { index, key, value ->
+        if (!predicate(index, key, value)) {
             return false
         }
     }
@@ -30,8 +30,8 @@
 }
 
 inline fun <T> IntMap<T>.anyIndexed(predicate: (Int, Int, T) -> Boolean): Boolean {
-    for (index in 0 until size) {
-        if (predicate(index, keyAt(index), valueAt(index))) {
+    forEachIndexed { index, key, value ->
+        if (predicate(index, key, value)) {
             return true
         }
     }
@@ -46,8 +46,8 @@
     }
 
 inline fun <T, R> IntMap<T>.firstNotNullOfOrNullIndexed(transform: (Int, Int, T) -> R): R? {
-    for (index in 0 until size) {
-        transform(index, keyAt(index), valueAt(index))?.let { return it }
+    forEachIndexed { index, key, value ->
+        transform(index, key, value)?.let { return it }
     }
     return null
 }
@@ -64,6 +64,12 @@
     }
 }
 
+inline fun <T> IntMap<T>.forEachReversedIndexed(action: (Int, Int, T) -> Unit) {
+    for (index in lastIndex downTo 0) {
+        action(index, keyAt(index), valueAt(index))
+    }
+}
+
 inline fun <T> IntMap<T>.forEachValueIndexed(action: (Int, T) -> Unit) {
     for (index in 0 until size) {
         action(index, valueAt(index))
@@ -91,8 +97,8 @@
 }
 
 inline fun <T> IntMap<T>.noneIndexed(predicate: (Int, Int, T) -> Boolean): Boolean {
-    for (index in 0 until size) {
-        if (predicate(index, keyAt(index), valueAt(index))) {
+    forEachIndexed { index, key, value ->
+        if (predicate(index, key, value)) {
             return false
         }
     }
@@ -120,10 +126,22 @@
     }
 }
 
+// SparseArray.removeReturnOld() is @hide, so a backup once we move to APIs.
+fun <T> IntMap<T>.removeReturnOld(key: Int): T? {
+    val index = indexOfKey(key)
+    return if (index >= 0) {
+        val oldValue = valueAt(index)
+        removeAt(index)
+        oldValue
+    } else {
+        null
+    }
+}
+
 inline fun <T> IntMap<T>.removeAllIndexed(predicate: (Int, Int, T) -> Boolean): Boolean {
     var isChanged = false
-    for (index in lastIndex downTo 0) {
-        if (predicate(index, keyAt(index), valueAt(index))) {
+    forEachReversedIndexed { index, key, value ->
+        if (predicate(index, key, value)) {
             removeAt(index)
             isChanged = true
         }
@@ -133,8 +151,8 @@
 
 inline fun <T> IntMap<T>.retainAllIndexed(predicate: (Int, Int, T) -> Boolean): Boolean {
     var isChanged = false
-    for (index in lastIndex downTo 0) {
-        if (!predicate(index, keyAt(index), valueAt(index))) {
+    forEachReversedIndexed { index, key, value ->
+        if (!predicate(index, key, value)) {
             removeAt(index)
             isChanged = true
         }
diff --git a/services/permission/java/com/android/server/permission/access/collection/IntSet.kt b/services/permission/java/com/android/server/permission/access/collection/IntSet.kt
index 0d75a4c..4717251 100644
--- a/services/permission/java/com/android/server/permission/access/collection/IntSet.kt
+++ b/services/permission/java/com/android/server/permission/access/collection/IntSet.kt
@@ -54,8 +54,8 @@
 fun IntSet(values: IntArray): IntSet = IntSet().apply{ this += values }
 
 inline fun IntSet.allIndexed(predicate: (Int, Int) -> Boolean): Boolean {
-    for (index in 0 until size) {
-        if (!predicate(index, elementAt(index))) {
+    forEachIndexed { index, element ->
+        if (!predicate(index, element)) {
             return false
         }
     }
@@ -63,8 +63,8 @@
 }
 
 inline fun IntSet.anyIndexed(predicate: (Int, Int) -> Boolean): Boolean {
-    for (index in 0 until size) {
-        if (predicate(index, elementAt(index))) {
+    forEachIndexed { index, element ->
+        if (predicate(index, element)) {
             return true
         }
     }
@@ -77,6 +77,12 @@
     }
 }
 
+inline fun IntSet.forEachReversedIndexed(action: (Int, Int) -> Unit) {
+    for (index in lastIndex downTo 0) {
+        action(index, elementAt(index))
+    }
+}
+
 inline val IntSet.lastIndex: Int
     get() = size - 1
 
@@ -89,8 +95,8 @@
 }
 
 inline fun IntSet.noneIndexed(predicate: (Int, Int) -> Boolean): Boolean {
-    for (index in 0 until size) {
-        if (predicate(index, elementAt(index))) {
+    forEachIndexed { index, element ->
+        if (predicate(index, element)) {
             return false
         }
     }
@@ -115,8 +121,8 @@
 
 inline fun IntSet.removeAllIndexed(predicate: (Int, Int) -> Boolean): Boolean {
     var isChanged = false
-    for (index in lastIndex downTo 0) {
-        if (predicate(index, elementAt(index))) {
+    forEachReversedIndexed { index, element ->
+        if (predicate(index, element)) {
             removeAt(index)
             isChanged = true
         }
@@ -126,8 +132,8 @@
 
 inline fun IntSet.retainAllIndexed(predicate: (Int, Int) -> Boolean): Boolean {
     var isChanged = false
-    for (index in lastIndex downTo 0) {
-        if (!predicate(index, elementAt(index))) {
+    forEachReversedIndexed { index, element ->
+        if (!predicate(index, element)) {
             removeAt(index)
             isChanged = true
         }
diff --git a/services/permission/java/com/android/server/permission/access/collection/List.kt b/services/permission/java/com/android/server/permission/access/collection/List.kt
index d35e69e..91f15bc 100644
--- a/services/permission/java/com/android/server/permission/access/collection/List.kt
+++ b/services/permission/java/com/android/server/permission/access/collection/List.kt
@@ -17,8 +17,8 @@
 package com.android.server.permission.access.collection
 
 inline fun <T> List<T>.allIndexed(predicate: (Int, T) -> Boolean): Boolean {
-    for (index in 0 until size) {
-        if (!predicate(index, this[index])) {
+    forEachIndexed { index, element ->
+        if (!predicate(index, element)) {
             return false
         }
     }
@@ -26,8 +26,8 @@
 }
 
 inline fun <T> List<T>.anyIndexed(predicate: (Int, T) -> Boolean): Boolean {
-    for (index in 0 until size) {
-        if (predicate(index, this[index])) {
+    forEachIndexed { index, element ->
+        if (predicate(index, element)) {
             return true
         }
     }
@@ -40,9 +40,15 @@
     }
 }
 
+inline fun <T> List<T>.forEachReversedIndexed(action: (Int, T) -> Unit) {
+    for (index in lastIndex downTo 0) {
+        action(index, this[index])
+    }
+}
+
 inline fun <T> List<T>.noneIndexed(predicate: (Int, T) -> Boolean): Boolean {
-    for (index in 0 until size) {
-        if (predicate(index, this[index])) {
+    forEachIndexed { index, element ->
+        if (predicate(index, element)) {
             return false
         }
     }
@@ -51,8 +57,8 @@
 
 inline fun <T> MutableList<T>.removeAllIndexed(predicate: (Int, T) -> Boolean): Boolean {
     var isChanged = false
-    for (index in lastIndex downTo 0) {
-        if (predicate(index, this[index])) {
+    forEachReversedIndexed { index, element ->
+        if (predicate(index, element)) {
             removeAt(index)
             isChanged = true
         }
@@ -62,8 +68,8 @@
 
 inline fun <T> MutableList<T>.retainAllIndexed(predicate: (Int, T) -> Boolean): Boolean {
     var isChanged = false
-    for (index in lastIndex downTo 0) {
-        if (!predicate(index, this[index])) {
+    forEachReversedIndexed { index, element ->
+        if (!predicate(index, element)) {
             removeAt(index)
             isChanged = true
         }
diff --git a/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt b/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt
index 1e6996b..f04734c 100644
--- a/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt
@@ -435,7 +435,8 @@
         } else {
             intArrayOf(userId)
         }
-        userIds.forEach { service.onPackageInstalled(androidPackage.packageName, params, it) }
+        userIds.forEach { service.onPackageInstalled(androidPackage.packageName, it) }
+        // TODO: Handle params.
     }
 
     override fun onPackageUninstalled(
diff --git a/services/permission/java/com/android/server/permission/access/permission/UidPermissionPolicy.kt b/services/permission/java/com/android/server/permission/access/permission/UidPermissionPolicy.kt
index 3d6d2ce..a17a317 100644
--- a/services/permission/java/com/android/server/permission/access/permission/UidPermissionPolicy.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/UidPermissionPolicy.kt
@@ -597,11 +597,9 @@
             newState.systemState.privilegedPermissionAllowlistSourcePackageNames) {
             return true
         }
-        if (isInSystemConfigPrivAppPermissions(androidPackage, permission.name)) {
-            return true
-        }
-        if (isInSystemConfigPrivAppDenyPermissions(androidPackage, permission.name)) {
-            return false
+        val allowlistState = getPrivilegedPermissionAllowlistState(androidPackage, permission.name)
+        if (allowlistState != null) {
+            return allowlistState
         }
         // Updated system apps do not need to be allowlisted
         if (packageState.isUpdatedSystemApp) {
@@ -611,66 +609,51 @@
         return !RoSystemProperties.CONTROL_PRIVAPP_PERMISSIONS_ENFORCE
     }
 
-    private fun MutateStateScope.isInSystemConfigPrivAppPermissions(
+    /**
+     * Get the whether a privileged permission is explicitly allowed or denied for a package in the
+     * allowlist, or `null` if it's not in the allowlist.
+     */
+    private fun MutateStateScope.getPrivilegedPermissionAllowlistState(
         androidPackage: AndroidPackage,
         permissionName: String
-    ): Boolean {
+    ): Boolean? {
+        val permissionAllowlist = newState.systemState.permissionAllowlist
         // TODO(b/261913353): STOPSHIP: Add AndroidPackage.apexModuleName. The below is only for
         //  passing compilation but won't actually work.
         //val apexModuleName = androidPackage.apexModuleName
         val apexModuleName = androidPackage.packageName
-        val systemState = newState.systemState
         val packageName = androidPackage.packageName
-        val permissionNames = when {
-            androidPackage.isVendor -> systemState.vendorPrivAppPermissions[packageName]
-            androidPackage.isProduct -> systemState.productPrivAppPermissions[packageName]
-            androidPackage.isSystemExt -> systemState.systemExtPrivAppPermissions[packageName]
+        return when {
+            androidPackage.isVendor -> permissionAllowlist.getVendorPrivilegedAppAllowlistState(
+                packageName, permissionName
+            )
+            androidPackage.isProduct -> permissionAllowlist.getProductPrivilegedAppAllowlistState(
+                packageName, permissionName
+            )
+            androidPackage.isSystemExt ->
+                permissionAllowlist.getSystemExtPrivilegedAppAllowlistState(
+                    packageName, permissionName
+                )
             apexModuleName != null -> {
-                val apexPrivAppPermissions = systemState.apexPrivAppPermissions[apexModuleName]
-                    ?.get(packageName)
-                val privAppPermissions = systemState.privAppPermissions[packageName]
-                when {
-                    apexPrivAppPermissions == null -> privAppPermissions
-                    privAppPermissions == null -> apexPrivAppPermissions
-                    else -> apexPrivAppPermissions + privAppPermissions
+                val nonApexAllowlistState = permissionAllowlist.getPrivilegedAppAllowlistState(
+                    packageName, permissionName
+                )
+                if (nonApexAllowlistState != null) {
+                    // TODO(andreionea): Remove check as soon as all apk-in-apex
+                    // permission allowlists are migrated.
+                    Log.w(
+                        LOG_TAG, "Package $packageName is an APK in APEX but has permission" +
+                            " allowlist on the system image, please bundle the allowlist in the" +
+                            " $apexModuleName APEX instead"
+                    )
                 }
+                val apexAllowlistState = permissionAllowlist.getApexPrivilegedAppAllowlistState(
+                    apexModuleName, packageName, permissionName
+                )
+                apexAllowlistState ?: nonApexAllowlistState
             }
-            else -> systemState.privAppPermissions[packageName]
+            else -> permissionAllowlist.getPrivilegedAppAllowlistState(packageName, permissionName)
         }
-        return permissionNames?.contains(permissionName) == true
-    }
-
-    private fun MutateStateScope.isInSystemConfigPrivAppDenyPermissions(
-        androidPackage: AndroidPackage,
-        permissionName: String
-    ): Boolean {
-        // Different from the previous implementation, which may incorrectly use the APEX package
-        // name, we now use the APEX module name to be consistent with the allowlist.
-        // TODO(b/261913353): STOPSHIP: Add AndroidPackage.apexModuleName. The below is only for
-        //  passing compilation but won't actually work.
-        //val apexModuleName = androidPackage.apexModuleName
-        val apexModuleName = androidPackage.packageName
-        val systemState = newState.systemState
-        val packageName = androidPackage.packageName
-        val permissionNames = when {
-            androidPackage.isVendor -> systemState.vendorPrivAppDenyPermissions[packageName]
-            androidPackage.isProduct -> systemState.productPrivAppDenyPermissions[packageName]
-            androidPackage.isSystemExt -> systemState.systemExtPrivAppDenyPermissions[packageName]
-            // Different from the previous implementation, which ignores the regular priv app
-            // denylist in this case, we now respect it as well to be consistent with the allowlist.
-            apexModuleName != null -> {
-                val apexPrivAppDenyPermissions = systemState
-                    .apexPrivAppDenyPermissions[apexModuleName]?.get(packageName)
-                val privAppDenyPermissions = systemState.privAppDenyPermissions[packageName]
-                when {
-                    apexPrivAppDenyPermissions == null -> privAppDenyPermissions
-                    privAppDenyPermissions == null -> apexPrivAppDenyPermissions
-                    else -> apexPrivAppDenyPermissions + privAppDenyPermissions
-                }
-            }
-            else -> systemState.privAppDenyPermissions[packageName]
-        }
-        return permissionNames?.contains(permissionName) == true
     }
 
     private fun MutateStateScope.anyPackageInAppId(
@@ -811,13 +794,13 @@
             }
             permission.isOem -> {
                 if (androidPackage.isOem) {
-                    val isOemAllowlisted = newState.systemState
-                        .oemPermissions[packageName]?.get(permissionName)
-                    checkNotNull(isOemAllowlisted) {
+                    val allowlistState = newState.systemState.permissionAllowlist
+                        .getOemAppAllowlistState(packageName, permissionName)
+                    checkNotNull(allowlistState) {
                         "OEM permission $permissionName requested by package" +
                             " $packageName must be explicitly declared granted or not"
                     }
-                    return isOemAllowlisted
+                    return allowlistState
                 }
             }
         }
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/AppsFilterImplTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/AppsFilterImplTest.java
similarity index 100%
rename from services/tests/PackageManagerServiceTests/server/src/com/android/server/AppsFilterImplTest.java
rename to services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/AppsFilterImplTest.java
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/BundleUtilsTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/BundleUtilsTest.java
similarity index 100%
rename from services/tests/PackageManagerServiceTests/server/src/com/android/server/BundleUtilsTest.java
rename to services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/BundleUtilsTest.java
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/CompatibilityModeTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/CompatibilityModeTest.java
similarity index 100%
rename from services/tests/PackageManagerServiceTests/server/src/com/android/server/CompatibilityModeTest.java
rename to services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/CompatibilityModeTest.java
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/CrossProfileAppsServiceImplTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/CrossProfileAppsServiceImplTest.java
similarity index 100%
rename from services/tests/PackageManagerServiceTests/server/src/com/android/server/CrossProfileAppsServiceImplTest.java
rename to services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/CrossProfileAppsServiceImplTest.java
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/InstallerTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/InstallerTest.java
similarity index 100%
rename from services/tests/PackageManagerServiceTests/server/src/com/android/server/InstallerTest.java
rename to services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/InstallerTest.java
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/KeySetManagerServiceTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/KeySetManagerServiceTest.java
similarity index 100%
rename from services/tests/PackageManagerServiceTests/server/src/com/android/server/KeySetManagerServiceTest.java
rename to services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/KeySetManagerServiceTest.java
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/KeySetStrings.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/KeySetStrings.java
similarity index 100%
rename from services/tests/PackageManagerServiceTests/server/src/com/android/server/KeySetStrings.java
rename to services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/KeySetStrings.java
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/KeySetUtils.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/KeySetUtils.java
similarity index 100%
rename from services/tests/PackageManagerServiceTests/server/src/com/android/server/KeySetUtils.java
rename to services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/KeySetUtils.java
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/ModuleInfoProviderTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/ModuleInfoProviderTest.java
similarity index 100%
rename from services/tests/PackageManagerServiceTests/server/src/com/android/server/ModuleInfoProviderTest.java
rename to services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/ModuleInfoProviderTest.java
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/PackageInstallerSessionTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageInstallerSessionTest.java
similarity index 100%
rename from services/tests/PackageManagerServiceTests/server/src/com/android/server/PackageInstallerSessionTest.java
rename to services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageInstallerSessionTest.java
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/PackageManagerServiceTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerServiceTest.java
similarity index 100%
rename from services/tests/PackageManagerServiceTests/server/src/com/android/server/PackageManagerServiceTest.java
rename to services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerServiceTest.java
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/PackageManagerSettingsTests.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java
similarity index 100%
rename from services/tests/PackageManagerServiceTests/server/src/com/android/server/PackageManagerSettingsTests.java
rename to services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/PackageManagerTests.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerTests.java
similarity index 100%
rename from services/tests/PackageManagerServiceTests/server/src/com/android/server/PackageManagerTests.java
rename to services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerTests.java
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/PackageParserTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageParserTest.java
similarity index 100%
rename from services/tests/PackageManagerServiceTests/server/src/com/android/server/PackageParserTest.java
rename to services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageParserTest.java
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/PackageSignaturesTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageSignaturesTest.java
similarity index 100%
rename from services/tests/PackageManagerServiceTests/server/src/com/android/server/PackageSignaturesTest.java
rename to services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageSignaturesTest.java
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/PackageUserStateTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageUserStateTest.java
similarity index 100%
rename from services/tests/PackageManagerServiceTests/server/src/com/android/server/PackageUserStateTest.java
rename to services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageUserStateTest.java
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/PackageVerificationStateTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageVerificationStateTest.java
similarity index 100%
rename from services/tests/PackageManagerServiceTests/server/src/com/android/server/PackageVerificationStateTest.java
rename to services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageVerificationStateTest.java
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/ParallelPackageParserTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/ParallelPackageParserTest.java
similarity index 100%
rename from services/tests/PackageManagerServiceTests/server/src/com/android/server/ParallelPackageParserTest.java
rename to services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/ParallelPackageParserTest.java
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/PreferredComponentTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PreferredComponentTest.java
similarity index 100%
rename from services/tests/PackageManagerServiceTests/server/src/com/android/server/PreferredComponentTest.java
rename to services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PreferredComponentTest.java
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/RestrictionsSetTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/RestrictionsSetTest.java
similarity index 100%
rename from services/tests/PackageManagerServiceTests/server/src/com/android/server/RestrictionsSetTest.java
rename to services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/RestrictionsSetTest.java
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/ScanRequestBuilder.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/ScanRequestBuilder.java
similarity index 100%
rename from services/tests/PackageManagerServiceTests/server/src/com/android/server/ScanRequestBuilder.java
rename to services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/ScanRequestBuilder.java
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/ScanTests.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/ScanTests.java
similarity index 100%
rename from services/tests/PackageManagerServiceTests/server/src/com/android/server/ScanTests.java
rename to services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/ScanTests.java
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/UserDataPreparerTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/UserDataPreparerTest.java
similarity index 100%
rename from services/tests/PackageManagerServiceTests/server/src/com/android/server/UserDataPreparerTest.java
rename to services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/UserDataPreparerTest.java
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/WatchedIntentHandlingTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/WatchedIntentHandlingTest.java
similarity index 100%
rename from services/tests/PackageManagerServiceTests/server/src/com/android/server/WatchedIntentHandlingTest.java
rename to services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/WatchedIntentHandlingTest.java
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/parsing/PackageParserLegacyCoreTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java
similarity index 100%
rename from services/tests/PackageManagerServiceTests/server/src/com/android/server/parsing/PackageParserLegacyCoreTest.java
rename to services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/parsing/PackageParsingDeferErrorTest.kt b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/PackageParsingDeferErrorTest.kt
similarity index 100%
rename from services/tests/PackageManagerServiceTests/server/src/com/android/server/parsing/PackageParsingDeferErrorTest.kt
rename to services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/PackageParsingDeferErrorTest.kt
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/parsing/SystemPartitionParseTest.kt b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/SystemPartitionParseTest.kt
similarity index 100%
rename from services/tests/PackageManagerServiceTests/server/src/com/android/server/parsing/SystemPartitionParseTest.kt
rename to services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/SystemPartitionParseTest.kt
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/parsing/library/AndroidHidlUpdaterTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/AndroidHidlUpdaterTest.java
similarity index 100%
rename from services/tests/PackageManagerServiceTests/server/src/com/android/server/parsing/library/AndroidHidlUpdaterTest.java
rename to services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/AndroidHidlUpdaterTest.java
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/parsing/library/AndroidNetIpSecIkeUpdaterTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/AndroidNetIpSecIkeUpdaterTest.java
similarity index 100%
rename from services/tests/PackageManagerServiceTests/server/src/com/android/server/parsing/library/AndroidNetIpSecIkeUpdaterTest.java
rename to services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/AndroidNetIpSecIkeUpdaterTest.java
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/parsing/library/AndroidTestBaseUpdaterTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/AndroidTestBaseUpdaterTest.java
similarity index 100%
rename from services/tests/PackageManagerServiceTests/server/src/com/android/server/parsing/library/AndroidTestBaseUpdaterTest.java
rename to services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/AndroidTestBaseUpdaterTest.java
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/parsing/library/AndroidTestRunnerSplitUpdaterTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/AndroidTestRunnerSplitUpdaterTest.java
similarity index 100%
rename from services/tests/PackageManagerServiceTests/server/src/com/android/server/parsing/library/AndroidTestRunnerSplitUpdaterTest.java
rename to services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/AndroidTestRunnerSplitUpdaterTest.java
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/parsing/library/ApexSharedLibraryUpdaterTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/ApexSharedLibraryUpdaterTest.java
similarity index 100%
rename from services/tests/PackageManagerServiceTests/server/src/com/android/server/parsing/library/ApexSharedLibraryUpdaterTest.java
rename to services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/ApexSharedLibraryUpdaterTest.java
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/parsing/library/ComGoogleAndroidMapsUpdaterTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/ComGoogleAndroidMapsUpdaterTest.java
similarity index 100%
rename from services/tests/PackageManagerServiceTests/server/src/com/android/server/parsing/library/ComGoogleAndroidMapsUpdaterTest.java
rename to services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/ComGoogleAndroidMapsUpdaterTest.java
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/parsing/library/OptionalClassRunner.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/OptionalClassRunner.java
similarity index 100%
rename from services/tests/PackageManagerServiceTests/server/src/com/android/server/parsing/library/OptionalClassRunner.java
rename to services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/OptionalClassRunner.java
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/parsing/library/OrgApacheHttpLegacyUpdaterTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/OrgApacheHttpLegacyUpdaterTest.java
similarity index 100%
rename from services/tests/PackageManagerServiceTests/server/src/com/android/server/parsing/library/OrgApacheHttpLegacyUpdaterTest.java
rename to services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/OrgApacheHttpLegacyUpdaterTest.java
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/parsing/library/PackageBackwardCompatibilityTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/PackageBackwardCompatibilityTest.java
similarity index 100%
rename from services/tests/PackageManagerServiceTests/server/src/com/android/server/parsing/library/PackageBackwardCompatibilityTest.java
rename to services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/PackageBackwardCompatibilityTest.java
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/parsing/library/PackageSharedLibraryUpdaterTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/PackageSharedLibraryUpdaterTest.java
similarity index 100%
rename from services/tests/PackageManagerServiceTests/server/src/com/android/server/parsing/library/PackageSharedLibraryUpdaterTest.java
rename to services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/PackageSharedLibraryUpdaterTest.java
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/parsing/library/RemoveUnnecessaryAndroidTestBaseLibraryTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/RemoveUnnecessaryAndroidTestBaseLibraryTest.java
similarity index 100%
rename from services/tests/PackageManagerServiceTests/server/src/com/android/server/parsing/library/RemoveUnnecessaryAndroidTestBaseLibraryTest.java
rename to services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/RemoveUnnecessaryAndroidTestBaseLibraryTest.java
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/parsing/library/RemoveUnnecessaryOrgApacheHttpLegacyLibraryTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/RemoveUnnecessaryOrgApacheHttpLegacyLibraryTest.java
similarity index 100%
rename from services/tests/PackageManagerServiceTests/server/src/com/android/server/parsing/library/RemoveUnnecessaryOrgApacheHttpLegacyLibraryTest.java
rename to services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/RemoveUnnecessaryOrgApacheHttpLegacyLibraryTest.java
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/utils/OWNERS b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/utils/OWNERS
similarity index 100%
rename from services/tests/PackageManagerServiceTests/server/src/com/android/server/utils/OWNERS
rename to services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/utils/OWNERS
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/utils/WatchableTester.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/utils/WatchableTester.java
similarity index 100%
rename from services/tests/PackageManagerServiceTests/server/src/com/android/server/utils/WatchableTester.java
rename to services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/utils/WatchableTester.java
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/utils/WatcherTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/utils/WatcherTest.java
similarity index 100%
rename from services/tests/PackageManagerServiceTests/server/src/com/android/server/utils/WatcherTest.java
rename to services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/utils/WatcherTest.java
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/JobConcurrencyManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/JobConcurrencyManagerTest.java
index a9dc4af..480a4f3 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/JobConcurrencyManagerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/JobConcurrencyManagerTest.java
@@ -217,7 +217,7 @@
         assertEquals(0, preferredUidOnly.size());
         assertEquals(0, stoppable.size());
         assertEquals(0, assignmentInfo.minPreferredUidOnlyWaitingTimeMs);
-        assertEquals(0, assignmentInfo.numRunningTopEj);
+        assertEquals(0, assignmentInfo.numRunningImmediacyPrivileged);
     }
 
     @Test
@@ -239,7 +239,7 @@
         assertEquals(0, preferredUidOnly.size());
         assertEquals(0, stoppable.size());
         assertEquals(0, assignmentInfo.minPreferredUidOnlyWaitingTimeMs);
-        assertEquals(0, assignmentInfo.numRunningTopEj);
+        assertEquals(0, assignmentInfo.numRunningImmediacyPrivileged);
     }
 
     @Test
@@ -265,15 +265,14 @@
         assertEquals(JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT, preferredUidOnly.size());
         assertEquals(0, stoppable.size());
         assertEquals(0, assignmentInfo.minPreferredUidOnlyWaitingTimeMs);
-        assertEquals(0, assignmentInfo.numRunningTopEj);
+        assertEquals(0, assignmentInfo.numRunningImmediacyPrivileged);
     }
 
     @Test
-    public void testPrepareForAssignmentDetermination_onlyRunningTopEjs() {
+    public void testPrepareForAssignmentDetermination_onlyStartedWithImmediacyPrivilege() {
         for (int i = 0; i < JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT; ++i) {
             JobStatus job = createJob(mDefaultUserId * UserHandle.PER_USER_RANGE + i);
-            job.startedAsExpeditedJob = true;
-            job.lastEvaluatedBias = JobInfo.BIAS_TOP_APP;
+            job.startedWithImmediacyPrivilege = true;
             mJobConcurrencyManager.addRunningJobForTesting(job);
         }
 
@@ -294,7 +293,7 @@
         assertEquals(JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT / 2, stoppable.size());
         assertEquals(0, assignmentInfo.minPreferredUidOnlyWaitingTimeMs);
         assertEquals(JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT,
-                assignmentInfo.numRunningTopEj);
+                assignmentInfo.numRunningImmediacyPrivileged);
     }
 
     @Test
@@ -500,6 +499,38 @@
     }
 
     @Test
+    public void testHasImmediacyPrivilege() {
+        JobStatus job = createJob(mDefaultUserId * UserHandle.PER_USER_RANGE, 0);
+        spyOn(job);
+        assertFalse(mJobConcurrencyManager.hasImmediacyPrivilegeLocked(job));
+
+        doReturn(false).when(job).shouldTreatAsExpeditedJob();
+        doReturn(false).when(job).shouldTreatAsUserInitiated();
+        job.lastEvaluatedBias = JobInfo.BIAS_TOP_APP;
+        assertFalse(mJobConcurrencyManager.hasImmediacyPrivilegeLocked(job));
+
+        doReturn(true).when(job).shouldTreatAsExpeditedJob();
+        doReturn(false).when(job).shouldTreatAsUserInitiated();
+        job.lastEvaluatedBias = JobInfo.BIAS_DEFAULT;
+        assertFalse(mJobConcurrencyManager.hasImmediacyPrivilegeLocked(job));
+
+        doReturn(false).when(job).shouldTreatAsExpeditedJob();
+        doReturn(true).when(job).shouldTreatAsUserInitiated();
+        job.lastEvaluatedBias = JobInfo.BIAS_DEFAULT;
+        assertFalse(mJobConcurrencyManager.hasImmediacyPrivilegeLocked(job));
+
+        doReturn(false).when(job).shouldTreatAsExpeditedJob();
+        doReturn(true).when(job).shouldTreatAsUserInitiated();
+        job.lastEvaluatedBias = JobInfo.BIAS_TOP_APP;
+        assertTrue(mJobConcurrencyManager.hasImmediacyPrivilegeLocked(job));
+
+        doReturn(true).when(job).shouldTreatAsExpeditedJob();
+        doReturn(false).when(job).shouldTreatAsUserInitiated();
+        job.lastEvaluatedBias = JobInfo.BIAS_TOP_APP;
+        assertTrue(mJobConcurrencyManager.hasImmediacyPrivilegeLocked(job));
+    }
+
+    @Test
     public void testIsPkgConcurrencyLimited_top() {
         final JobStatus topJob = createJob(mDefaultUserId * UserHandle.PER_USER_RANGE, 0);
         topJob.lastEvaluatedBias = JobInfo.BIAS_TOP_APP;
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
index 62ef523..2d16409 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
@@ -97,6 +97,8 @@
 import com.android.server.input.InputManagerInternal;
 import com.android.server.sensors.SensorManagerInternal;
 
+import com.google.android.collect.Sets;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -108,6 +110,7 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
+import java.util.Set;
 import java.util.function.Consumer;
 
 @Presubmit
@@ -124,12 +127,13 @@
     private static final String GOOGLE_MAPS_PACKAGE_NAME = "com.google.android.apps.maps";
     private static final String DEVICE_NAME = "device name";
     private static final int DISPLAY_ID = 2;
+    private static final int DISPLAY_ID_2 = 3;
+    private static final int DEVICE_OWNER_UID_1 = 50;
+    private static final int DEVICE_OWNER_UID_2 = 51;
     private static final int UID_1 = 0;
     private static final int UID_2 = 10;
     private static final int UID_3 = 10000;
     private static final int UID_4 = 10001;
-    private static final int ASSOCIATION_ID_1 = 1;
-    private static final int ASSOCIATION_ID_2 = 2;
     private static final int PRODUCT_ID = 10;
     private static final int VENDOR_ID = 5;
     private static final String UNIQUE_ID = "uniqueid";
@@ -301,22 +305,13 @@
                 mContext.getSystemService(WindowManager.class), threadVerifier);
         mSensorController = new SensorController(new Object(), VIRTUAL_DEVICE_ID);
 
-        mAssociationInfo = new AssociationInfo(1, 0, null,
+        mAssociationInfo = new AssociationInfo(/* associationId= */ 1, 0, null,
                 MacAddress.BROADCAST_ADDRESS, "", null, null, true, false, false, 0, 0);
 
         mVdms = new VirtualDeviceManagerService(mContext);
         mLocalService = mVdms.getLocalServiceInstance();
         mVdm = mVdms.new VirtualDeviceManagerImpl();
-
-        VirtualDeviceParams params = new VirtualDeviceParams
-                .Builder()
-                .setBlockedActivities(getBlockedActivities())
-                .build();
-        mDeviceImpl = new VirtualDeviceImpl(mContext,
-                mAssociationInfo, new Binder(), /* ownerUid= */ 0, VIRTUAL_DEVICE_ID,
-                mInputController, mSensorController, (int associationId) -> {},
-                mPendingTrampolineCallback, mActivityListener, mRunningAppsChangedCallback, params);
-        mVdms.addVirtualDevice(mDeviceImpl);
+        mDeviceImpl = createVirtualDevice(VIRTUAL_DEVICE_ID, DEVICE_OWNER_UID_1);
     }
 
     @Test
@@ -380,7 +375,8 @@
                 .build();
         mDeviceImpl = new VirtualDeviceImpl(mContext,
                 mAssociationInfo, new Binder(), /* ownerUid */ 0, VIRTUAL_DEVICE_ID,
-                mInputController, mSensorController, (int associationId) -> {},
+                mInputController, mSensorController,
+                /* onDeviceCloseListener= */ (int deviceId) -> {},
                 mPendingTrampolineCallback, mActivityListener, mRunningAppsChangedCallback, params);
         mVdms.addVirtualDevice(mDeviceImpl);
 
@@ -389,6 +385,106 @@
     }
 
     @Test
+    public void getDeviceOwnerUid_oneDevice_returnsCorrectId() {
+        int ownerUid = mLocalService.getDeviceOwnerUid(mDeviceImpl.getDeviceId());
+        assertThat(ownerUid).isEqualTo(mDeviceImpl.getOwnerUid());
+    }
+
+    @Test
+    public void getDeviceOwnerUid_twoDevices_returnsCorrectId() {
+        int firstDeviceId = mDeviceImpl.getDeviceId();
+        int secondDeviceId = VIRTUAL_DEVICE_ID + 1;
+
+        createVirtualDevice(secondDeviceId, DEVICE_OWNER_UID_2);
+
+        int secondDeviceOwner = mLocalService.getDeviceOwnerUid(secondDeviceId);
+        assertThat(secondDeviceOwner).isEqualTo(DEVICE_OWNER_UID_2);
+
+        int firstDeviceOwner = mLocalService.getDeviceOwnerUid(firstDeviceId);
+        assertThat(firstDeviceOwner).isEqualTo(DEVICE_OWNER_UID_1);
+    }
+
+    @Test
+    public void getDeviceOwnerUid_nonExistentDevice_returnsInvalidUid() {
+        int nonExistentDeviceId = DEVICE_ID_DEFAULT;
+        int ownerUid = mLocalService.getDeviceOwnerUid(nonExistentDeviceId);
+        assertThat(ownerUid).isEqualTo(Process.INVALID_UID);
+    }
+
+    @Test
+    public void getDeviceIdsForUid_noRunningApps_returnsNull() {
+        Set<Integer> deviceIds = mLocalService.getDeviceIdsForUid(UID_1);
+        assertThat(deviceIds).isEmpty();
+    }
+
+    @Test
+    public void getDeviceIdsForUid_differentUidOnDevice_returnsNull() {
+        GenericWindowPolicyController gwpc =
+                mDeviceImpl.createWindowPolicyController(new ArrayList<>());
+        mDeviceImpl.onVirtualDisplayCreatedLocked(gwpc, DISPLAY_ID);
+        gwpc.onRunningAppsChanged(Sets.newArraySet(UID_2));
+
+        Set<Integer> deviceIds = mLocalService.getDeviceIdsForUid(UID_1);
+        assertThat(deviceIds).isEmpty();
+    }
+
+    @Test
+    public void getDeviceIdsForUid_oneUidOnDevice_returnsCorrectId() {
+        GenericWindowPolicyController gwpc =
+                mDeviceImpl.createWindowPolicyController(new ArrayList<>());
+        mDeviceImpl.onVirtualDisplayCreatedLocked(gwpc, DISPLAY_ID);
+        gwpc.onRunningAppsChanged(Sets.newArraySet(UID_1));
+
+        Set<Integer> deviceIds = mLocalService.getDeviceIdsForUid(UID_1);
+        assertThat(deviceIds).containsExactly(mDeviceImpl.getDeviceId());
+    }
+
+    @Test
+    public void getDeviceIdsForUid_twoUidsOnDevice_returnsCorrectId() {
+        GenericWindowPolicyController gwpc =
+                mDeviceImpl.createWindowPolicyController(new ArrayList<>());
+        mDeviceImpl.onVirtualDisplayCreatedLocked(gwpc, DISPLAY_ID);
+        gwpc.onRunningAppsChanged(Sets.newArraySet(UID_1, UID_2));
+
+        Set<Integer> deviceIds = mLocalService.getDeviceIdsForUid(UID_1);
+        assertThat(deviceIds).containsExactly(mDeviceImpl.getDeviceId());
+    }
+
+    @Test
+    public void getDeviceIdsForUid_twoDevicesUidOnOne_returnsCorrectId() {
+        int secondDeviceId = VIRTUAL_DEVICE_ID + 1;
+
+        VirtualDeviceImpl secondDevice = createVirtualDevice(secondDeviceId, DEVICE_OWNER_UID_2);
+
+        GenericWindowPolicyController gwpc =
+                secondDevice.createWindowPolicyController(new ArrayList<>());
+        secondDevice.onVirtualDisplayCreatedLocked(gwpc, DISPLAY_ID_2);
+        gwpc.onRunningAppsChanged(Sets.newArraySet(UID_1));
+
+        Set<Integer> deviceIds = mLocalService.getDeviceIdsForUid(UID_1);
+        assertThat(deviceIds).containsExactly(secondDevice.getDeviceId());
+    }
+
+    @Test
+    public void getDeviceIdsForUid_twoDevicesUidOnBoth_returnsCorrectId() {
+        int secondDeviceId = VIRTUAL_DEVICE_ID + 1;
+
+        VirtualDeviceImpl secondDevice = createVirtualDevice(secondDeviceId, DEVICE_OWNER_UID_2);
+        GenericWindowPolicyController gwpc1 =
+                mDeviceImpl.createWindowPolicyController(new ArrayList<>());
+        GenericWindowPolicyController gwpc2 =
+                mDeviceImpl.createWindowPolicyController(new ArrayList<>());
+        mDeviceImpl.onVirtualDisplayCreatedLocked(gwpc1, DISPLAY_ID);
+        secondDevice.onVirtualDisplayCreatedLocked(gwpc2, DISPLAY_ID_2);
+        gwpc1.onRunningAppsChanged(Sets.newArraySet(UID_1));
+        gwpc2.onRunningAppsChanged(Sets.newArraySet(UID_1, UID_2));
+
+        Set<Integer> deviceIds = mLocalService.getDeviceIdsForUid(UID_1);
+        assertThat(deviceIds).containsExactly(
+                mDeviceImpl.getDeviceId(), secondDevice.getDeviceId());
+    }
+
+    @Test
     public void onVirtualDisplayRemovedLocked_doesNotThrowException() {
         mDeviceImpl.onVirtualDisplayCreatedLocked(
                 mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID);
@@ -423,7 +519,7 @@
         ArraySet<Integer> uids = new ArraySet<>(Arrays.asList(UID_1, UID_2));
         mLocalService.registerAppsOnVirtualDeviceListener(mAppsOnVirtualDeviceListener);
 
-        mVdms.notifyRunningAppsChanged(ASSOCIATION_ID_1, uids);
+        mVdms.notifyRunningAppsChanged(mDeviceImpl.getDeviceId(), uids);
         TestableLooper.get(this).processAllMessages();
 
         verify(mAppsOnVirtualDeviceListener).onAppsOnAnyVirtualDeviceChanged(uids);
@@ -436,13 +532,13 @@
         mLocalService.registerAppsOnVirtualDeviceListener(mAppsOnVirtualDeviceListener);
 
         // Notifies that the running apps on the first virtual device has changed.
-        mVdms.notifyRunningAppsChanged(ASSOCIATION_ID_1, uidsOnDevice1);
+        mVdms.notifyRunningAppsChanged(mDeviceImpl.getDeviceId(), uidsOnDevice1);
         TestableLooper.get(this).processAllMessages();
         verify(mAppsOnVirtualDeviceListener).onAppsOnAnyVirtualDeviceChanged(
                 new ArraySet<>(Arrays.asList(UID_1, UID_2)));
 
         // Notifies that the running apps on the second virtual device has changed.
-        mVdms.notifyRunningAppsChanged(ASSOCIATION_ID_2, uidsOnDevice2);
+        mVdms.notifyRunningAppsChanged(mDeviceImpl.getDeviceId() + 1, uidsOnDevice2);
         TestableLooper.get(this).processAllMessages();
         // The union of the apps running on both virtual devices are sent to the listeners.
         verify(mAppsOnVirtualDeviceListener).onAppsOnAnyVirtualDeviceChanged(
@@ -450,7 +546,7 @@
 
         // Notifies that the running apps on the first virtual device has changed again.
         uidsOnDevice1.remove(UID_2);
-        mVdms.notifyRunningAppsChanged(ASSOCIATION_ID_1, uidsOnDevice1);
+        mVdms.notifyRunningAppsChanged(mDeviceImpl.getDeviceId(), uidsOnDevice1);
         mLocalService.onAppsOnVirtualDeviceChanged();
         TestableLooper.get(this).processAllMessages();
         // The union of the apps running on both virtual devices are sent to the listeners.
@@ -459,7 +555,7 @@
 
         // Notifies that the running apps on the first virtual device has changed but with the same
         // set of UIDs.
-        mVdms.notifyRunningAppsChanged(ASSOCIATION_ID_1, uidsOnDevice1);
+        mVdms.notifyRunningAppsChanged(mDeviceImpl.getDeviceId(), uidsOnDevice1);
         mLocalService.onAppsOnVirtualDeviceChanged();
         TestableLooper.get(this).processAllMessages();
         // Listeners should not be notified.
@@ -1160,7 +1256,6 @@
                 /* targetDisplayCategory= */ null);
         verify(mContext).startActivityAsUser(argThat(intent ->
                 intent.filterEquals(blockedAppIntent)), any(), any());
-
     }
 
     @Test
@@ -1185,4 +1280,18 @@
         verify(mContext).startActivityAsUser(argThat(intent ->
                 intent.filterEquals(blockedAppIntent)), any(), any());
     }
+
+    private VirtualDeviceImpl createVirtualDevice(int virtualDeviceId, int ownerUid) {
+        VirtualDeviceParams params = new VirtualDeviceParams
+                .Builder()
+                .setBlockedActivities(getBlockedActivities())
+                .build();
+        VirtualDeviceImpl virtualDeviceImpl = new VirtualDeviceImpl(mContext,
+                mAssociationInfo, new Binder(), ownerUid, virtualDeviceId,
+                mInputController, mSensorController,
+                /* onDeviceCloseListener= */ (int deviceId) -> {},
+                mPendingTrampolineCallback, mActivityListener, mRunningAppsChangedCallback, params);
+        mVdms.addVirtualDevice(virtualDeviceImpl);
+        return virtualDeviceImpl;
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java
index c7caa43..c6a0b0f 100644
--- a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java
@@ -18,10 +18,14 @@
 
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.Display.DEFAULT_DISPLAY_GROUP;
+import static android.view.Display.TYPE_INTERNAL;
+import static android.view.Display.TYPE_VIRTUAL;
 
+import static com.android.server.display.DeviceStateToLayoutMap.STATE_DEFAULT;
 import static com.android.server.display.DisplayAdapter.DISPLAY_DEVICE_EVENT_ADDED;
 import static com.android.server.display.DisplayAdapter.DISPLAY_DEVICE_EVENT_CHANGED;
 import static com.android.server.display.DisplayAdapter.DISPLAY_DEVICE_EVENT_REMOVED;
+import static com.android.server.display.DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY;
 import static com.android.server.display.LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_ADDED;
 import static com.android.server.display.LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_REMOVED;
 
@@ -69,7 +73,6 @@
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.util.Arrays;
-import java.util.Set;
 
 @SmallTest
 @RunWith(AndroidJUnit4.class)
@@ -155,8 +158,8 @@
 
     @Test
     public void testDisplayDeviceAddAndRemove_Internal() {
-        DisplayDevice device = createDisplayDevice(Display.TYPE_INTERNAL, 600, 800,
-                DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
+        DisplayDevice device = createDisplayDevice(TYPE_INTERNAL, 600, 800,
+                FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
 
         // add
         LogicalDisplay displayAdded = add(device);
@@ -177,7 +180,7 @@
         testDisplayDeviceAddAndRemove_NonInternal(Display.TYPE_EXTERNAL);
         testDisplayDeviceAddAndRemove_NonInternal(Display.TYPE_WIFI);
         testDisplayDeviceAddAndRemove_NonInternal(Display.TYPE_OVERLAY);
-        testDisplayDeviceAddAndRemove_NonInternal(Display.TYPE_VIRTUAL);
+        testDisplayDeviceAddAndRemove_NonInternal(TYPE_VIRTUAL);
         testDisplayDeviceAddAndRemove_NonInternal(Display.TYPE_UNKNOWN);
 
         // Call the internal test again, just to verify that adding non-internal displays
@@ -187,9 +190,9 @@
 
     @Test
     public void testDisplayDeviceAdd_TwoInternalOneDefault() {
-        DisplayDevice device1 = createDisplayDevice(Display.TYPE_INTERNAL, 600, 800, 0);
-        DisplayDevice device2 = createDisplayDevice(Display.TYPE_INTERNAL, 600, 800,
-                DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
+        DisplayDevice device1 = createDisplayDevice(TYPE_INTERNAL, 600, 800, 0);
+        DisplayDevice device2 = createDisplayDevice(TYPE_INTERNAL, 600, 800,
+                FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
 
         LogicalDisplay display1 = add(device1);
         assertEquals(info(display1).address, info(device1).address);
@@ -202,10 +205,10 @@
 
     @Test
     public void testDisplayDeviceAdd_TwoInternalBothDefault() {
-        DisplayDevice device1 = createDisplayDevice(Display.TYPE_INTERNAL, 600, 800,
-                DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
-        DisplayDevice device2 = createDisplayDevice(Display.TYPE_INTERNAL, 600, 800,
-                DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
+        DisplayDevice device1 = createDisplayDevice(TYPE_INTERNAL, 600, 800,
+                FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
+        DisplayDevice device2 = createDisplayDevice(TYPE_INTERNAL, 600, 800,
+                FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
 
         LogicalDisplay display1 = add(device1);
         assertEquals(info(display1).address, info(device1).address);
@@ -220,7 +223,7 @@
     @Test
     public void testDisplayDeviceAddAndRemove_OneExternalDefault() {
         DisplayDevice device = createDisplayDevice(Display.TYPE_EXTERNAL, 600, 800,
-                DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
+                FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
 
         // add
         LogicalDisplay displayAdded = add(device);
@@ -238,10 +241,10 @@
 
     @Test
     public void testDisplayDeviceAddAndRemove_SwitchDefault() {
-        DisplayDevice device1 = createDisplayDevice(Display.TYPE_INTERNAL, 600, 800,
-                DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
-        DisplayDevice device2 = createDisplayDevice(Display.TYPE_INTERNAL, 600, 800,
-                DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
+        DisplayDevice device1 = createDisplayDevice(TYPE_INTERNAL, 600, 800,
+                FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
+        DisplayDevice device2 = createDisplayDevice(TYPE_INTERNAL, 600, 800,
+                FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
 
         LogicalDisplay display1 = add(device1);
         assertEquals(info(display1).address, info(device1).address);
@@ -267,10 +270,10 @@
 
     @Test
     public void testGetDisplayIdsLocked() {
-        add(createDisplayDevice(Display.TYPE_INTERNAL, 600, 800,
-                DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY));
+        add(createDisplayDevice(TYPE_INTERNAL, 600, 800,
+                FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY));
         add(createDisplayDevice(Display.TYPE_EXTERNAL, 600, 800, 0));
-        add(createDisplayDevice(Display.TYPE_VIRTUAL, 600, 800, 0));
+        add(createDisplayDevice(TYPE_VIRTUAL, 600, 800, 0));
 
         int [] ids = mLogicalDisplayMapper.getDisplayIdsLocked(Process.SYSTEM_UID,
                 /* includeDisabled= */ true);
@@ -280,71 +283,98 @@
     }
 
     @Test
-    public void testGetDisplayInfoForStateLocked_oneDisplayGroup_internalType() {
-        add(createDisplayDevice(Display.TYPE_INTERNAL, 600, 800,
-                DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY));
-        add(createDisplayDevice(Display.TYPE_INTERNAL, 200, 800,
-                DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY));
-        add(createDisplayDevice(Display.TYPE_INTERNAL, 700, 800,
-                DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY));
+    public void testGetDisplayInfoForStateLocked_defaultLayout() {
+        final DisplayDevice device1 = createDisplayDevice(TYPE_INTERNAL, 600, 800,
+                FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
+        final DisplayDevice device2 = createDisplayDevice(TYPE_INTERNAL, 200, 800,
+                FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
 
-        Set<DisplayInfo> displayInfos = mLogicalDisplayMapper.getDisplayInfoForStateLocked(
-                DeviceStateToLayoutMap.STATE_DEFAULT, DEFAULT_DISPLAY, DEFAULT_DISPLAY_GROUP);
-        assertThat(displayInfos.size()).isEqualTo(1);
-        for (DisplayInfo displayInfo : displayInfos) {
-            assertThat(displayInfo.displayId).isEqualTo(DEFAULT_DISPLAY);
-            assertThat(displayInfo.displayGroupId).isEqualTo(DEFAULT_DISPLAY_GROUP);
-            assertThat(displayInfo.logicalWidth).isEqualTo(600);
-            assertThat(displayInfo.logicalHeight).isEqualTo(800);
-        }
+        add(device1);
+        add(device2);
+
+        Layout layout1 = new Layout();
+        layout1.createDisplayLocked(info(device1).address, /* isDefault= */ true,
+                /* isEnabled= */ true, mIdProducer);
+        layout1.createDisplayLocked(info(device2).address, /* isDefault= */ false,
+                /* isEnabled= */ true, mIdProducer);
+        when(mDeviceStateToLayoutMapSpy.get(STATE_DEFAULT)).thenReturn(layout1);
+        assertThat(layout1.size()).isEqualTo(2);
+        final int logicalId2 = layout1.getByAddress(info(device2).address).getLogicalDisplayId();
+
+        final DisplayInfo displayInfoDefault = mLogicalDisplayMapper.getDisplayInfoForStateLocked(
+                STATE_DEFAULT, DEFAULT_DISPLAY);
+        assertThat(displayInfoDefault.displayId).isEqualTo(DEFAULT_DISPLAY);
+        assertThat(displayInfoDefault.logicalWidth).isEqualTo(width(device1));
+        assertThat(displayInfoDefault.logicalHeight).isEqualTo(height(device1));
+
+        final DisplayInfo displayInfoOther = mLogicalDisplayMapper.getDisplayInfoForStateLocked(
+                STATE_DEFAULT, logicalId2);
+        assertThat(displayInfoOther).isNotNull();
+        assertThat(displayInfoOther.displayId).isEqualTo(logicalId2);
+        assertThat(displayInfoOther.logicalWidth).isEqualTo(width(device2));
+        assertThat(displayInfoOther.logicalHeight).isEqualTo(height(device2));
     }
 
     @Test
-    public void testGetDisplayInfoForStateLocked_oneDisplayGroup_differentTypes() {
-        add(createDisplayDevice(Display.TYPE_INTERNAL, 600, 800,
-                DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY));
-        add(createDisplayDevice(Display.TYPE_INTERNAL, 200, 800,
-                DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY));
-        add(createDisplayDevice(Display.TYPE_EXTERNAL, 700, 800,
-                DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY));
+    public void testGetDisplayInfoForStateLocked_multipleLayouts() {
+        final DisplayDevice device1 = createDisplayDevice(TYPE_INTERNAL, 600, 800,
+                FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
+        final DisplayDevice device2 = createDisplayDevice(TYPE_INTERNAL, 200, 800,
+                FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
+        final DisplayDevice device3 = createDisplayDevice(TYPE_VIRTUAL, 700, 800,
+                DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP);
 
-        Set<DisplayInfo> displayInfos = mLogicalDisplayMapper.getDisplayInfoForStateLocked(
-                DeviceStateToLayoutMap.STATE_DEFAULT, DEFAULT_DISPLAY, DEFAULT_DISPLAY_GROUP);
-        assertThat(displayInfos.size()).isEqualTo(1);
-        for (DisplayInfo displayInfo : displayInfos) {
-            assertThat(displayInfo.displayId).isEqualTo(DEFAULT_DISPLAY);
-            assertThat(displayInfo.displayGroupId).isEqualTo(DEFAULT_DISPLAY_GROUP);
-            assertThat(displayInfo.logicalWidth).isEqualTo(600);
-            assertThat(displayInfo.logicalHeight).isEqualTo(800);
-        }
-    }
+        add(device1);
+        add(device2);
+        add(device3);
 
-    @Test
-    public void testGetDisplayInfoForStateLocked_multipleDisplayGroups_defaultGroup() {
-        add(createDisplayDevice(Display.TYPE_INTERNAL, 600, 800,
-                DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY));
-        add(createDisplayDevice(Display.TYPE_INTERNAL, 200, 800,
-                DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY));
-        add(createDisplayDevice(Display.TYPE_VIRTUAL, 700, 800,
-                DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP));
+        Layout layout1 = new Layout();
+        layout1.createDisplayLocked(info(device1).address,
+                /* isDefault= */ true, /* isEnabled= */ true, mIdProducer);
+        when(mDeviceStateToLayoutMapSpy.get(STATE_DEFAULT)).thenReturn(layout1);
 
-        Set<DisplayInfo> displayInfos = mLogicalDisplayMapper.getDisplayInfoForStateLocked(
-                DeviceStateToLayoutMap.STATE_DEFAULT, DEFAULT_DISPLAY, DEFAULT_DISPLAY_GROUP);
-        assertThat(displayInfos.size()).isEqualTo(1);
-        for (DisplayInfo displayInfo : displayInfos) {
-            assertThat(displayInfo.displayId).isEqualTo(DEFAULT_DISPLAY);
-            assertThat(displayInfo.displayGroupId).isEqualTo(DEFAULT_DISPLAY_GROUP);
-            assertThat(displayInfo.logicalWidth).isEqualTo(600);
-            assertThat(displayInfo.logicalHeight).isEqualTo(800);
-        }
+        final int layoutState2 = 2;
+        Layout layout2 = new Layout();
+        layout2.createDisplayLocked(info(device2).address,
+                /* isDefault= */ false, /* isEnabled= */ true, mIdProducer);
+        // Device3 is the default display.
+        layout2.createDisplayLocked(info(device3).address,
+                /* isDefault= */ true, /* isEnabled= */ true, mIdProducer);
+        when(mDeviceStateToLayoutMapSpy.get(layoutState2)).thenReturn(layout2);
+        assertThat(layout2.size()).isEqualTo(2);
+        final int logicalId2 = layout2.getByAddress(info(device2).address).getLogicalDisplayId();
+
+        // Default layout.
+        final DisplayInfo displayInfoLayout1Default =
+                mLogicalDisplayMapper.getDisplayInfoForStateLocked(
+                        STATE_DEFAULT, DEFAULT_DISPLAY);
+        assertThat(displayInfoLayout1Default.displayId).isEqualTo(DEFAULT_DISPLAY);
+        assertThat(displayInfoLayout1Default.logicalWidth).isEqualTo(width(device1));
+        assertThat(displayInfoLayout1Default.logicalHeight).isEqualTo(height(device1));
+
+        // Second layout, where device3 is the default display.
+        final DisplayInfo displayInfoLayout2Default =
+                mLogicalDisplayMapper.getDisplayInfoForStateLocked(
+                        layoutState2, DEFAULT_DISPLAY);
+        assertThat(displayInfoLayout2Default.displayId).isEqualTo(DEFAULT_DISPLAY);
+        assertThat(displayInfoLayout2Default.logicalWidth).isEqualTo(width(device3));
+        assertThat(displayInfoLayout2Default.logicalHeight).isEqualTo(height(device3));
+
+        final DisplayInfo displayInfoLayout2Other =
+                mLogicalDisplayMapper.getDisplayInfoForStateLocked(
+                        layoutState2, logicalId2);
+        assertThat(displayInfoLayout2Other).isNotNull();
+        assertThat(displayInfoLayout2Other.displayId).isEqualTo(logicalId2);
+        assertThat(displayInfoLayout2Other.logicalWidth).isEqualTo(width(device2));
+        assertThat(displayInfoLayout2Other.logicalHeight).isEqualTo(height(device2));
     }
 
     @Test
     public void testSingleDisplayGroup() {
-        LogicalDisplay display1 = add(createDisplayDevice(Display.TYPE_INTERNAL, 600, 800,
-                DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY));
-        LogicalDisplay display2 = add(createDisplayDevice(Display.TYPE_INTERNAL, 600, 800, 0));
-        LogicalDisplay display3 = add(createDisplayDevice(Display.TYPE_VIRTUAL, 600, 800, 0));
+        LogicalDisplay display1 = add(createDisplayDevice(TYPE_INTERNAL, 600, 800,
+                FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY));
+        LogicalDisplay display2 = add(createDisplayDevice(TYPE_INTERNAL, 600, 800, 0));
+        LogicalDisplay display3 = add(createDisplayDevice(TYPE_VIRTUAL, 600, 800, 0));
 
         assertEquals(DEFAULT_DISPLAY_GROUP,
                 mLogicalDisplayMapper.getDisplayGroupIdFromDisplayIdLocked(id(display1)));
@@ -356,12 +386,12 @@
 
     @Test
     public void testMultipleDisplayGroups() {
-        LogicalDisplay display1 = add(createDisplayDevice(Display.TYPE_INTERNAL, 600, 800,
-                DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY));
-        LogicalDisplay display2 = add(createDisplayDevice(Display.TYPE_INTERNAL, 600, 800, 0));
+        LogicalDisplay display1 = add(createDisplayDevice(TYPE_INTERNAL, 600, 800,
+                FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY));
+        LogicalDisplay display2 = add(createDisplayDevice(TYPE_INTERNAL, 600, 800, 0));
 
 
-        TestDisplayDevice device3 = createDisplayDevice(Display.TYPE_VIRTUAL, 600, 800,
+        TestDisplayDevice device3 = createDisplayDevice(TYPE_VIRTUAL, 600, 800,
                 DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP);
         LogicalDisplay display3 = add(device3);
 
@@ -519,10 +549,10 @@
 
     @Test
     public void testDeviceStateLocked() {
-        DisplayDevice device1 = createDisplayDevice(Display.TYPE_INTERNAL, 600, 800,
-                DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
-        DisplayDevice device2 = createDisplayDevice(Display.TYPE_INTERNAL, 600, 800,
-                DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
+        DisplayDevice device1 = createDisplayDevice(TYPE_INTERNAL, 600, 800,
+                FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
+        DisplayDevice device2 = createDisplayDevice(TYPE_INTERNAL, 600, 800,
+                FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
 
         Layout layout = new Layout();
         layout.createDisplayLocked(device1.getDisplayDeviceInfoLocked().address,
@@ -577,14 +607,11 @@
         DisplayAddress displayAddressThree = new TestUtils.TestDisplayAddress();
 
         TestDisplayDevice device1 = createDisplayDevice(displayAddressOne, "one",
-                Display.TYPE_INTERNAL, 600, 800,
-                DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
+                TYPE_INTERNAL, 600, 800, DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
         TestDisplayDevice device2 = createDisplayDevice(displayAddressTwo, "two",
-                Display.TYPE_INTERNAL, 200, 800,
-                DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP);
+                TYPE_INTERNAL, 200, 800, DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP);
         TestDisplayDevice device3 = createDisplayDevice(displayAddressThree, "three",
-                Display.TYPE_INTERNAL, 600, 900,
-                DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP);
+                TYPE_INTERNAL, 600, 900, DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP);
 
         Layout threeDevicesEnabledLayout = new Layout();
         threeDevicesEnabledLayout.createDisplayLocked(
@@ -603,7 +630,7 @@
                 /* isEnabled= */ true,
                 mIdProducer);
 
-        when(mDeviceStateToLayoutMapSpy.get(DeviceStateToLayoutMap.STATE_DEFAULT))
+        when(mDeviceStateToLayoutMapSpy.get(STATE_DEFAULT))
                 .thenReturn(threeDevicesEnabledLayout);
 
         LogicalDisplay display1 = add(device1);
@@ -735,6 +762,14 @@
         return device.getDisplayDeviceInfoLocked();
     }
 
+    private int width(DisplayDevice device) {
+        return info(device).width;
+    }
+
+    private int height(DisplayDevice device) {
+        return info(device).height;
+    }
+
     private DisplayInfo info(LogicalDisplay display) {
         return display.getDisplayInfoLocked();
     }
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java b/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java
index 4668e5d..5ca695b 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java
@@ -85,7 +85,8 @@
     protected static final int SECONDARY_USER_ID = 20;
 
     private static final UserInfo PRIMARY_USER_INFO = new UserInfo(PRIMARY_USER_ID, null, null,
-            UserInfo.FLAG_INITIALIZED | UserInfo.FLAG_ADMIN | UserInfo.FLAG_PRIMARY);
+            UserInfo.FLAG_INITIALIZED | UserInfo.FLAG_ADMIN | UserInfo.FLAG_PRIMARY
+                    | UserInfo.FLAG_MAIN);
     private static final UserInfo SECONDARY_USER_INFO = new UserInfo(SECONDARY_USER_ID, null, null,
             UserInfo.FLAG_INITIALIZED);
 
diff --git a/services/tests/servicestests/src/com/android/server/power/PowerGroupTest.java b/services/tests/servicestests/src/com/android/server/power/PowerGroupTest.java
index b034b0d..fe31b9c 100644
--- a/services/tests/servicestests/src/com/android/server/power/PowerGroupTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/PowerGroupTest.java
@@ -257,7 +257,6 @@
                 .build();
 
         mPowerGroup.updateLocked(/* screenBrightnessOverride= */ BRIGHTNESS,
-                /* autoBrightness = */ false,
                 /* useProximitySensor= */ false,
                 /* boostScreenBrightness= */ false,
                 /* dozeScreenStateOverride= */ Display.STATE_ON,
@@ -273,7 +272,6 @@
                 mPowerGroup.mDisplayPowerRequest;
         assertThat(displayPowerRequest.policy).isEqualTo(POLICY_DIM);
         assertThat(displayPowerRequest.screenBrightnessOverride).isWithin(PRECISION).of(BRIGHTNESS);
-        assertThat(displayPowerRequest.useAutoBrightness).isEqualTo(false);
         assertThat(displayPowerRequest.useProximitySensor).isEqualTo(false);
         assertThat(displayPowerRequest.boostScreenBrightness).isEqualTo(false);
         assertThat(displayPowerRequest.dozeScreenState).isEqualTo(Display.STATE_UNKNOWN);
@@ -297,7 +295,6 @@
         mPowerGroup.setWakeLockSummaryLocked(WAKE_LOCK_DOZE);
 
         mPowerGroup.updateLocked(/* screenBrightnessOverride= */ BRIGHTNESS,
-                /* autoBrightness = */ true,
                 /* useProximitySensor= */ true,
                 /* boostScreenBrightness= */ true,
                 /* dozeScreenStateOverride= */ Display.STATE_ON,
@@ -313,7 +310,6 @@
                 mPowerGroup.mDisplayPowerRequest;
         assertThat(displayPowerRequest.policy).isEqualTo(POLICY_DOZE);
         assertThat(displayPowerRequest.screenBrightnessOverride).isWithin(PRECISION).of(BRIGHTNESS);
-        assertThat(displayPowerRequest.useAutoBrightness).isEqualTo(true);
         assertThat(displayPowerRequest.useProximitySensor).isEqualTo(true);
         assertThat(displayPowerRequest.boostScreenBrightness).isEqualTo(true);
         assertThat(displayPowerRequest.dozeScreenState).isEqualTo(Display.STATE_ON);
@@ -336,7 +332,6 @@
         assertThat(mPowerGroup.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_DOZING);
 
         mPowerGroup.updateLocked(/* screenBrightnessOverride= */ BRIGHTNESS,
-                /* autoBrightness = */ true,
                 /* useProximitySensor= */ true,
                 /* boostScreenBrightness= */ true,
                 /* dozeScreenStateOverride= */ Display.STATE_ON,
@@ -352,7 +347,6 @@
                 mPowerGroup.mDisplayPowerRequest;
         assertThat(displayPowerRequest.policy).isEqualTo(POLICY_OFF);
         assertThat(displayPowerRequest.screenBrightnessOverride).isWithin(PRECISION).of(BRIGHTNESS);
-        assertThat(displayPowerRequest.useAutoBrightness).isEqualTo(true);
         assertThat(displayPowerRequest.useProximitySensor).isEqualTo(true);
         assertThat(displayPowerRequest.boostScreenBrightness).isEqualTo(true);
         assertThat(displayPowerRequest.dozeScreenState).isEqualTo(Display.STATE_UNKNOWN);
@@ -374,7 +368,6 @@
         assertThat(mPowerGroup.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
 
         mPowerGroup.updateLocked(/* screenBrightnessOverride= */ BRIGHTNESS,
-                /* autoBrightness = */ true,
                 /* useProximitySensor= */ true,
                 /* boostScreenBrightness= */ true,
                 /* dozeScreenStateOverride= */ Display.STATE_ON,
@@ -390,7 +383,6 @@
                 mPowerGroup.mDisplayPowerRequest;
         assertThat(displayPowerRequest.policy).isEqualTo(POLICY_OFF);
         assertThat(displayPowerRequest.screenBrightnessOverride).isWithin(PRECISION).of(BRIGHTNESS);
-        assertThat(displayPowerRequest.useAutoBrightness).isEqualTo(true);
         assertThat(displayPowerRequest.useProximitySensor).isEqualTo(true);
         assertThat(displayPowerRequest.boostScreenBrightness).isEqualTo(true);
         assertThat(displayPowerRequest.dozeScreenState).isEqualTo(Display.STATE_UNKNOWN);
@@ -412,7 +404,6 @@
         mPowerGroup.sleepLocked(TIMESTAMP1, UID, GO_TO_SLEEP_REASON_TIMEOUT);
         assertThat(mPowerGroup.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
         mPowerGroup.updateLocked(/* screenBrightnessOverride= */ BRIGHTNESS,
-                /* autoBrightness = */ true,
                 /* useProximitySensor= */ true,
                 /* boostScreenBrightness= */ true,
                 /* dozeScreenStateOverride= */ Display.STATE_ON,
@@ -428,7 +419,6 @@
                 mPowerGroup.mDisplayPowerRequest;
         assertThat(displayPowerRequest.policy).isEqualTo(POLICY_OFF);
         assertThat(displayPowerRequest.screenBrightnessOverride).isWithin(PRECISION).of(BRIGHTNESS);
-        assertThat(displayPowerRequest.useAutoBrightness).isEqualTo(true);
         assertThat(displayPowerRequest.useProximitySensor).isEqualTo(true);
         assertThat(displayPowerRequest.boostScreenBrightness).isEqualTo(true);
         assertThat(displayPowerRequest.dozeScreenState).isEqualTo(Display.STATE_UNKNOWN);
@@ -451,7 +441,6 @@
         assertThat(mPowerGroup.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_DREAMING);
         mPowerGroup.setWakeLockSummaryLocked(WAKE_LOCK_SCREEN_BRIGHT);
         mPowerGroup.updateLocked(/* screenBrightnessOverride= */ BRIGHTNESS,
-                /* autoBrightness = */ true,
                 /* useProximitySensor= */ true,
                 /* boostScreenBrightness= */ true,
                 /* dozeScreenStateOverride= */ Display.STATE_ON,
@@ -467,7 +456,6 @@
                 mPowerGroup.mDisplayPowerRequest;
         assertThat(displayPowerRequest.policy).isEqualTo(POLICY_BRIGHT);
         assertThat(displayPowerRequest.screenBrightnessOverride).isWithin(PRECISION).of(BRIGHTNESS);
-        assertThat(displayPowerRequest.useAutoBrightness).isEqualTo(true);
         assertThat(displayPowerRequest.useProximitySensor).isEqualTo(true);
         assertThat(displayPowerRequest.boostScreenBrightness).isEqualTo(true);
         assertThat(displayPowerRequest.dozeScreenState).isEqualTo(Display.STATE_UNKNOWN);
@@ -488,7 +476,6 @@
                 .build();
         assertThat(mPowerGroup.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
         mPowerGroup.updateLocked(/* screenBrightnessOverride= */ BRIGHTNESS,
-                /* autoBrightness = */ true,
                 /* useProximitySensor= */ true,
                 /* boostScreenBrightness= */ true,
                 /* dozeScreenStateOverride= */ Display.STATE_ON,
@@ -504,7 +491,6 @@
                 mPowerGroup.mDisplayPowerRequest;
         assertThat(displayPowerRequest.policy).isEqualTo(POLICY_BRIGHT);
         assertThat(displayPowerRequest.screenBrightnessOverride).isWithin(PRECISION).of(BRIGHTNESS);
-        assertThat(displayPowerRequest.useAutoBrightness).isEqualTo(true);
         assertThat(displayPowerRequest.useProximitySensor).isEqualTo(true);
         assertThat(displayPowerRequest.boostScreenBrightness).isEqualTo(true);
         assertThat(displayPowerRequest.dozeScreenState).isEqualTo(Display.STATE_UNKNOWN);
@@ -526,7 +512,6 @@
         assertThat(mPowerGroup.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
         mPowerGroup.setUserActivitySummaryLocked(USER_ACTIVITY_SCREEN_BRIGHT);
         mPowerGroup.updateLocked(/* screenBrightnessOverride= */ BRIGHTNESS,
-                /* autoBrightness = */ true,
                 /* useProximitySensor= */ true,
                 /* boostScreenBrightness= */ true,
                 /* dozeScreenStateOverride= */ Display.STATE_ON,
@@ -542,7 +527,6 @@
                 mPowerGroup.mDisplayPowerRequest;
         assertThat(displayPowerRequest.policy).isEqualTo(POLICY_BRIGHT);
         assertThat(displayPowerRequest.screenBrightnessOverride).isWithin(PRECISION).of(BRIGHTNESS);
-        assertThat(displayPowerRequest.useAutoBrightness).isEqualTo(true);
         assertThat(displayPowerRequest.useProximitySensor).isEqualTo(true);
         assertThat(displayPowerRequest.boostScreenBrightness).isEqualTo(true);
         assertThat(displayPowerRequest.dozeScreenState).isEqualTo(Display.STATE_UNKNOWN);
@@ -563,7 +547,6 @@
                 .build();
         assertThat(mPowerGroup.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
         mPowerGroup.updateLocked(/* screenBrightnessOverride= */ BRIGHTNESS,
-                /* autoBrightness = */ true,
                 /* useProximitySensor= */ true,
                 /* boostScreenBrightness= */ true,
                 /* dozeScreenStateOverride= */ Display.STATE_ON,
@@ -579,7 +562,6 @@
                 mPowerGroup.mDisplayPowerRequest;
         assertThat(displayPowerRequest.policy).isEqualTo(POLICY_BRIGHT);
         assertThat(displayPowerRequest.screenBrightnessOverride).isWithin(PRECISION).of(BRIGHTNESS);
-        assertThat(displayPowerRequest.useAutoBrightness).isEqualTo(true);
         assertThat(displayPowerRequest.useProximitySensor).isEqualTo(true);
         assertThat(displayPowerRequest.boostScreenBrightness).isEqualTo(true);
         assertThat(displayPowerRequest.dozeScreenState).isEqualTo(Display.STATE_UNKNOWN);
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsHistoryIteratorTest.java b/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsHistoryIteratorTest.java
index 22e44f8..71c8c1d 100644
--- a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsHistoryIteratorTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsHistoryIteratorTest.java
@@ -93,43 +93,44 @@
         }
 
         final BatteryStatsHistoryIterator iterator =
-                mBatteryStats.createBatteryStatsHistoryIterator();
+                mBatteryStats.iterateBatteryStatsHistory();
 
-        BatteryStats.HistoryItem item = new BatteryStats.HistoryItem();
+        BatteryStats.HistoryItem item;
 
-        assertThat(iterator.next(item)).isTrue();
+        assertThat(item = iterator.next()).isNotNull();
         assertHistoryItem(item,
                 BatteryStats.HistoryItem.CMD_RESET, BatteryStats.HistoryItem.EVENT_NONE,
                 null, 0, 3_600_000, 90, 1_000_000);
 
-        assertThat(iterator.next(item)).isTrue();
+        assertThat(item = iterator.next()).isNotNull();
         assertHistoryItem(item,
                 BatteryStats.HistoryItem.CMD_UPDATE, BatteryStats.HistoryItem.EVENT_NONE,
                 null, 0, 3_600_000, 90, 1_000_000);
 
-        assertThat(iterator.next(item)).isTrue();
+        assertThat(item = iterator.next()).isNotNull();
         assertHistoryItem(item,
                 BatteryStats.HistoryItem.CMD_UPDATE, BatteryStats.HistoryItem.EVENT_NONE,
                 null, 0, 2_400_000, 80, 2_000_000);
 
-        assertThat(iterator.next(item)).isTrue();
+        assertThat(item = iterator.next()).isNotNull();
         assertHistoryItem(item,
                 BatteryStats.HistoryItem.CMD_UPDATE, BatteryStats.HistoryItem.EVENT_NONE,
                 null, 0, 2_400_000, 80, 2_000_000);
 
-        assertThat(iterator.next(item)).isTrue();
+        assertThat(item = iterator.next()).isNotNull();
         assertHistoryItem(item,
                 BatteryStats.HistoryItem.CMD_UPDATE,
                 BatteryStats.HistoryItem.EVENT_ALARM | BatteryStats.HistoryItem.EVENT_FLAG_START,
                 "foo", APP_UID, 2_400_000, 80, 3_000_000);
 
-        assertThat(iterator.next(item)).isTrue();
+        assertThat(item = iterator.next()).isNotNull();
         assertHistoryItem(item,
                 BatteryStats.HistoryItem.CMD_UPDATE,
                 BatteryStats.HistoryItem.EVENT_ALARM | BatteryStats.HistoryItem.EVENT_FLAG_FINISH,
                 "foo", APP_UID, 2_400_000, 80, 3_001_000);
 
-        assertThat(iterator.next(item)).isFalse();
+        assertThat(iterator.hasNext()).isFalse();
+        assertThat(iterator.next()).isNull();
     }
 
     // Test history that spans multiple buffers and uses more than 32k different strings.
@@ -163,21 +164,21 @@
         }
 
         final BatteryStatsHistoryIterator iterator =
-                mBatteryStats.createBatteryStatsHistoryIterator();
+                mBatteryStats.iterateBatteryStatsHistory();
 
-        BatteryStats.HistoryItem item = new BatteryStats.HistoryItem();
-        assertThat(iterator.next(item)).isTrue();
+        BatteryStats.HistoryItem item;
+        assertThat(item = iterator.next()).isNotNull();
         assertThat(item.cmd).isEqualTo((int) BatteryStats.HistoryItem.CMD_RESET);
         assertThat(item.eventCode).isEqualTo(BatteryStats.HistoryItem.EVENT_NONE);
         assertThat(item.eventTag).isNull();
 
-        assertThat(iterator.next(item)).isTrue();
+        assertThat(item = iterator.next()).isNotNull();
         assertThat(item.cmd).isEqualTo((int) BatteryStats.HistoryItem.CMD_UPDATE);
         assertThat(item.eventCode).isEqualTo(BatteryStats.HistoryItem.EVENT_NONE);
         assertThat(item.eventTag).isNull();
         assertThat(item.time).isEqualTo(1_000_000);
 
-        assertThat(iterator.next(item)).isTrue();
+        assertThat(item = iterator.next()).isNotNull();
         assertThat(item.cmd).isEqualTo((int) BatteryStats.HistoryItem.CMD_UPDATE);
         assertThat(item.eventCode).isEqualTo(BatteryStats.HistoryItem.EVENT_NONE);
         assertThat(item.eventTag).isNull();
@@ -186,7 +187,7 @@
         for (int i = 0; i < eventCount; i++) {
             String name = "a" + (i % 10);
             do {
-                assertThat(iterator.next(item)).isTrue();
+                assertThat(item = iterator.next()).isNotNull();
                 // Skip a blank event inserted at the start of every buffer
             } while (item.cmd != BatteryStats.HistoryItem.CMD_UPDATE
                     || item.eventCode == BatteryStats.HistoryItem.EVENT_NONE);
@@ -196,7 +197,7 @@
             assertThat(item.eventTag.string).isEqualTo(name);
 
             do {
-                assertThat(iterator.next(item)).isTrue();
+                assertThat(item = iterator.next()).isNotNull();
             } while (item.cmd != BatteryStats.HistoryItem.CMD_UPDATE
                     || item.eventCode == BatteryStats.HistoryItem.EVENT_NONE);
 
@@ -205,7 +206,8 @@
             assertThat(item.eventTag.string).isEqualTo(name);
         }
 
-        assertThat(iterator.next(item)).isFalse();
+        assertThat(iterator.hasNext()).isFalse();
+        assertThat(iterator.next()).isNull();
     }
 
     private void assertHistoryItem(BatteryStats.HistoryItem item, int command, int eventCode,
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java b/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java
index 3f5d331..22a7e8d 100644
--- a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java
@@ -309,10 +309,10 @@
         mHistory.recordMeasuredEnergyDetails(200, 200, details);
 
         BatteryStatsHistoryIterator iterator = mHistory.iterate();
-        BatteryStats.HistoryItem item = new BatteryStats.HistoryItem();
-        assertThat(iterator.next(item)).isTrue(); // First item contains current time only
+        BatteryStats.HistoryItem item;
+        assertThat(item = iterator.next()).isNotNull(); // First item contains current time only
 
-        assertThat(iterator.next(item)).isTrue();
+        assertThat(item = iterator.next()).isNotNull();
 
         String dump = toString(item, /* checkin */ false);
         assertThat(dump).contains("+200ms");
@@ -344,9 +344,9 @@
 
         BatteryStatsHistoryIterator iterator = mHistory.iterate();
         BatteryStats.HistoryItem item = new BatteryStats.HistoryItem();
-        assertThat(iterator.next(item)).isTrue(); // First item contains current time only
+        assertThat(item = iterator.next()).isNotNull(); // First item contains current time only
 
-        assertThat(iterator.next(item)).isTrue();
+        assertThat(item = iterator.next()).isNotNull();
 
         String dump = toString(item, /* checkin */ false);
         assertThat(dump).contains("+200ms");
@@ -361,7 +361,7 @@
         assertThat(checkin).contains("XB,3,2,HIGH");
         assertThat(checkin).contains("XC,10123,100,200,300");
 
-        assertThat(iterator.next(item)).isTrue();
+        assertThat(item = iterator.next()).isNotNull();
 
         dump = toString(item, /* checkin */ false);
         assertThat(dump).contains("+300ms");
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsNoteTest.java b/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsNoteTest.java
index 5b51868..773a2dc 100644
--- a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsNoteTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsNoteTest.java
@@ -83,6 +83,7 @@
  * Run: adb shell am instrument -e class BatteryStatsNoteTest -w \
  *      com.android.frameworks.coretests/androidx.test.runner.AndroidJUnitRunner
  */
+@SuppressWarnings("GuardedBy")
 public class BatteryStatsNoteTest extends TestCase {
     private static final String TAG = BatteryStatsNoteTest.class.getSimpleName();
 
@@ -271,11 +272,11 @@
         clocks.realtime = clocks.uptime = 220;
         bi.noteLongPartialWakelockFinish(name, historyName, ISOLATED_UID);
 
-        final BatteryStatsHistoryIterator iterator =  bi.createBatteryStatsHistoryIterator();
+        final BatteryStatsHistoryIterator iterator =  bi.iterateBatteryStatsHistory();
 
-        BatteryStats.HistoryItem item = new BatteryStats.HistoryItem();
+        BatteryStats.HistoryItem item;
 
-        while (iterator.next(item)) {
+        while ((item = iterator.next()) != null) {
             if (item.eventCode == HistoryItem.EVENT_LONG_WAKE_LOCK_START) break;
         }
         assertThat(item.eventCode).isEqualTo(HistoryItem.EVENT_LONG_WAKE_LOCK_START);
@@ -283,7 +284,7 @@
         assertThat(item.eventTag.string).isEqualTo(historyName);
         assertThat(item.eventTag.uid).isEqualTo(UID);
 
-        while (iterator.next(item)) {
+        while ((item = iterator.next()) != null) {
             if (item.eventCode == HistoryItem.EVENT_LONG_WAKE_LOCK_FINISH) break;
         }
         assertThat(item.eventCode).isEqualTo(HistoryItem.EVENT_LONG_WAKE_LOCK_FINISH);
@@ -327,11 +328,11 @@
         clocks.realtime = clocks.uptime = 220;
         bi.noteLongPartialWakelockFinish(name, historyName, ISOLATED_UID);
 
-        final BatteryStatsHistoryIterator iterator = bi.createBatteryStatsHistoryIterator();
+        final BatteryStatsHistoryIterator iterator = bi.iterateBatteryStatsHistory();
 
-        BatteryStats.HistoryItem item = new BatteryStats.HistoryItem();
+        BatteryStats.HistoryItem item;
 
-        while (iterator.next(item)) {
+        while ((item = iterator.next()) != null) {
             if (item.eventCode == HistoryItem.EVENT_LONG_WAKE_LOCK_START) break;
         }
         assertThat(item.eventCode).isEqualTo(HistoryItem.EVENT_LONG_WAKE_LOCK_START);
@@ -339,7 +340,7 @@
         assertThat(item.eventTag.string).isEqualTo(historyName);
         assertThat(item.eventTag.uid).isEqualTo(UID);
 
-        while (iterator.next(item)) {
+        while ((item = iterator.next()) != null) {
             if (item.eventCode == HistoryItem.EVENT_LONG_WAKE_LOCK_FINISH) break;
         }
         assertThat(item.eventCode).isEqualTo(HistoryItem.EVENT_LONG_WAKE_LOCK_FINISH);
@@ -941,26 +942,27 @@
         clocks.realtime = clocks.uptime = 5000;
         bi.noteAlarmFinishLocked("foo", null, UID);
 
-        HistoryItem item = new HistoryItem();
-        assertTrue(bi.startIteratingHistoryLocked());
+        BatteryStatsHistoryIterator iterator = bi.iterateBatteryStatsHistory();
+        HistoryItem item;
 
-        assertTrue(bi.getNextHistoryLocked(item));
+        assertThat(item = iterator.next()).isNotNull();
         assertEquals(HistoryItem.EVENT_ALARM_START, item.eventCode);
         assertEquals("foo", item.eventTag.string);
         assertEquals(UID, item.eventTag.uid);
 
         // TODO(narayan): Figure out why this event is written to the history buffer. See
         // test below where it is being interspersed between multiple START events too.
-        assertTrue(bi.getNextHistoryLocked(item));
+        assertThat(item = iterator.next()).isNotNull();
         assertEquals(HistoryItem.EVENT_NONE, item.eventCode);
 
-        assertTrue(bi.getNextHistoryLocked(item));
+        assertThat(item = iterator.next()).isNotNull();
         assertEquals(HistoryItem.EVENT_ALARM_FINISH, item.eventCode);
         assertTrue(item.isDeltaData());
         assertEquals("foo", item.eventTag.string);
         assertEquals(UID, item.eventTag.uid);
 
-        assertFalse(bi.getNextHistoryLocked(item));
+        assertThat(iterator.hasNext()).isFalse();
+        assertThat(iterator.next()).isNull();
     }
 
     @SmallTest
@@ -980,28 +982,28 @@
         clocks.realtime = clocks.uptime = 5000;
         bi.noteAlarmFinishLocked("foo", ws, UID);
 
-        HistoryItem item = new HistoryItem();
-        assertTrue(bi.startIteratingHistoryLocked());
+        BatteryStatsHistoryIterator iterator = bi.iterateBatteryStatsHistory();
+        HistoryItem item;
 
-        assertTrue(bi.getNextHistoryLocked(item));
+        assertThat(item = iterator.next()).isNotNull();
         assertEquals(HistoryItem.EVENT_ALARM_START, item.eventCode);
         assertEquals("foo", item.eventTag.string);
         assertEquals(100, item.eventTag.uid);
 
-        assertTrue(bi.getNextHistoryLocked(item));
+        assertThat(item = iterator.next()).isNotNull();
         assertEquals(HistoryItem.EVENT_NONE, item.eventCode);
 
-        assertTrue(bi.getNextHistoryLocked(item));
+        assertThat(item = iterator.next()).isNotNull();
         assertEquals(HistoryItem.EVENT_ALARM_START, item.eventCode);
         assertEquals("foo", item.eventTag.string);
         assertEquals(500, item.eventTag.uid);
 
-        assertTrue(bi.getNextHistoryLocked(item));
+        assertThat(item = iterator.next()).isNotNull();
         assertEquals(HistoryItem.EVENT_ALARM_FINISH, item.eventCode);
         assertEquals("foo", item.eventTag.string);
         assertEquals(100, item.eventTag.uid);
 
-        assertTrue(bi.getNextHistoryLocked(item));
+        assertThat(item = iterator.next()).isNotNull();
         assertEquals(HistoryItem.EVENT_ALARM_FINISH, item.eventCode);
         assertEquals("foo", item.eventTag.string);
         assertEquals(500, item.eventTag.uid);
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BatteryUsageStatsProviderTest.java b/services/tests/servicestests/src/com/android/server/power/stats/BatteryUsageStatsProviderTest.java
index 9d46e11..968609b 100644
--- a/services/tests/servicestests/src/com/android/server/power/stats/BatteryUsageStatsProviderTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/stats/BatteryUsageStatsProviderTest.java
@@ -219,36 +219,37 @@
 
         final BatteryStatsHistoryIterator iterator =
                 unparceled.iterateBatteryStatsHistory();
-        BatteryStats.HistoryItem item = new BatteryStats.HistoryItem();
+        BatteryStats.HistoryItem item;
 
-        assertThat(iterator.next(item)).isTrue();
+        assertThat(item = iterator.next()).isNotNull();
         assertHistoryItem(item,
                 BatteryStats.HistoryItem.CMD_RESET, BatteryStats.HistoryItem.EVENT_NONE,
                 null, 0, 3_600_000, 90, 1_000_000);
 
-        assertThat(iterator.next(item)).isTrue();
+        assertThat(item = iterator.next()).isNotNull();
         assertHistoryItem(item,
                 BatteryStats.HistoryItem.CMD_UPDATE, BatteryStats.HistoryItem.EVENT_NONE,
                 null, 0, 3_600_000, 90, 1_000_000);
 
-        assertThat(iterator.next(item)).isTrue();
+        assertThat(item = iterator.next()).isNotNull();
         assertHistoryItem(item,
                 BatteryStats.HistoryItem.CMD_UPDATE, BatteryStats.HistoryItem.EVENT_NONE,
                 null, 0, 3_600_000, 90, 2_000_000);
 
-        assertThat(iterator.next(item)).isTrue();
+        assertThat(item = iterator.next()).isNotNull();
         assertHistoryItem(item,
                 BatteryStats.HistoryItem.CMD_UPDATE,
                 BatteryStats.HistoryItem.EVENT_ALARM | BatteryStats.HistoryItem.EVENT_FLAG_START,
                 "foo", APP_UID, 3_600_000, 90, 3_000_000);
 
-        assertThat(iterator.next(item)).isTrue();
+        assertThat(item = iterator.next()).isNotNull();
         assertHistoryItem(item,
                 BatteryStats.HistoryItem.CMD_UPDATE,
                 BatteryStats.HistoryItem.EVENT_ALARM | BatteryStats.HistoryItem.EVENT_FLAG_FINISH,
                 "foo", APP_UID, 3_600_000, 90, 3_001_000);
 
-        assertThat(iterator.next(item)).isFalse();
+        assertThat(iterator.hasNext()).isFalse();
+        assertThat(iterator.next()).isNull();
     }
 
     @Test
@@ -304,16 +305,16 @@
                 BatteryUsageStats.class);
 
         BatteryStatsHistoryIterator iterator = unparceled.iterateBatteryStatsHistory();
-        BatteryStats.HistoryItem item = new BatteryStats.HistoryItem();
+        BatteryStats.HistoryItem item;
 
-        assertThat(iterator.next(item)).isTrue();
+        assertThat(item = iterator.next()).isNotNull();
         assertThat(item.cmd).isEqualTo((int) BatteryStats.HistoryItem.CMD_RESET);
 
         int expectedUid = 1;
-        while (iterator.next(item)) {
+        while ((item = iterator.next()) != null) {
             while (item.cmd != BatteryStats.HistoryItem.CMD_UPDATE
                     || item.eventCode == BatteryStats.HistoryItem.EVENT_NONE) {
-                assertThat(iterator.next(item)).isTrue();
+                assertThat(item = iterator.next()).isNotNull();
             }
             int uid = item.eventTag.uid;
             assertThat(uid).isEqualTo(expectedUid++);
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index 982137b..f983fa9 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -3284,7 +3284,7 @@
         assertFalse(app2.mActivityRecord.mImeInsetsFrozenUntilStartInput);
         verify(app2.mClient, atLeastOnce()).resized(any(), anyBoolean(), any(),
                 insetsStateCaptor.capture(), anyBoolean(), anyBoolean(), anyInt(), anyInt(),
-                anyBoolean());
+                anyInt());
         assertFalse(app2.getInsetsState().getSource(ITYPE_IME).isVisible());
     }
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
index d16e11b..43627f4 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
@@ -1637,6 +1637,8 @@
         final ActivityRecord target = new ActivityBuilder(mAtm).setAffinity(info.taskAffinity)
                 .build();
         final ActivityStarter starter = prepareStarter(FLAG_ACTIVITY_NEW_TASK, false);
+        spyOn(starter);
+        doReturn(START_SUCCESS).when(starter).isAllowedToStart(any(), anyBoolean(), any());
         startActivityInner(starter, target, null /* source */, null /* options */,
                 null /* inTask */, null /* inTaskFragment */);
 
@@ -1661,6 +1663,8 @@
         final ActivityRecord target = new ActivityBuilder(mAtm).setRequiredDisplayCategory("auto")
                 .setAffinity(info.taskAffinity).build();
         final ActivityStarter starter = prepareStarter(0, false);
+        spyOn(starter);
+        doReturn(START_SUCCESS).when(starter).isAllowedToStart(any(), anyBoolean(), any());
         startActivityInner(starter, target,  task.getBottomMostActivity(), null /* options */,
                 null /* inTask */, null /* inTaskFragment */);
 
@@ -1685,7 +1689,10 @@
         final ActivityRecord target = new ActivityBuilder(mAtm)
                 .setRequiredDisplayCategory(info.requiredDisplayCategory)
                 .setAffinity(info.taskAffinity).build();
+
         final ActivityStarter starter = prepareStarter(0, false);
+        spyOn(starter);
+        doReturn(START_SUCCESS).when(starter).isAllowedToStart(any(), anyBoolean(), any());
         startActivityInner(starter, target,  task.getBottomMostActivity(), null /* options */,
                 null /* inTask */, null /* inTaskFragment */);
 
@@ -1706,6 +1713,8 @@
         inTask.inRecents = true;
 
         final ActivityStarter starter = prepareStarter(0, false);
+        spyOn(starter);
+        doReturn(START_SUCCESS).when(starter).isAllowedToStart(any(), anyBoolean(), any());
         final ActivityRecord target = new ActivityBuilder(mAtm).build();
         startActivityInner(starter, target, null /* source */, null /* options */, inTask,
                 null /* inTaskFragment */);
@@ -1724,6 +1733,8 @@
         inTask.inRecents = true;
 
         final ActivityStarter starter = prepareStarter(0, false);
+        spyOn(starter);
+        doReturn(START_SUCCESS).when(starter).isAllowedToStart(any(), anyBoolean(), any());
         final ActivityRecord target = new ActivityBuilder(mAtm).setRequiredDisplayCategory("auto")
                 .build();
         startActivityInner(starter, target, null /* source */, null /* options */, inTask,
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayWindowPolicyControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayWindowPolicyControllerTests.java
index ba68a25..2ce1d60 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayWindowPolicyControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayWindowPolicyControllerTests.java
@@ -193,6 +193,30 @@
         assertEquals(result, START_ABORTED);
     }
 
+    @Test
+    public void testCanActivityBeLaunched_requiredDisplayCategory() {
+        ActivityStarter starter = new ActivityStarter(mock(ActivityStartController.class), mAtm,
+                mSupervisor, mock(ActivityStartInterceptor.class));
+        final Task task = new TaskBuilder(mSupervisor).setDisplay(mSecondaryDisplay).build();
+        final ActivityRecord sourceRecord = new ActivityBuilder(mAtm).setTask(task).build();
+        final ActivityRecord disallowedRecord =
+                new ActivityBuilder(mAtm).setRequiredDisplayCategory("auto").build();
+
+        int result = starter.startActivityInner(
+                disallowedRecord,
+                sourceRecord,
+                /* voiceSession= */null,
+                /* voiceInteractor= */ null,
+                /* startFlags= */ 0,
+                /* options= */null,
+                /* inTask= */null,
+                /* inTaskFragment= */ null,
+                /* restrictedBgActivity= */false,
+                /* intentGrants= */null);
+
+        assertEquals(result, START_ABORTED);
+    }
+
     private class TestDisplayWindowPolicyController extends DisplayWindowPolicyController {
 
         public ComponentName DISALLOWED_ACTIVITY =
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 13ea99a..6877e4f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -166,6 +166,114 @@
     }
 
     @Test
+    public void testApplyStrategyToTranslucentActivities() {
+        mWm.mLetterboxConfiguration.setTranslucentLetterboxingOverrideEnabled(true);
+        setUpDisplaySizeWithApp(2000, 1000);
+        prepareUnresizable(mActivity, 1.5f /* maxAspect */, SCREEN_ORIENTATION_PORTRAIT);
+        mActivity.info.setMinAspectRatio(1.2f);
+        mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+        // Translucent Activity
+        final ActivityRecord translucentActivity = new ActivityBuilder(mAtm)
+                .setLaunchedFromUid(mActivity.getUid())
+                .setScreenOrientation(SCREEN_ORIENTATION_LANDSCAPE)
+                .setMinAspectRatio(1.1f)
+                .setMaxAspectRatio(3f)
+                .build();
+        doReturn(false).when(translucentActivity).fillsParent();
+        mTask.addChild(translucentActivity);
+        // We check bounds
+        final Rect opaqueBounds = mActivity.getConfiguration().windowConfiguration.getBounds();
+        final Rect translucentRequestedBounds = translucentActivity.getRequestedOverrideBounds();
+        assertEquals(opaqueBounds, translucentRequestedBounds);
+        // We check orientation
+        final int translucentOrientation =
+                translucentActivity.getRequestedConfigurationOrientation();
+        assertEquals(ORIENTATION_PORTRAIT, translucentOrientation);
+        // We check aspect ratios
+        assertEquals(1.2f, translucentActivity.getMinAspectRatio(), 0.00001f);
+        assertEquals(1.5f, translucentActivity.getMaxAspectRatio(), 0.00001f);
+    }
+
+    @Test
+    public void testNotApplyStrategyToTranslucentActivitiesWithDifferentUid() {
+        mWm.mLetterboxConfiguration.setTranslucentLetterboxingOverrideEnabled(true);
+        setUpDisplaySizeWithApp(2000, 1000);
+        prepareUnresizable(mActivity, 1.5f /* maxAspect */, SCREEN_ORIENTATION_PORTRAIT);
+        mActivity.info.setMinAspectRatio(1.2f);
+        mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+        // Translucent Activity
+        final ActivityRecord translucentActivity = new ActivityBuilder(mAtm)
+                .setScreenOrientation(SCREEN_ORIENTATION_LANDSCAPE)
+                .setMinAspectRatio(1.1f)
+                .setMaxAspectRatio(3f)
+                .build();
+        doReturn(false).when(translucentActivity).fillsParent();
+        mTask.addChild(translucentActivity);
+        // We check bounds
+        final Rect opaqueBounds = mActivity.getConfiguration().windowConfiguration.getBounds();
+        final Rect translucentRequestedBounds = translucentActivity.getRequestedOverrideBounds();
+        assertNotEquals(opaqueBounds, translucentRequestedBounds);
+    }
+
+    @Test
+    public void testApplyStrategyToMultipleTranslucentActivities() {
+        mWm.mLetterboxConfiguration.setTranslucentLetterboxingOverrideEnabled(true);
+        setUpDisplaySizeWithApp(2000, 1000);
+        prepareUnresizable(mActivity, 1.5f /* maxAspect */, SCREEN_ORIENTATION_PORTRAIT);
+        mActivity.info.setMinAspectRatio(1.2f);
+        mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+        // Translucent Activity
+        final ActivityRecord translucentActivity = new ActivityBuilder(mAtm)
+                .setLaunchedFromUid(mActivity.getUid())
+                .setScreenOrientation(SCREEN_ORIENTATION_LANDSCAPE)
+                .setMinAspectRatio(1.1f)
+                .setMaxAspectRatio(3f)
+                .build();
+        doReturn(false).when(translucentActivity).fillsParent();
+        mTask.addChild(translucentActivity);
+        // We check bounds
+        final Rect opaqueBounds = mActivity.getConfiguration().windowConfiguration.getBounds();
+        final Rect translucentRequestedBounds = translucentActivity.getRequestedOverrideBounds();
+        assertEquals(opaqueBounds, translucentRequestedBounds);
+        // Launch another translucent activity
+        final ActivityRecord translucentActivity2 = new ActivityBuilder(mAtm)
+                .setLaunchedFromUid(mActivity.getUid())
+                .setScreenOrientation(SCREEN_ORIENTATION_LANDSCAPE)
+                .build();
+        doReturn(false).when(translucentActivity2).fillsParent();
+        mTask.addChild(translucentActivity2);
+        // We check bounds
+        final Rect translucent2RequestedBounds = translucentActivity2.getRequestedOverrideBounds();
+        assertEquals(opaqueBounds, translucent2RequestedBounds);
+    }
+
+    @Test
+    public void testTranslucentActivitiesDontGoInSizeCompactMode() {
+        mWm.mLetterboxConfiguration.setTranslucentLetterboxingOverrideEnabled(true);
+        setUpDisplaySizeWithApp(2800, 1400);
+        mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+        prepareUnresizable(mActivity, -1f /* maxAspect */, SCREEN_ORIENTATION_PORTRAIT);
+        // Rotate to put activity in size compat mode.
+        rotateDisplay(mActivity.mDisplayContent, ROTATION_90);
+        assertTrue(mActivity.inSizeCompatMode());
+        // Rotate back
+        rotateDisplay(mActivity.mDisplayContent, ROTATION_0);
+        assertFalse(mActivity.inSizeCompatMode());
+        // We launch a transparent activity
+        final ActivityRecord translucentActivity = new ActivityBuilder(mAtm)
+                .setLaunchedFromUid(mActivity.getUid())
+                .setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT)
+                .build();
+        doReturn(true).when(translucentActivity).fillsParent();
+        mTask.addChild(translucentActivity);
+        // It should not be in SCM
+        assertFalse(translucentActivity.inSizeCompatMode());
+        // We rotate again
+        rotateDisplay(translucentActivity.mDisplayContent, ROTATION_90);
+        assertFalse(translucentActivity.inSizeCompatMode());
+    }
+
+    @Test
     public void testRestartProcessIfVisible() {
         setUpDisplaySizeWithApp(1000, 2500);
         doNothing().when(mSupervisor).scheduleRestartTimeout(mActivity);
diff --git a/services/tests/wmtests/src/com/android/server/wm/SurfaceSyncGroupTest.java b/services/tests/wmtests/src/com/android/server/wm/SurfaceSyncGroupTest.java
index 5e1fae0..7d9f29c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SurfaceSyncGroupTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SurfaceSyncGroupTest.java
@@ -20,6 +20,9 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
 
 import android.platform.test.annotations.Presubmit;
 import android.view.SurfaceControl;
@@ -31,12 +34,15 @@
 import org.junit.Test;
 
 import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
 import java.util.concurrent.TimeUnit;
 
 @SmallTest
 @Presubmit
 public class SurfaceSyncGroupTest {
 
+    private final Executor mExecutor = Runnable::run;
+
     @Before
     public void setup() {
         SurfaceSyncGroup.setTransactionFactory(StubTransaction::new);
@@ -45,10 +51,11 @@
     @Test
     public void testSyncOne() throws InterruptedException {
         final CountDownLatch finishedLatch = new CountDownLatch(1);
-        SurfaceSyncGroup syncGroup = new SurfaceSyncGroup(transaction -> finishedLatch.countDown());
+        SurfaceSyncGroup syncGroup = new SurfaceSyncGroup();
+        syncGroup.addSyncCompleteCallback(mExecutor, finishedLatch::countDown);
         SyncTarget syncTarget = new SyncTarget();
-        syncGroup.addToSync(syncTarget);
-        syncGroup.markSyncReady();
+        syncGroup.addToSync(syncTarget, false /* parentSyncGroupMerge */);
+        syncGroup.onTransactionReady(null);
 
         syncTarget.onBufferReady();
 
@@ -59,15 +66,16 @@
     @Test
     public void testSyncMultiple() throws InterruptedException {
         final CountDownLatch finishedLatch = new CountDownLatch(1);
-        SurfaceSyncGroup syncGroup = new SurfaceSyncGroup(transaction -> finishedLatch.countDown());
+        SurfaceSyncGroup syncGroup = new SurfaceSyncGroup();
+        syncGroup.addSyncCompleteCallback(mExecutor, finishedLatch::countDown);
         SyncTarget syncTarget1 = new SyncTarget();
         SyncTarget syncTarget2 = new SyncTarget();
         SyncTarget syncTarget3 = new SyncTarget();
 
-        syncGroup.addToSync(syncTarget1);
-        syncGroup.addToSync(syncTarget2);
-        syncGroup.addToSync(syncTarget3);
-        syncGroup.markSyncReady();
+        syncGroup.addToSync(syncTarget1, false /* parentSyncGroupMerge */);
+        syncGroup.addToSync(syncTarget2, false /* parentSyncGroupMerge */);
+        syncGroup.addToSync(syncTarget3, false /* parentSyncGroupMerge */);
+        syncGroup.onTransactionReady(null);
 
         syncTarget1.onBufferReady();
         assertNotEquals(0, finishedLatch.getCount());
@@ -83,35 +91,35 @@
 
     @Test
     public void testAddSyncWhenSyncComplete() {
-        final CountDownLatch finishedLatch = new CountDownLatch(1);
-        SurfaceSyncGroup syncGroup = new SurfaceSyncGroup(transaction -> finishedLatch.countDown());
+        SurfaceSyncGroup syncGroup = new SurfaceSyncGroup();
 
         SyncTarget syncTarget1 = new SyncTarget();
         SyncTarget syncTarget2 = new SyncTarget();
 
-        assertTrue(syncGroup.addToSync(syncTarget1));
-        syncGroup.markSyncReady();
+        assertTrue(syncGroup.addToSync(syncTarget1, false /* parentSyncGroupMerge */));
+        syncGroup.onTransactionReady(null);
         // Adding to a sync that has been completed is also invalid since the sync id has been
         // cleared.
-        assertFalse(syncGroup.addToSync(syncTarget2));
+        assertFalse(syncGroup.addToSync(syncTarget2, false /* parentSyncGroupMerge */));
     }
 
     @Test
-    public void testMultiplesyncGroups() throws InterruptedException {
+    public void testMultipleSyncGroups() throws InterruptedException {
         final CountDownLatch finishedLatch1 = new CountDownLatch(1);
         final CountDownLatch finishedLatch2 = new CountDownLatch(1);
-        SurfaceSyncGroup syncGroup1 = new SurfaceSyncGroup(
-                transaction -> finishedLatch1.countDown());
-        SurfaceSyncGroup syncGroup2 = new SurfaceSyncGroup(
-                transaction -> finishedLatch2.countDown());
+        SurfaceSyncGroup syncGroup1 = new SurfaceSyncGroup();
+        SurfaceSyncGroup syncGroup2 = new SurfaceSyncGroup();
+
+        syncGroup1.addSyncCompleteCallback(mExecutor, finishedLatch1::countDown);
+        syncGroup2.addSyncCompleteCallback(mExecutor, finishedLatch2::countDown);
 
         SyncTarget syncTarget1 = new SyncTarget();
         SyncTarget syncTarget2 = new SyncTarget();
 
-        assertTrue(syncGroup1.addToSync(syncTarget1));
-        assertTrue(syncGroup2.addToSync(syncTarget2));
-        syncGroup1.markSyncReady();
-        syncGroup2.markSyncReady();
+        assertTrue(syncGroup1.addToSync(syncTarget1, false /* parentSyncGroupMerge */));
+        assertTrue(syncGroup2.addToSync(syncTarget2, false /* parentSyncGroupMerge */));
+        syncGroup1.onTransactionReady(null);
+        syncGroup2.onTransactionReady(null);
 
         syncTarget1.onBufferReady();
 
@@ -126,22 +134,23 @@
     }
 
     @Test
-    public void testMergeSync() throws InterruptedException {
+    public void testAddSyncGroup() throws InterruptedException {
         final CountDownLatch finishedLatch1 = new CountDownLatch(1);
         final CountDownLatch finishedLatch2 = new CountDownLatch(1);
-        SurfaceSyncGroup syncGroup1 = new SurfaceSyncGroup(
-                transaction -> finishedLatch1.countDown());
-        SurfaceSyncGroup syncGroup2 = new SurfaceSyncGroup(
-                transaction -> finishedLatch2.countDown());
+        SurfaceSyncGroup syncGroup1 = new SurfaceSyncGroup();
+        SurfaceSyncGroup syncGroup2 = new SurfaceSyncGroup();
+
+        syncGroup1.addSyncCompleteCallback(mExecutor, finishedLatch1::countDown);
+        syncGroup2.addSyncCompleteCallback(mExecutor, finishedLatch2::countDown);
 
         SyncTarget syncTarget1 = new SyncTarget();
         SyncTarget syncTarget2 = new SyncTarget();
 
-        assertTrue(syncGroup1.addToSync(syncTarget1));
-        assertTrue(syncGroup2.addToSync(syncTarget2));
-        syncGroup1.markSyncReady();
-        syncGroup2.merge(syncGroup1);
-        syncGroup2.markSyncReady();
+        assertTrue(syncGroup1.addToSync(syncTarget1, false /* parentSyncGroupMerge */));
+        assertTrue(syncGroup2.addToSync(syncTarget2, false /* parentSyncGroupMerge */));
+        syncGroup1.onTransactionReady(null);
+        syncGroup2.addToSync(syncGroup1, false /* parentSyncGroupMerge */);
+        syncGroup2.onTransactionReady(null);
 
         // Finish syncTarget2 first to test that the syncGroup is not complete until the merged sync
         // is also done.
@@ -161,28 +170,29 @@
     }
 
     @Test
-    public void testMergeSyncAlreadyComplete() throws InterruptedException {
+    public void testAddSyncAlreadyComplete() throws InterruptedException {
         final CountDownLatch finishedLatch1 = new CountDownLatch(1);
         final CountDownLatch finishedLatch2 = new CountDownLatch(1);
-        SurfaceSyncGroup syncGroup1 = new SurfaceSyncGroup(
-                transaction -> finishedLatch1.countDown());
-        SurfaceSyncGroup syncGroup2 = new SurfaceSyncGroup(
-                transaction -> finishedLatch2.countDown());
+        SurfaceSyncGroup syncGroup1 = new SurfaceSyncGroup();
+        SurfaceSyncGroup syncGroup2 = new SurfaceSyncGroup();
+
+        syncGroup1.addSyncCompleteCallback(mExecutor, finishedLatch1::countDown);
+        syncGroup2.addSyncCompleteCallback(mExecutor, finishedLatch2::countDown);
 
         SyncTarget syncTarget1 = new SyncTarget();
         SyncTarget syncTarget2 = new SyncTarget();
 
-        assertTrue(syncGroup1.addToSync(syncTarget1));
-        assertTrue(syncGroup2.addToSync(syncTarget2));
-        syncGroup1.markSyncReady();
+        assertTrue(syncGroup1.addToSync(syncTarget1, false /* parentSyncGroupMerge */));
+        assertTrue(syncGroup2.addToSync(syncTarget2, false /* parentSyncGroupMerge */));
+        syncGroup1.onTransactionReady(null);
         syncTarget1.onBufferReady();
 
         // The first sync will still get a callback when it's sync requirements are done.
         finishedLatch1.await(5, TimeUnit.SECONDS);
         assertEquals(0, finishedLatch1.getCount());
 
-        syncGroup2.merge(syncGroup1);
-        syncGroup2.markSyncReady();
+        syncGroup2.addToSync(syncGroup1, false /* parentSyncGroupMerge */);
+        syncGroup2.onTransactionReady(null);
         syncTarget2.onBufferReady();
 
         // Verify that the second sync will receive complete since the merged sync was already
@@ -191,18 +201,145 @@
         assertEquals(0, finishedLatch2.getCount());
     }
 
-    private static class SyncTarget implements SurfaceSyncGroup.SyncTarget {
-        private SurfaceSyncGroup.TransactionReadyCallback mTransactionReadyCallback;
+    @Test
+    public void testAddSyncAlreadyInASync_NewSyncReadyFirst() throws InterruptedException {
+        final CountDownLatch finishedLatch1 = new CountDownLatch(1);
+        final CountDownLatch finishedLatch2 = new CountDownLatch(1);
+        SurfaceSyncGroup syncGroup1 = new SurfaceSyncGroup();
+        SurfaceSyncGroup syncGroup2 = new SurfaceSyncGroup();
 
-        @Override
-        public void onAddedToSyncGroup(SurfaceSyncGroup parentSyncGroup,
-                SurfaceSyncGroup.TransactionReadyCallback transactionReadyCallback) {
-            mTransactionReadyCallback = transactionReadyCallback;
-        }
+        syncGroup1.addSyncCompleteCallback(mExecutor, finishedLatch1::countDown);
+        syncGroup2.addSyncCompleteCallback(mExecutor, finishedLatch2::countDown);
 
+        SyncTarget syncTarget1 = new SyncTarget();
+        SyncTarget syncTarget2 = new SyncTarget();
+        SyncTarget syncTarget3 = new SyncTarget();
+
+        assertTrue(syncGroup1.addToSync(syncTarget1, false /* parentSyncGroupMerge */));
+        assertTrue(syncGroup1.addToSync(syncTarget2, false /* parentSyncGroupMerge */));
+
+        // Add syncTarget1 to syncGroup2 so it forces syncGroup1 into syncGroup2
+        assertTrue(syncGroup2.addToSync(syncTarget1, false /* parentSyncGroupMerge */));
+        assertTrue(syncGroup2.addToSync(syncTarget3, false /* parentSyncGroupMerge */));
+
+        syncGroup1.onTransactionReady(null);
+        syncGroup2.onTransactionReady(null);
+
+        // Make target1 and target3 ready, but not target2. SyncGroup2 should not be ready since
+        // SyncGroup2 also waits for all of SyncGroup1 to finish, which includes target2
+        syncTarget1.onBufferReady();
+        syncTarget3.onBufferReady();
+
+        // Neither SyncGroup will be ready.
+        finishedLatch1.await(1, TimeUnit.SECONDS);
+        finishedLatch2.await(1, TimeUnit.SECONDS);
+
+        assertEquals(1, finishedLatch1.getCount());
+        assertEquals(1, finishedLatch2.getCount());
+
+        syncTarget2.onBufferReady();
+
+        // Both sync groups should be ready after target2 completed.
+        finishedLatch1.await(5, TimeUnit.SECONDS);
+        finishedLatch2.await(5, TimeUnit.SECONDS);
+        assertEquals(0, finishedLatch1.getCount());
+        assertEquals(0, finishedLatch2.getCount());
+    }
+
+    @Test
+    public void testAddSyncAlreadyInASync_OldSyncFinishesFirst() throws InterruptedException {
+        final CountDownLatch finishedLatch1 = new CountDownLatch(1);
+        final CountDownLatch finishedLatch2 = new CountDownLatch(1);
+        SurfaceSyncGroup syncGroup1 = new SurfaceSyncGroup();
+        SurfaceSyncGroup syncGroup2 = new SurfaceSyncGroup();
+
+        syncGroup1.addSyncCompleteCallback(mExecutor, finishedLatch1::countDown);
+        syncGroup2.addSyncCompleteCallback(mExecutor, finishedLatch2::countDown);
+
+        SyncTarget syncTarget1 = new SyncTarget();
+        SyncTarget syncTarget2 = new SyncTarget();
+        SyncTarget syncTarget3 = new SyncTarget();
+
+        assertTrue(syncGroup1.addToSync(syncTarget1, false /* parentSyncGroupMerge */));
+        assertTrue(syncGroup1.addToSync(syncTarget2, false /* parentSyncGroupMerge */));
+        syncTarget2.onBufferReady();
+
+        // Add syncTarget1 to syncGroup2 so it forces syncGroup1 into syncGroup2
+        assertTrue(syncGroup2.addToSync(syncTarget1, false /* parentSyncGroupMerge */));
+        assertTrue(syncGroup2.addToSync(syncTarget3, false /* parentSyncGroupMerge */));
+
+        syncGroup1.onTransactionReady(null);
+        syncGroup2.onTransactionReady(null);
+
+        syncTarget1.onBufferReady();
+
+        // Only SyncGroup1 will be ready, but SyncGroup2 still needs its own targets to be ready.
+        finishedLatch1.await(1, TimeUnit.SECONDS);
+        finishedLatch2.await(1, TimeUnit.SECONDS);
+
+        assertEquals(0, finishedLatch1.getCount());
+        assertEquals(1, finishedLatch2.getCount());
+
+        syncTarget3.onBufferReady();
+
+        // SyncGroup2 is finished after target3 completed.
+        finishedLatch2.await(1, TimeUnit.SECONDS);
+        assertEquals(0, finishedLatch2.getCount());
+    }
+
+    @Test
+    public void testParentSyncGroupMerge_true() {
+        // Temporarily set a new transaction factory so it will return the stub transaction for
+        // the sync group.
+        SurfaceControl.Transaction parentTransaction = spy(new StubTransaction());
+        SurfaceSyncGroup.setTransactionFactory(() -> parentTransaction);
+
+        final CountDownLatch finishedLatch = new CountDownLatch(1);
+        SurfaceSyncGroup syncGroup = new SurfaceSyncGroup();
+        syncGroup.addSyncCompleteCallback(mExecutor, finishedLatch::countDown);
+
+        SurfaceControl.Transaction targetTransaction = spy(new StubTransaction());
+        SurfaceSyncGroup.setTransactionFactory(() -> targetTransaction);
+
+        SyncTarget syncTarget = new SyncTarget();
+        assertTrue(syncGroup.addToSync(syncTarget, true /* parentSyncGroupMerge */));
+        syncTarget.onTransactionReady(null);
+
+        // When parentSyncGroupMerge is true, the transaction passed in merges the main SyncGroup
+        // transaction first because it knows the previous parentSyncGroup is older so it should
+        // be overwritten by anything newer.
+        verify(targetTransaction).merge(parentTransaction);
+        verify(parentTransaction).merge(targetTransaction);
+    }
+
+    @Test
+    public void testParentSyncGroupMerge_false() {
+        // Temporarily set a new transaction factory so it will return the stub transaction for
+        // the sync group.
+        SurfaceControl.Transaction parentTransaction = spy(new StubTransaction());
+        SurfaceSyncGroup.setTransactionFactory(() -> parentTransaction);
+
+        final CountDownLatch finishedLatch = new CountDownLatch(1);
+        SurfaceSyncGroup syncGroup = new SurfaceSyncGroup();
+        syncGroup.addSyncCompleteCallback(mExecutor, finishedLatch::countDown);
+
+        SurfaceControl.Transaction targetTransaction = spy(new StubTransaction());
+        SurfaceSyncGroup.setTransactionFactory(() -> targetTransaction);
+
+        SyncTarget syncTarget = new SyncTarget();
+        assertTrue(syncGroup.addToSync(syncTarget, false /* parentSyncGroupMerge */));
+        syncTarget.onTransactionReady(null);
+
+        // When parentSyncGroupMerge is false, the transaction passed in should not merge
+        // the main SyncGroup since we don't need to change the transaction order
+        verify(targetTransaction, never()).merge(parentTransaction);
+        verify(parentTransaction).merge(targetTransaction);
+    }
+
+    private static class SyncTarget extends SurfaceSyncGroup {
         void onBufferReady() {
             SurfaceControl.Transaction t = new StubTransaction();
-            mTransactionReadyCallback.onTransactionReady(t);
+            onTransactionReady(t);
         }
     }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/TestIWindow.java b/services/tests/wmtests/src/com/android/server/wm/TestIWindow.java
index 3f8acc6..6e72bf3 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TestIWindow.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TestIWindow.java
@@ -46,7 +46,7 @@
     @Override
     public void resized(ClientWindowFrames frames, boolean reportDraw,
             MergedConfiguration mergedConfig, InsetsState insetsState, boolean forceLayout,
-            boolean alwaysConsumeSystemBars, int displayId, int seqId, boolean dragResizing)
+            boolean alwaysConsumeSystemBars, int displayId, int seqId, int resizeMode)
             throws RemoteException {
     }
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
index 183ccce..69e3244 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -276,9 +276,12 @@
         assertFalse(imeWindow.canBeImeTarget());
 
         // Simulate the window is in split screen root task.
+        final DockedTaskDividerController controller =
+                mDisplayContent.getDockedDividerController();
         final Task rootTask = createTask(mDisplayContent,
                 WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD);
         spyOn(appWindow);
+        spyOn(controller);
         spyOn(rootTask);
         rootTask.setFocusable(false);
         doReturn(rootTask).when(appWindow).getRootTask();
@@ -772,7 +775,7 @@
                     anyBoolean() /* reportDraw */, any() /* mergedConfig */,
                     any() /* insetsState */, anyBoolean() /* forceLayout */,
                     anyBoolean() /* alwaysConsumeSystemBars */, anyInt() /* displayId */,
-                    anyInt() /* seqId */, anyBoolean() /* dragResizing */);
+                    anyInt() /* seqId */, anyInt() /* resizeMode */);
         } catch (RemoteException ignored) {
         }
         win.reportResized();
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/DspTrustedHotwordDetectorSession.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/DspTrustedHotwordDetectorSession.java
index 2812264..9375b59 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/DspTrustedHotwordDetectorSession.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/DspTrustedHotwordDetectorSession.java
@@ -106,12 +106,14 @@
                     }
                     HotwordMetricsLogger.writeKeyphraseTriggerEvent(
                             HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_DSP,
-                            HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__DETECTED);
+                            HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__DETECTED,
+                            mVoiceInteractionServiceUid);
                     if (!mValidatingDspTrigger) {
                         Slog.i(TAG, "Ignoring #onDetected due to a process restart");
                         HotwordMetricsLogger.writeKeyphraseTriggerEvent(
                                 HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_DSP,
-                                METRICS_KEYPHRASE_TRIGGERED_DETECT_UNEXPECTED_CALLBACK);
+                                METRICS_KEYPHRASE_TRIGGERED_DETECT_UNEXPECTED_CALLBACK,
+                                mVoiceInteractionServiceUid);
                         return;
                     }
                     mValidatingDspTrigger = false;
@@ -122,7 +124,8 @@
                         Slog.i(TAG, "Ignoring #onDetected due to a SecurityException", e);
                         HotwordMetricsLogger.writeKeyphraseTriggerEvent(
                                 HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_DSP,
-                                METRICS_KEYPHRASE_TRIGGERED_DETECT_SECURITY_EXCEPTION);
+                                METRICS_KEYPHRASE_TRIGGERED_DETECT_SECURITY_EXCEPTION,
+                                mVoiceInteractionServiceUid);
                         externalCallback.onError(CALLBACK_ONDETECTED_GOT_SECURITY_EXCEPTION);
                         return;
                     }
@@ -157,12 +160,14 @@
                     }
                     HotwordMetricsLogger.writeKeyphraseTriggerEvent(
                             HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_DSP,
-                            HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__REJECTED);
+                            HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__REJECTED,
+                            mVoiceInteractionServiceUid);
                     if (!mValidatingDspTrigger) {
                         Slog.i(TAG, "Ignoring #onRejected due to a process restart");
                         HotwordMetricsLogger.writeKeyphraseTriggerEvent(
                                 HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_DSP,
-                                METRICS_KEYPHRASE_TRIGGERED_REJECT_UNEXPECTED_CALLBACK);
+                                METRICS_KEYPHRASE_TRIGGERED_REJECT_UNEXPECTED_CALLBACK,
+                                mVoiceInteractionServiceUid);
                         return;
                     }
                     mValidatingDspTrigger = false;
@@ -187,7 +192,8 @@
                         Slog.w(TAG, "Timed out on #detectFromDspSource");
                         HotwordMetricsLogger.writeKeyphraseTriggerEvent(
                                 HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_DSP,
-                                HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__DETECT_TIMEOUT);
+                                HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__DETECT_TIMEOUT,
+                                mVoiceInteractionServiceUid);
                         try {
                             externalCallback.onError(CALLBACK_DETECT_TIMEOUT);
                         } catch (RemoteException e) {
@@ -220,7 +226,8 @@
                 mCallback.onRejected(new HotwordRejectedResult.Builder().build());
                 HotwordMetricsLogger.writeKeyphraseTriggerEvent(
                         HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_DSP,
-                        HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__REJECTED_FROM_RESTART);
+                        HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__REJECTED_FROM_RESTART,
+                        mVoiceInteractionServiceUid);
             } catch (RemoteException e) {
                 Slog.w(TAG, "Failed to call #rejected");
                 HotwordMetricsLogger.writeDetectorEvent(
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
index 2a4d4a1..411bad6 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
@@ -165,7 +165,8 @@
                 synchronized (mLock) {
                     restartProcessLocked();
                     HotwordMetricsLogger.writeServiceRestartEvent(mDetectorType,
-                            HOTWORD_DETECTION_SERVICE_RESTARTED__REASON__SCHEDULE);
+                            HOTWORD_DETECTION_SERVICE_RESTARTED__REASON__SCHEDULE,
+                            mVoiceInteractionServiceUid);
                 }
             }, mReStartPeriodSeconds, mReStartPeriodSeconds, TimeUnit.SECONDS);
         }
@@ -200,7 +201,8 @@
             // conditions with audio reading in the service.
             restartProcessLocked();
             HotwordMetricsLogger.writeServiceRestartEvent(mDetectorType,
-                    HOTWORD_DETECTION_SERVICE_RESTARTED__REASON__AUDIO_SERVICE_DIED);
+                    HOTWORD_DETECTION_SERVICE_RESTARTED__REASON__AUDIO_SERVICE_DIED,
+                    mVoiceInteractionServiceUid);
         }
     }
 
@@ -364,11 +366,13 @@
     static final class SoundTriggerCallback extends IRecognitionStatusCallback.Stub {
         private final HotwordDetectionConnection mHotwordDetectionConnection;
         private final IHotwordRecognitionStatusCallback mExternalCallback;
+        private final int mVoiceInteractionServiceUid;
 
         SoundTriggerCallback(IHotwordRecognitionStatusCallback callback,
-                HotwordDetectionConnection connection) {
+                HotwordDetectionConnection connection, int uid) {
             mHotwordDetectionConnection = connection;
             mExternalCallback = callback;
+            mVoiceInteractionServiceUid = uid;
         }
 
         @Override
@@ -381,13 +385,15 @@
             if (useHotwordDetectionService) {
                 HotwordMetricsLogger.writeKeyphraseTriggerEvent(
                         HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__DETECTOR_TYPE__TRUSTED_DETECTOR_DSP,
-                        HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__KEYPHRASE_TRIGGER);
+                        HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__KEYPHRASE_TRIGGER,
+                        mVoiceInteractionServiceUid);
                 mHotwordDetectionConnection.detectFromDspSource(
                         recognitionEvent, mExternalCallback);
             } else {
                 HotwordMetricsLogger.writeKeyphraseTriggerEvent(
                         HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__DETECTOR_TYPE__NORMAL_DETECTOR,
-                        HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__KEYPHRASE_TRIGGER);
+                        HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__KEYPHRASE_TRIGGER,
+                        mVoiceInteractionServiceUid);
                 mExternalCallback.onKeyphraseDetected(recognitionEvent, null);
             }
         }
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectorSession.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectorSession.java
index f9f43c9..742c324 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectorSession.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectorSession.java
@@ -238,7 +238,7 @@
                     try {
                         mCallback.onStatusReported(status);
                         HotwordMetricsLogger.writeServiceInitResultEvent(getDetectorType(),
-                                initResultMetricsResult);
+                                initResultMetricsResult, mVoiceInteractionServiceUid);
                     } catch (RemoteException e) {
                         Slog.w(TAG, "Failed to report initialization status: " + e);
                         HotwordMetricsLogger.writeDetectorEvent(getDetectorType(),
@@ -269,7 +269,7 @@
                 try {
                     mCallback.onStatusReported(INITIALIZATION_STATUS_UNKNOWN);
                     HotwordMetricsLogger.writeServiceInitResultEvent(getDetectorType(),
-                            METRICS_INIT_UNKNOWN_TIMEOUT);
+                            METRICS_INIT_UNKNOWN_TIMEOUT, mVoiceInteractionServiceUid);
                 } catch (RemoteException e) {
                     Slog.w(TAG, "Failed to report initialization status UNKNOWN", e);
                     HotwordMetricsLogger.writeDetectorEvent(getDetectorType(),
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordMetricsLogger.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordMetricsLogger.java
index 940aed3..61c18be 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordMetricsLogger.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordMetricsLogger.java
@@ -64,28 +64,28 @@
     /**
      * Logs information related to hotword detection service init result.
      */
-    public static void writeServiceInitResultEvent(int detectorType, int result) {
+    public static void writeServiceInitResultEvent(int detectorType, int result, int uid) {
         int metricsDetectorType = getInitMetricsDetectorType(detectorType);
         FrameworkStatsLog.write(FrameworkStatsLog.HOTWORD_DETECTION_SERVICE_INIT_RESULT_REPORTED,
-                metricsDetectorType, result);
+                metricsDetectorType, result, uid);
     }
 
     /**
      * Logs information related to hotword detection service restarting.
      */
-    public static void writeServiceRestartEvent(int detectorType, int reason) {
+    public static void writeServiceRestartEvent(int detectorType, int reason, int uid) {
         int metricsDetectorType = getRestartMetricsDetectorType(detectorType);
         FrameworkStatsLog.write(FrameworkStatsLog.HOTWORD_DETECTION_SERVICE_RESTARTED,
-                metricsDetectorType, reason);
+                metricsDetectorType, reason, uid);
     }
 
     /**
      * Logs information related to keyphrase trigger.
      */
-    public static void writeKeyphraseTriggerEvent(int detectorType, int result) {
+    public static void writeKeyphraseTriggerEvent(int detectorType, int result, int uid) {
         int metricsDetectorType = getKeyphraseMetricsDetectorType(detectorType);
         FrameworkStatsLog.write(FrameworkStatsLog.HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED,
-                metricsDetectorType, result);
+                metricsDetectorType, result, uid);
     }
 
     /**
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/SoftwareTrustedHotwordDetectorSession.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/SoftwareTrustedHotwordDetectorSession.java
index 6930689..00aec71 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/SoftwareTrustedHotwordDetectorSession.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/SoftwareTrustedHotwordDetectorSession.java
@@ -101,12 +101,14 @@
                 synchronized (mLock) {
                     HotwordMetricsLogger.writeKeyphraseTriggerEvent(
                             HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_SOFTWARE,
-                            HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__DETECTED);
+                            HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__DETECTED,
+                            mVoiceInteractionServiceUid);
                     if (!mPerformingSoftwareHotwordDetection) {
                         Slog.i(TAG, "Hotword detection has already completed");
                         HotwordMetricsLogger.writeKeyphraseTriggerEvent(
                                 HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_SOFTWARE,
-                                METRICS_KEYPHRASE_TRIGGERED_DETECT_UNEXPECTED_CALLBACK);
+                                METRICS_KEYPHRASE_TRIGGERED_DETECT_UNEXPECTED_CALLBACK,
+                                mVoiceInteractionServiceUid);
                         return;
                     }
                     mPerformingSoftwareHotwordDetection = false;
@@ -115,7 +117,8 @@
                     } catch (SecurityException e) {
                         HotwordMetricsLogger.writeKeyphraseTriggerEvent(
                                 HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_SOFTWARE,
-                                METRICS_KEYPHRASE_TRIGGERED_DETECT_SECURITY_EXCEPTION);
+                                METRICS_KEYPHRASE_TRIGGERED_DETECT_SECURITY_EXCEPTION,
+                                mVoiceInteractionServiceUid);
                         mSoftwareCallback.onError();
                         return;
                     }
@@ -144,7 +147,8 @@
                 }
                 HotwordMetricsLogger.writeKeyphraseTriggerEvent(
                         HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_SOFTWARE,
-                        HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__REJECTED);
+                        HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__REJECTED,
+                        mVoiceInteractionServiceUid);
                 // onRejected isn't allowed here, and we are not expecting it.
             }
         };
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
index 0a660b0..6674520 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
@@ -730,7 +730,7 @@
             Slog.d(TAG, "createSoundTriggerCallbackLocked");
         }
         return new HotwordDetectionConnection.SoundTriggerCallback(callback,
-                mHotwordDetectionConnection);
+                mHotwordDetectionConnection, mInfo.getServiceInfo().applicationInfo.uid);
     }
 
     private static ServiceInfo getServiceInfoLocked(@NonNull ComponentName componentName,
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 9103658..8cd5a85 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -17,6 +17,7 @@
 package android.telephony;
 
 import android.Manifest;
+import android.annotation.CallbackExecutor;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -10337,4 +10338,85 @@
             configs.putPersistableBundle(key, (PersistableBundle) value);
         }
     }
+
+    /**
+     * Listener interface to get a notification when the carrier configurations have changed.
+     *
+     * Use this listener to receive timely updates when the carrier configuration changes. System
+     * components should prefer this listener over {@link #ACTION_CARRIER_CONFIG_CHANGED}
+     * whenever possible.
+     *
+     * To register the listener, call
+     * {@link #registerCarrierConfigChangeListener(Executor, CarrierConfigChangeListener)}.
+     * To unregister, call
+     * {@link #unregisterCarrierConfigChangeListener(CarrierConfigChangeListener)}.
+     *
+     * Note that on registration, registrants will NOT receive a notification on last carrier config
+     * change. Only carrier configs change AFTER the registration will be sent to registrants. And
+     * unlike {@link #ACTION_CARRIER_CONFIG_CHANGED}, notification wouldn't send when the device is
+     * unlocked. Registrants only receive the notification when there has been real carrier config
+     * changes.
+     *
+     * @see #registerCarrierConfigChangeListener(Executor, CarrierConfigChangeListener)
+     * @see #unregisterCarrierConfigChangeListener(CarrierConfigChangeListener)
+     * @see #ACTION_CARRIER_CONFIG_CHANGED
+     * @see #getConfig(String...)
+     * @see #getConfigForSubId(int, String...)
+     */
+    public interface CarrierConfigChangeListener {
+        /**
+         * Called when carrier configurations have changed.
+         *
+         * @param logicalSlotIndex  The logical SIM slot index on which to monitor and get
+         *                          notification. It is guaranteed to be valid.
+         * @param subscriptionId    The subscription on the SIM slot. May be
+         *                          {@link SubscriptionManager#INVALID_SUBSCRIPTION_ID}.
+         * @param carrierId         The optional carrier Id, may be
+         *                          {@link TelephonyManager#UNKNOWN_CARRIER_ID}.
+         *                          See {@link TelephonyManager#getSimCarrierId()}.
+         * @param specificCarrierId The optional fine-grained carrierId, may be {@link
+         *                          TelephonyManager#UNKNOWN_CARRIER_ID}. A specific carrierId may
+         *                          be different from the carrierId above in a MVNO scenario. See
+         *                          detail in {@link TelephonyManager#getSimSpecificCarrierId()}.
+         */
+        void onCarrierConfigChanged(int logicalSlotIndex, int subscriptionId, int carrierId,
+                int specificCarrierId);
+    }
+
+    /**
+     * Register a {@link CarrierConfigChangeListener} to get a notification when carrier
+     * configurations have changed.
+     *
+     * @param executor The executor on which the listener will be called.
+     * @param listener The CarrierConfigChangeListener called when carrier configs has changed.
+     */
+    public void registerCarrierConfigChangeListener(@NonNull @CallbackExecutor Executor executor,
+            @NonNull CarrierConfigChangeListener listener) {
+        Objects.requireNonNull(executor, "Executor should be non-null.");
+        Objects.requireNonNull(listener, "Listener should be non-null.");
+
+        TelephonyRegistryManager trm = mContext.getSystemService(TelephonyRegistryManager.class);
+        if (trm == null) {
+            throw new IllegalStateException("Telephony registry service is null");
+        }
+        trm.addCarrierConfigChangedListener(executor, listener);
+    }
+
+    /**
+     * Unregister the {@link CarrierConfigChangeListener} to stop notification on carrier
+     * configurations change.
+     *
+     * @param listener The CarrierConfigChangeListener which was registered with method
+     * {@link #registerCarrierConfigChangeListener(Executor, CarrierConfigChangeListener)}.
+     */
+    public void unregisterCarrierConfigChangeListener(
+            @NonNull CarrierConfigChangeListener listener) {
+        Objects.requireNonNull(listener, "Listener should be non-null.");
+
+        TelephonyRegistryManager trm = mContext.getSystemService(TelephonyRegistryManager.class);
+        if (trm == null) {
+            throw new IllegalStateException("Telephony registry service is null");
+        }
+        trm.removeCarrierConfigChangedListener(listener);
+    }
 }
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 6985c84..5d49413 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -2935,7 +2935,7 @@
     public static final int NETWORK_TYPE_HSPA = TelephonyProtoEnums.NETWORK_TYPE_HSPA; // = 10.
     /**
      * Current network is iDen
-     * @deprecated Legacy network type no longer being used.
+     * @deprecated Legacy network type no longer being used starting in Android U.
      */
     @Deprecated
     public static final int NETWORK_TYPE_IDEN = TelephonyProtoEnums.NETWORK_TYPE_IDEN; // = 11.
@@ -13960,7 +13960,7 @@
      * If used, will be converted to {@link #NETWORK_TYPE_BITMASK_LTE}.
      * network type bitmask indicating the support of radio tech LTE CA (carrier aggregation).
      *
-     * @deprecated Please use {@link #NETWORK_TYPE_BITMASK_LTE} instead.
+     * @deprecated Please use {@link #NETWORK_TYPE_BITMASK_LTE} instead. Deprecated in Android U.
      */
     @Deprecated
     public static final long NETWORK_TYPE_BITMASK_LTE_CA = (1 << (NETWORK_TYPE_LTE_CA -1));
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt
index afc5f65..ca4c6a3 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt
@@ -106,7 +106,9 @@
     @IwTest(focusArea = "ime")
     override fun cujCompleted() {
         super.cujCompleted()
-        navBarLayerPositionAtStartAndEnd()
+        if (!flicker.scenario.isTablet) {
+            navBarLayerPositionAtStartAndEnd()
+        }
         imeLayerBecomesInvisible()
         imeAppLayerIsAlwaysVisible()
         imeAppWindowIsAlwaysVisible()
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt
index aedf965..730c4f5 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt
@@ -104,7 +104,9 @@
     @IwTest(focusArea = "ime")
     override fun cujCompleted() {
         super.cujCompleted()
-        navBarLayerPositionAtStartAndEnd()
+        if (!flicker.scenario.isTablet) {
+            navBarLayerPositionAtStartAndEnd()
+        }
         imeLayerBecomesInvisible()
         imeAppWindowBecomesInvisible()
         imeWindowBecomesInvisible()
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt
index 54f38c3..2447474 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt
@@ -213,6 +213,14 @@
             // not yet tablet compatible
             appLayerRotates()
             appLayerAlwaysVisible()
+            // not tablet compatible
+            navBarLayerIsVisibleAtStartAndEnd()
+            navBarWindowIsAlwaysVisible()
+        }
+
+        if (flicker.scenario.isTablet) {
+            taskBarLayerIsVisibleAtStartAndEnd()
+            taskBarWindowIsAlwaysVisible()
         }
 
         appWindowFullScreen()
@@ -223,10 +231,6 @@
         appLayerRotates_StartingPos()
         appLayerRotates_EndingPos()
         entireScreenCovered()
-        navBarLayerIsVisibleAtStartAndEnd()
-        navBarWindowIsAlwaysVisible()
-        taskBarLayerIsVisibleAtStartAndEnd()
-        taskBarWindowIsAlwaysVisible()
         visibleLayersShownMoreThanOneConsecutiveEntry()
         visibleWindowsShownMoreThanOneConsecutiveEntry()
     }
diff --git a/tests/vcn/java/android/net/vcn/VcnConfigTest.java b/tests/vcn/java/android/net/vcn/VcnConfigTest.java
index 7ac51b7..b313c9f 100644
--- a/tests/vcn/java/android/net/vcn/VcnConfigTest.java
+++ b/tests/vcn/java/android/net/vcn/VcnConfigTest.java
@@ -16,7 +16,12 @@
 
 package android.net.vcn;
 
+import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
+
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.fail;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
@@ -24,6 +29,7 @@
 import android.annotation.NonNull;
 import android.content.Context;
 import android.os.Parcel;
+import android.util.ArraySet;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
@@ -42,19 +48,36 @@
     private static final Set<VcnGatewayConnectionConfig> GATEWAY_CONNECTION_CONFIGS =
             Collections.singleton(VcnGatewayConnectionConfigTest.buildTestConfig());
 
+    private static final Set<Integer> RESTRICTED_TRANSPORTS = new ArraySet<>();
+
+    static {
+        RESTRICTED_TRANSPORTS.add(TRANSPORT_WIFI);
+        RESTRICTED_TRANSPORTS.add(TRANSPORT_CELLULAR);
+    }
+
     private final Context mContext = mock(Context.class);
 
     // Public visibility for VcnManagementServiceTest
-    public static VcnConfig buildTestConfig(@NonNull Context context) {
+    public static VcnConfig buildTestConfig(
+            @NonNull Context context, Set<Integer> restrictedTransports) {
         VcnConfig.Builder builder = new VcnConfig.Builder(context);
 
         for (VcnGatewayConnectionConfig gatewayConnectionConfig : GATEWAY_CONNECTION_CONFIGS) {
             builder.addGatewayConnectionConfig(gatewayConnectionConfig);
         }
 
+        if (restrictedTransports != null) {
+            builder.setRestrictedUnderlyingNetworkTransports(restrictedTransports);
+        }
+
         return builder.build();
     }
 
+    // Public visibility for VcnManagementServiceTest
+    public static VcnConfig buildTestConfig(@NonNull Context context) {
+        return buildTestConfig(context, null);
+    }
+
     @Before
     public void setUp() throws Exception {
         doReturn(TEST_PACKAGE_NAME).when(mContext).getOpPackageName();
@@ -91,11 +114,25 @@
     }
 
     @Test
-    public void testBuilderAndGetters() {
+    public void testBuilderAndGettersDefaultValues() {
         final VcnConfig config = buildTestConfig(mContext);
 
         assertEquals(TEST_PACKAGE_NAME, config.getProvisioningPackageName());
         assertEquals(GATEWAY_CONNECTION_CONFIGS, config.getGatewayConnectionConfigs());
+        assertFalse(config.isTestModeProfile());
+        assertEquals(
+                Collections.singleton(TRANSPORT_WIFI),
+                config.getRestrictedUnderlyingNetworkTransports());
+    }
+
+    @Test
+    public void testBuilderAndGettersConfigRestrictedTransports() {
+        final VcnConfig config = buildTestConfig(mContext, RESTRICTED_TRANSPORTS);
+
+        assertEquals(TEST_PACKAGE_NAME, config.getProvisioningPackageName());
+        assertEquals(GATEWAY_CONNECTION_CONFIGS, config.getGatewayConnectionConfigs());
+        assertFalse(config.isTestModeProfile());
+        assertEquals(RESTRICTED_TRANSPORTS, config.getRestrictedUnderlyingNetworkTransports());
     }
 
     @Test
@@ -106,6 +143,24 @@
     }
 
     @Test
+    public void testPersistableBundleWithRestrictedTransports() {
+        final VcnConfig config = buildTestConfig(mContext, RESTRICTED_TRANSPORTS);
+
+        assertEquals(config, new VcnConfig(config.toPersistableBundle()));
+    }
+
+    @Test
+    public void testEqualityWithRestrictedTransports() {
+        final VcnConfig config = buildTestConfig(mContext, RESTRICTED_TRANSPORTS);
+        final VcnConfig configEqual = buildTestConfig(mContext, RESTRICTED_TRANSPORTS);
+        final VcnConfig configNotEqual =
+                buildTestConfig(mContext, Collections.singleton(TRANSPORT_WIFI));
+
+        assertEquals(config, configEqual);
+        assertNotEquals(config, configNotEqual);
+    }
+
+    @Test
     public void testParceling() {
         final VcnConfig config = buildTestConfig(mContext);
 
diff --git a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
index 258642ac..075bc5e 100644
--- a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
+++ b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
@@ -258,7 +258,7 @@
 
         doReturn(Collections.singleton(TRANSPORT_WIFI))
                 .when(mMockDeps)
-                .getRestrictedTransports(any(), any());
+                .getRestrictedTransports(any(), any(), any());
     }
 
 
@@ -1038,18 +1038,18 @@
                 new LinkProperties());
     }
 
-    private void checkGetRestrictedTransports(
+    private void checkGetRestrictedTransportsFromCarrierConfig(
             ParcelUuid subGrp,
             TelephonySubscriptionSnapshot lastSnapshot,
             Set<Integer> expectedTransports) {
         Set<Integer> result =
                 new VcnManagementService.Dependencies()
-                        .getRestrictedTransports(subGrp, lastSnapshot);
+                        .getRestrictedTransportsFromCarrierConfig(subGrp, lastSnapshot);
         assertEquals(expectedTransports, result);
     }
 
     @Test
-    public void testGetRestrictedTransports() {
+    public void testGetRestrictedTransportsFromCarrierConfig() {
         final Set<Integer> restrictedTransports = new ArraySet<>();
         restrictedTransports.add(TRANSPORT_CELLULAR);
         restrictedTransports.add(TRANSPORT_WIFI);
@@ -1065,11 +1065,12 @@
                 mock(TelephonySubscriptionSnapshot.class);
         doReturn(carrierConfig).when(lastSnapshot).getCarrierConfigForSubGrp(eq(TEST_UUID_2));
 
-        checkGetRestrictedTransports(TEST_UUID_2, lastSnapshot, restrictedTransports);
+        checkGetRestrictedTransportsFromCarrierConfig(
+                TEST_UUID_2, lastSnapshot, restrictedTransports);
     }
 
     @Test
-    public void testGetRestrictedTransports_noRestrictPolicyConfigured() {
+    public void testGetRestrictedTransportsFromCarrierConfig_noRestrictPolicyConfigured() {
         final Set<Integer> restrictedTransports = Collections.singleton(TRANSPORT_WIFI);
 
         final PersistableBundleWrapper carrierConfig =
@@ -1078,17 +1079,54 @@
                 mock(TelephonySubscriptionSnapshot.class);
         doReturn(carrierConfig).when(lastSnapshot).getCarrierConfigForSubGrp(eq(TEST_UUID_2));
 
-        checkGetRestrictedTransports(TEST_UUID_2, lastSnapshot, restrictedTransports);
+        checkGetRestrictedTransportsFromCarrierConfig(
+                TEST_UUID_2, lastSnapshot, restrictedTransports);
     }
 
     @Test
-    public void testGetRestrictedTransports_noCarrierConfig() {
+    public void testGetRestrictedTransportsFromCarrierConfig_noCarrierConfig() {
         final Set<Integer> restrictedTransports = Collections.singleton(TRANSPORT_WIFI);
 
         final TelephonySubscriptionSnapshot lastSnapshot =
                 mock(TelephonySubscriptionSnapshot.class);
 
-        checkGetRestrictedTransports(TEST_UUID_2, lastSnapshot, restrictedTransports);
+        checkGetRestrictedTransportsFromCarrierConfig(
+                TEST_UUID_2, lastSnapshot, restrictedTransports);
+    }
+
+    @Test
+    public void testGetRestrictedTransportsFromCarrierConfigAndVcnConfig() {
+        // Configure restricted transport in CarrierConfig
+        final Set<Integer> restrictedTransportInCarrierConfig =
+                Collections.singleton(TRANSPORT_WIFI);
+
+        PersistableBundle carrierConfigBundle = new PersistableBundle();
+        carrierConfigBundle.putIntArray(
+                VCN_RESTRICTED_TRANSPORTS_INT_ARRAY_KEY,
+                restrictedTransportInCarrierConfig.stream().mapToInt(i -> i).toArray());
+        final PersistableBundleWrapper carrierConfig =
+                new PersistableBundleWrapper(carrierConfigBundle);
+
+        final TelephonySubscriptionSnapshot lastSnapshot =
+                mock(TelephonySubscriptionSnapshot.class);
+        doReturn(carrierConfig).when(lastSnapshot).getCarrierConfigForSubGrp(eq(TEST_UUID_2));
+
+        // Configure restricted transport in VcnConfig
+        final Context mockContext = mock(Context.class);
+        doReturn(TEST_PACKAGE_NAME).when(mockContext).getOpPackageName();
+        final VcnConfig vcnConfig =
+                VcnConfigTest.buildTestConfig(
+                        mockContext, Collections.singleton(TRANSPORT_CELLULAR));
+
+        // Verifications
+        final Set<Integer> expectedTransports = new ArraySet<>();
+        expectedTransports.add(TRANSPORT_CELLULAR);
+        expectedTransports.add(TRANSPORT_WIFI);
+
+        Set<Integer> result =
+                new VcnManagementService.Dependencies()
+                        .getRestrictedTransports(TEST_UUID_2, lastSnapshot, vcnConfig);
+        assertEquals(expectedTransports, result);
     }
 
     private void checkGetUnderlyingNetworkPolicy(
@@ -1103,7 +1141,7 @@
         if (isTransportRestricted) {
             restrictedTransports.add(transportType);
         }
-        doReturn(restrictedTransports).when(mMockDeps).getRestrictedTransports(any(), any());
+        doReturn(restrictedTransports).when(mMockDeps).getRestrictedTransports(any(), any(), any());
 
         final VcnUnderlyingNetworkPolicy policy =
                 startVcnAndGetPolicyForTransport(
@@ -1201,7 +1239,7 @@
     public void testGetUnderlyingNetworkPolicyCell_restrictWifi() throws Exception {
         doReturn(Collections.singleton(TRANSPORT_WIFI))
                 .when(mMockDeps)
-                .getRestrictedTransports(any(), any());
+                .getRestrictedTransports(any(), any(), any());
 
         setupSubscriptionAndStartVcn(TEST_SUBSCRIPTION_ID, TEST_UUID_2, true /* isVcnActive */);
 
@@ -1344,6 +1382,23 @@
     }
 
     @Test
+    public void testVcnConfigChangeUpdatesPolicyListener() throws Exception {
+        setupActiveSubscription(TEST_UUID_2);
+
+        mVcnMgmtSvc.setVcnConfig(TEST_UUID_2, TEST_VCN_CONFIG, TEST_PACKAGE_NAME);
+        mVcnMgmtSvc.addVcnUnderlyingNetworkPolicyListener(mMockPolicyListener);
+
+        final Context mockContext = mock(Context.class);
+        doReturn(TEST_PACKAGE_NAME).when(mockContext).getOpPackageName();
+        final VcnConfig vcnConfig =
+                VcnConfigTest.buildTestConfig(
+                        mockContext, Collections.singleton(TRANSPORT_CELLULAR));
+        mVcnMgmtSvc.setVcnConfig(TEST_UUID_2, vcnConfig, TEST_PACKAGE_NAME);
+
+        verify(mMockPolicyListener).onPolicyChanged();
+    }
+
+    @Test
     public void testRemoveVcnUpdatesPolicyListener() throws Exception {
         setupActiveSubscription(TEST_UUID_2);
 
@@ -1375,7 +1430,7 @@
         setupActiveSubscription(TEST_UUID_2);
 
         mVcnMgmtSvc.setVcnConfig(TEST_UUID_2, TEST_VCN_CONFIG, TEST_PACKAGE_NAME);
-        mVcnMgmtSvc.addVcnUnderlyingNetworkPolicyListenerForTest(mMockPolicyListener);
+        mVcnMgmtSvc.addVcnUnderlyingNetworkPolicyListener(mMockPolicyListener);
 
         final TelephonySubscriptionSnapshot snapshot =
                 buildSubscriptionSnapshot(
diff --git a/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionDetector.kt b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionDetector.kt
index bcef917..2665b3c 100644
--- a/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionDetector.kt
+++ b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionDetector.kt
@@ -31,10 +31,11 @@
 import com.android.tools.lint.detector.api.Severity
 import com.android.tools.lint.detector.api.SourceCodeScanner
 import com.intellij.psi.PsiAnnotation
+import com.intellij.psi.PsiArrayInitializerMemberValue
 import com.intellij.psi.PsiClass
+import com.intellij.psi.PsiElement
 import com.intellij.psi.PsiMethod
 import org.jetbrains.uast.UAnnotation
-import org.jetbrains.uast.UClass
 import org.jetbrains.uast.UElement
 import org.jetbrains.uast.UMethod
 
@@ -65,6 +66,12 @@
         return listOf(UAnnotation::class.java)
     }
 
+    private fun annotationValueGetChildren(elem: PsiElement): Array<PsiElement> {
+        if (elem is PsiArrayInitializerMemberValue)
+            return elem.getInitializers().map { it as PsiElement }.toTypedArray()
+        return elem.getChildren()
+    }
+
     private fun areAnnotationsEquivalent(
         context: JavaContext,
         anno1: PsiAnnotation,
@@ -82,18 +89,28 @@
             if (attr1[i].name != attr2[i].name) {
                 return false
             }
-            val value1 = attr1[i].value
-            val value2 = attr2[i].value
-            if (value1 == null && value2 == null) {
-                continue
-            }
-            if (value1 == null || value2 == null) {
-                return false
-            }
+            val value1 = attr1[i].value ?: return false
+            val value2 = attr2[i].value ?: return false
+            // Try to compare values directly with each other.
             val v1 = ConstantEvaluator.evaluate(context, value1)
             val v2 = ConstantEvaluator.evaluate(context, value2)
-            if (v1 != v2) {
-                return false
+            if (v1 != null && v2 != null) {
+                if (v1 != v2) {
+                    return false
+                }
+            } else {
+                val children1 = annotationValueGetChildren(value1)
+                val children2 = annotationValueGetChildren(value2)
+                if (children1.size != children2.size) {
+                    return false
+                }
+                for (j in children1.indices) {
+                    val c1 = ConstantEvaluator.evaluate(context, children1[j])
+                    val c2 = ConstantEvaluator.evaluate(context, children2[j])
+                    if (c1 != c2) {
+                        return false
+                    }
+                }
             }
         }
         return true
@@ -140,50 +157,13 @@
         }
     }
 
-    private fun compareClasses(
-        context: JavaContext,
-        element: UElement,
-        newClass: PsiClass,
-        extendedClass: PsiClass,
-        checkEquivalence: Boolean = true
-    ) {
-        val newAnnotation = newClass.getAnnotation(ANNOTATION_ENFORCE_PERMISSION)
-        val extendedAnnotation = extendedClass.getAnnotation(ANNOTATION_ENFORCE_PERMISSION)
-
-        val location = context.getLocation(element)
-        val newClassName = newClass.qualifiedName
-        val extendedClassName = extendedClass.qualifiedName
-        if (newAnnotation == null) {
-            val msg = "The class $newClassName extends the class $extendedClassName which " +
-                "is annotated with @EnforcePermission. The same annotation must be used " +
-                "on $newClassName."
-            context.report(ISSUE_MISSING_ENFORCE_PERMISSION, element, location, msg)
-        } else if (extendedAnnotation == null) {
-            val msg = "The class $newClassName extends the class $extendedClassName which " +
-                "is not annotated with @EnforcePermission. The same annotation must be used " +
-                "on $extendedClassName. Did you forget to annotate the AIDL definition?"
-            context.report(ISSUE_MISSING_ENFORCE_PERMISSION, element, location, msg)
-        } else if (checkEquivalence && !areAnnotationsEquivalent(
-            context, newAnnotation, extendedAnnotation)) {
-            val msg = "The class $newClassName is annotated with ${newAnnotation.text} " +
-                "which differs from the parent class $extendedClassName: " +
-                "${extendedAnnotation.text}. The same annotation must be used for " +
-                "both classes."
-            context.report(ISSUE_MISMATCHING_ENFORCE_PERMISSION, element, location, msg)
-        }
-    }
-
     override fun visitAnnotationUsage(
         context: JavaContext,
         element: UElement,
         annotationInfo: AnnotationInfo,
         usageInfo: AnnotationUsageInfo
     ) {
-        if (usageInfo.type == AnnotationUsageType.EXTENDS) {
-            val newClass = element.sourcePsi?.parent?.parent as PsiClass
-            val extendedClass: PsiClass = usageInfo.referenced as PsiClass
-            compareClasses(context, element, newClass, extendedClass)
-        } else if (usageInfo.type == AnnotationUsageType.METHOD_OVERRIDE &&
+        if (usageInfo.type == AnnotationUsageType.METHOD_OVERRIDE &&
             annotationInfo.origin == AnnotationOrigin.METHOD) {
             val overridingMethod = element.sourcePsi as PsiMethod
             val overriddenMethod = usageInfo.referenced as PsiMethod
@@ -198,17 +178,7 @@
                     return
                 }
                 val method = node.uastParent as? UMethod
-                val klass = node.uastParent as? UClass
-                if (klass != null) {
-                    val newClass = klass as PsiClass
-                    val extendedClass = newClass.getSuperClass()
-                    if (extendedClass != null && extendedClass.qualifiedName != JAVA_OBJECT) {
-                        // The equivalence check can be skipped, if both classes are
-                        // annotated, it will be verified by visitAnnotationUsage.
-                        compareClasses(context, klass, newClass,
-                            extendedClass, checkEquivalence = false)
-                    }
-                } else if (method != null) {
+                if (method != null) {
                     val overridingMethod = method as PsiMethod
                     val parents = overridingMethod.findSuperMethods()
                     for (overriddenMethod in parents) {
diff --git a/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionDetectorTest.kt b/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionDetectorTest.kt
index 618bbcc..4ed68a8 100644
--- a/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionDetectorTest.kt
+++ b/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionDetectorTest.kt
@@ -33,21 +33,6 @@
 
     override fun lint(): TestLintTask = super.lint().allowMissingSdk(true)
 
-    fun testDoesNotDetectIssuesCorrectAnnotationOnClass() {
-        lint().files(java(
-            """
-            package test.pkg;
-            @android.annotation.EnforcePermission(android.Manifest.permission.READ_PHONE_STATE)
-            public class TestClass1 extends IFoo.Stub {
-                public void testMethod() {}
-            }
-            """).indented(),
-                *stubs
-        )
-        .run()
-        .expectClean()
-    }
-
     fun testDoesNotDetectIssuesCorrectAnnotationOnMethod() {
         lint().files(java(
             """
@@ -65,26 +50,72 @@
         .expectClean()
     }
 
-    fun testDetectIssuesMismatchingAnnotationOnClass() {
+    fun testDoesNotDetectIssuesCorrectAnnotationAllOnMethod() {
         lint().files(java(
             """
             package test.pkg;
-            @android.annotation.EnforcePermission(android.Manifest.permission.INTERNET)
-            public class TestClass3 extends IFoo.Stub {
-                public void testMethod() {}
+            import android.annotation.EnforcePermission;
+            public class TestClass11 extends IFooMethod.Stub {
+                @Override
+                @EnforcePermission(allOf={android.Manifest.permission.INTERNET, android.Manifest.permission.READ_PHONE_STATE})
+                public void testMethodAll() {}
             }
             """).indented(),
                 *stubs
         )
         .run()
-        .expect("""src/test/pkg/TestClass3.java:3: Error: The class test.pkg.TestClass3 is \
-annotated with @android.annotation.EnforcePermission(android.Manifest.permission.INTERNET) \
-which differs from the parent class IFoo.Stub: \
-@android.annotation.EnforcePermission(android.Manifest.permission.READ_PHONE_STATE). The \
-same annotation must be used for both classes. [MismatchingEnforcePermissionAnnotation]
-public class TestClass3 extends IFoo.Stub {
-                                ~~~~~~~~~
-1 errors, 0 warnings""".addLineContinuation())
+        .expectClean()
+    }
+
+    fun testDoesNotDetectIssuesCorrectAnnotationAllLiteralOnMethod() {
+        lint().files(java(
+            """
+            package test.pkg;
+            import android.annotation.EnforcePermission;
+            public class TestClass111 extends IFooMethod.Stub {
+                @Override
+                @EnforcePermission(allOf={"android.permission.INTERNET", android.Manifest.permission.READ_PHONE_STATE})
+                public void testMethodAllLiteral() {}
+            }
+            """).indented(),
+                *stubs
+        )
+        .run()
+        .expectClean()
+    }
+
+    fun testDoesNotDetectIssuesCorrectAnnotationAnyOnMethod() {
+        lint().files(java(
+            """
+            package test.pkg;
+            import android.annotation.EnforcePermission;
+            public class TestClass12 extends IFooMethod.Stub {
+                @Override
+                @EnforcePermission(anyOf={android.Manifest.permission.INTERNET, android.Manifest.permission.READ_PHONE_STATE})
+                public void testMethodAny() {}
+            }
+            """).indented(),
+                *stubs
+        )
+        .run()
+        .expectClean()
+    }
+
+    fun testDoesNotDetectIssuesCorrectAnnotationAnyLiteralOnMethod() {
+        lint().files(java(
+            """
+            package test.pkg;
+            import android.annotation.EnforcePermission;
+            public class TestClass121 extends IFooMethod.Stub {
+                @Override
+                @EnforcePermission(anyOf={"android.permission.INTERNET", android.Manifest.permission.READ_PHONE_STATE})
+                public void testMethodAnyLiteral() {}
+            }
+            """).indented(),
+                *stubs
+        )
+        .run()
+        .expectClean()
     }
 
     fun testDetectIssuesMismatchingAnnotationOnMethod() {
@@ -99,33 +130,132 @@
                 *stubs
         )
         .run()
-        .expect("""src/test/pkg/TestClass4.java:4: Error: The method TestClass4.testMethod is \
-annotated with @android.annotation.EnforcePermission(android.Manifest.permission.INTERNET) \
-which differs from the overridden method Stub.testMethod: \
-@android.annotation.EnforcePermission(android.Manifest.permission.READ_PHONE_STATE). The same \
-annotation must be used for both methods. [MismatchingEnforcePermissionAnnotation]
-    public void testMethod() {}
-                ~~~~~~~~~~
-1 errors, 0 warnings""".addLineContinuation())
+        .expect("""
+                src/test/pkg/TestClass4.java:4: Error: The method TestClass4.testMethod is annotated with @android.annotation.EnforcePermission(android.Manifest.permission.INTERNET) \
+                which differs from the overridden method Stub.testMethod: @android.annotation.EnforcePermission(android.Manifest.permission.READ_PHONE_STATE). \
+                The same annotation must be used for both methods. [MismatchingEnforcePermissionAnnotation]
+                    public void testMethod() {}
+                                ~~~~~~~~~~
+                1 errors, 0 warnings
+                """.addLineContinuation())
     }
 
-    fun testDetectIssuesMissingAnnotationOnClass() {
+    fun testDetectIssuesEmptyAnnotationOnMethod() {
         lint().files(java(
             """
             package test.pkg;
-            public class TestClass5 extends IFoo.Stub {
+            public class TestClass41 extends IFooMethod.Stub {
+                @android.annotation.EnforcePermission
                 public void testMethod() {}
             }
             """).indented(),
                 *stubs
         )
         .run()
-        .expect("""src/test/pkg/TestClass5.java:2: Error: The class test.pkg.TestClass5 extends \
-the class IFoo.Stub which is annotated with @EnforcePermission. The same annotation must be \
-used on test.pkg.TestClass5. [MissingEnforcePermissionAnnotation]
-public class TestClass5 extends IFoo.Stub {
-                                ~~~~~~~~~
-1 errors, 0 warnings""".addLineContinuation())
+        .expect("""
+                src/test/pkg/TestClass41.java:4: Error: The method TestClass41.testMethod is annotated with @android.annotation.EnforcePermission \
+                which differs from the overridden method Stub.testMethod: @android.annotation.EnforcePermission(android.Manifest.permission.READ_PHONE_STATE). \
+                The same annotation must be used for both methods. [MismatchingEnforcePermissionAnnotation]
+                    public void testMethod() {}
+                                ~~~~~~~~~~
+                1 errors, 0 warnings
+                """.addLineContinuation())
+    }
+
+    fun testDetectIssuesMismatchingAnyAnnotationOnMethod() {
+        lint().files(java(
+            """
+            package test.pkg;
+            public class TestClass9 extends IFooMethod.Stub {
+                @android.annotation.EnforcePermission(anyOf={android.Manifest.permission.INTERNET, android.Manifest.permission.NFC})
+                public void testMethodAny() {}
+            }
+            """).indented(),
+                *stubs
+        )
+        .run()
+        .expect("""
+                src/test/pkg/TestClass9.java:4: Error: The method TestClass9.testMethodAny is annotated with \
+                @android.annotation.EnforcePermission(anyOf={android.Manifest.permission.INTERNET, android.Manifest.permission.NFC}) \
+                which differs from the overridden method Stub.testMethodAny: \
+                @android.annotation.EnforcePermission(anyOf={android.Manifest.permission.INTERNET, android.Manifest.permission.READ_PHONE_STATE}). \
+                The same annotation must be used for both methods. [MismatchingEnforcePermissionAnnotation]
+                    public void testMethodAny() {}
+                                ~~~~~~~~~~~~~
+                1 errors, 0 warnings
+                """.addLineContinuation())
+    }
+
+    fun testDetectIssuesMismatchingAnyLiteralAnnotationOnMethod() {
+        lint().files(java(
+            """
+            package test.pkg;
+            public class TestClass91 extends IFooMethod.Stub {
+                @android.annotation.EnforcePermission(anyOf={"android.permission.INTERNET", "android.permissionoopsthisisatypo.READ_PHONE_STATE"})
+                public void testMethodAnyLiteral() {}
+            }
+            """).indented(),
+                *stubs
+        )
+        .run()
+        .expect("""
+                src/test/pkg/TestClass91.java:4: Error: The method TestClass91.testMethodAnyLiteral is annotated with \
+                @android.annotation.EnforcePermission(anyOf={"android.permission.INTERNET", "android.permissionoopsthisisatypo.READ_PHONE_STATE"}) \
+                which differs from the overridden method Stub.testMethodAnyLiteral: \
+                @android.annotation.EnforcePermission(anyOf={android.Manifest.permission.INTERNET, "android.permission.READ_PHONE_STATE"}). \
+                The same annotation must be used for both methods. [MismatchingEnforcePermissionAnnotation]
+                    public void testMethodAnyLiteral() {}
+                                ~~~~~~~~~~~~~~~~~~~~
+                1 errors, 0 warnings
+                """.addLineContinuation())
+    }
+
+    fun testDetectIssuesMismatchingAllAnnotationOnMethod() {
+        lint().files(java(
+            """
+            package test.pkg;
+            public class TestClass10 extends IFooMethod.Stub {
+                @android.annotation.EnforcePermission(allOf={android.Manifest.permission.INTERNET, android.Manifest.permission.NFC})
+                public void testMethodAll() {}
+            }
+            """).indented(),
+                *stubs
+        )
+        .run()
+        .expect("""
+                src/test/pkg/TestClass10.java:4: Error: The method TestClass10.testMethodAll is annotated with \
+                @android.annotation.EnforcePermission(allOf={android.Manifest.permission.INTERNET, android.Manifest.permission.NFC}) \
+                which differs from the overridden method Stub.testMethodAll: \
+                @android.annotation.EnforcePermission(allOf={android.Manifest.permission.INTERNET, android.Manifest.permission.READ_PHONE_STATE}). \
+                The same annotation must be used for both methods. [MismatchingEnforcePermissionAnnotation]
+                    public void testMethodAll() {}
+                                ~~~~~~~~~~~~~
+                1 errors, 0 warnings
+                """.addLineContinuation())
+    }
+
+    fun testDetectIssuesMismatchingAllLiteralAnnotationOnMethod() {
+        lint().files(java(
+            """
+            package test.pkg;
+            public class TestClass101 extends IFooMethod.Stub {
+                @android.annotation.EnforcePermission(allOf={"android.permission.INTERNET", "android.permissionoopsthisisatypo.READ_PHONE_STATE"})
+                public void testMethodAllLiteral() {}
+            }
+            """).indented(),
+                *stubs
+        )
+        .run()
+        .expect("""
+                src/test/pkg/TestClass101.java:4: Error: The method TestClass101.testMethodAllLiteral is annotated with \
+                @android.annotation.EnforcePermission(allOf={"android.permission.INTERNET", "android.permissionoopsthisisatypo.READ_PHONE_STATE"}) \
+                which differs from the overridden method Stub.testMethodAllLiteral: \
+                @android.annotation.EnforcePermission(allOf={android.Manifest.permission.INTERNET, "android.permission.READ_PHONE_STATE"}). \
+                The same annotation must be used for both methods. [MismatchingEnforcePermissionAnnotation]
+                    public void testMethodAllLiteral() {}
+                                ~~~~~~~~~~~~~~~~~~~~
+                1 errors, 0 warnings
+                """.addLineContinuation())
     }
 
     fun testDetectIssuesMissingAnnotationOnMethod() {
@@ -139,12 +269,13 @@
                 *stubs
         )
         .run()
-        .expect("""src/test/pkg/TestClass6.java:3: Error: The method TestClass6.testMethod \
-overrides the method Stub.testMethod which is annotated with @EnforcePermission. The same \
-annotation must be used on TestClass6.testMethod [MissingEnforcePermissionAnnotation]
-    public void testMethod() {}
-                ~~~~~~~~~~
-1 errors, 0 warnings""".addLineContinuation())
+        .expect("""
+                src/test/pkg/TestClass6.java:3: Error: The method TestClass6.testMethod overrides the method Stub.testMethod which is annotated with @EnforcePermission. \
+                The same annotation must be used on TestClass6.testMethod [MissingEnforcePermissionAnnotation]
+                    public void testMethod() {}
+                                ~~~~~~~~~~
+                1 errors, 0 warnings
+                """.addLineContinuation())
     }
 
     fun testDetectIssuesExtraAnnotationMethod() {
@@ -159,34 +290,13 @@
                 *stubs
         )
         .run()
-        .expect("""src/test/pkg/TestClass7.java:4: Error: The method TestClass7.testMethod \
-overrides the method Stub.testMethod which is not annotated with @EnforcePermission. The same \
-annotation must be used on Stub.testMethod. Did you forget to annotate the AIDL definition? \
-[MissingEnforcePermissionAnnotation]
-    public void testMethod() {}
-                ~~~~~~~~~~
-1 errors, 0 warnings""".addLineContinuation())
-    }
-
-    fun testDetectIssuesExtraAnnotationInterface() {
-        lint().files(java(
-            """
-            package test.pkg;
-            @android.annotation.EnforcePermission(android.Manifest.permission.INTERNET)
-            public class TestClass8 extends IBar.Stub {
-                public void testMethod() {}
-            }
-            """).indented(),
-                *stubs
-        )
-        .run()
-        .expect("""src/test/pkg/TestClass8.java:2: Error: The class test.pkg.TestClass8 \
-extends the class IBar.Stub which is not annotated with @EnforcePermission. The same annotation \
-must be used on IBar.Stub. Did you forget to annotate the AIDL definition? \
-[MissingEnforcePermissionAnnotation]
-@android.annotation.EnforcePermission(android.Manifest.permission.INTERNET)
-^
-1 errors, 0 warnings""".addLineContinuation())
+        .expect("""
+                src/test/pkg/TestClass7.java:4: Error: The method TestClass7.testMethod overrides the method Stub.testMethod which is not annotated with @EnforcePermission. \
+                The same annotation must be used on Stub.testMethod. Did you forget to annotate the AIDL definition? [MissingEnforcePermissionAnnotation]
+                    public void testMethod() {}
+                                ~~~~~~~~~~
+                1 errors, 0 warnings
+                """.addLineContinuation())
     }
 
     fun testDetectIssuesMissingAnnotationOnMethodWhenClassIsCalledDefault() {
@@ -213,21 +323,6 @@
 
     /* Stubs */
 
-    // A service with permission annotation on the class.
-    private val interfaceIFooStub: TestFile = java(
-        """
-        @android.annotation.EnforcePermission(android.Manifest.permission.READ_PHONE_STATE)
-        public interface IFoo {
-         @android.annotation.EnforcePermission(android.Manifest.permission.READ_PHONE_STATE)
-         public static abstract class Stub extends android.os.Binder implements IFoo {
-           @Override
-           public void testMethod() {}
-         }
-         public void testMethod();
-        }
-        """
-    ).indented()
-
     // A service with permission annotation on the method.
     private val interfaceIFooMethodStub: TestFile = java(
         """
@@ -236,9 +331,29 @@
             @Override
             @android.annotation.EnforcePermission(android.Manifest.permission.READ_PHONE_STATE)
             public void testMethod() {}
+            @Override
+            @android.annotation.EnforcePermission(anyOf={android.Manifest.permission.INTERNET, android.Manifest.permission.READ_PHONE_STATE})
+            public void testMethodAny() {}
+            @Override
+            @android.annotation.EnforcePermission(anyOf={android.Manifest.permission.INTERNET, "android.permission.READ_PHONE_STATE"})
+            public void testMethodAnyLiteral() {}
+            @Override
+            @android.annotation.EnforcePermission(allOf={android.Manifest.permission.INTERNET, android.Manifest.permission.READ_PHONE_STATE})
+            public void testMethodAll() {}
+            @Override
+            @android.annotation.EnforcePermission(allOf={android.Manifest.permission.INTERNET, "android.permission.READ_PHONE_STATE"})
+            public void testMethodAllLiteral() {}
           }
           @android.annotation.EnforcePermission(android.Manifest.permission.READ_PHONE_STATE)
           public void testMethod();
+          @android.annotation.EnforcePermission(anyOf={android.Manifest.permission.INTERNET, android.Manifest.permission.READ_PHONE_STATE})
+          public void testMethodAny() {}
+          @android.annotation.EnforcePermission(anyOf={android.Manifest.permission.INTERNET, "android.permission.READ_PHONE_STATE"})
+          public void testMethodAnyLiteral() {}
+          @android.annotation.EnforcePermission(allOf={android.Manifest.permission.INTERNET, android.Manifest.permission.READ_PHONE_STATE})
+          public void testMethodAll() {}
+          @android.annotation.EnforcePermission(allOf={android.Manifest.permission.INTERNET, "android.permission.READ_PHONE_STATE"})
+          public void testMethodAllLiteral() {}
         }
         """
     ).indented()
@@ -261,6 +376,7 @@
         package android.Manifest;
         class permission {
           public static final String READ_PHONE_STATE = "android.permission.READ_PHONE_STATE";
+          public static final String NFC = "android.permission.NFC";
           public static final String INTERNET = "android.permission.INTERNET";
         }
         """
@@ -273,7 +389,7 @@
         """
     ).indented()
 
-    private val stubs = arrayOf(interfaceIFooStub, interfaceIFooMethodStub, interfaceIBarStub,
+    private val stubs = arrayOf(interfaceIFooMethodStub, interfaceIBarStub,
             manifestPermissionStub, enforcePermissionAnnotationStub)
 
     // Substitutes "backslash + new line" with an empty string to imitate line continuation