Merge "Enables the rotation suggestion button during setup wizard"
diff --git a/Android.bp b/Android.bp
index 54983d6..3b8ef61 100644
--- a/Android.bp
+++ b/Android.bp
@@ -156,7 +156,6 @@
         "framework-scheduling.stubs.module_lib",
         "framework-sdkextensions.stubs.module_lib",
         "framework-statsd.stubs.module_lib",
-        "framework-supplementalapi.stubs.module_lib",
         "framework-supplementalprocess.stubs.module_lib",
         "framework-tethering.stubs.module_lib",
         "framework-uwb.stubs.module_lib",
@@ -181,7 +180,6 @@
         "framework-scheduling.impl",
         "framework-sdkextensions.impl",
         "framework-statsd.impl",
-        "framework-supplementalapi.impl",
         "framework-supplementalprocess.impl",
         "framework-tethering.impl",
         "framework-uwb.impl",
diff --git a/StubLibraries.bp b/StubLibraries.bp
index 92c63c9..3b11036 100644
--- a/StubLibraries.bp
+++ b/StubLibraries.bp
@@ -250,7 +250,6 @@
     "framework-scheduling.stubs",
     "framework-sdkextensions.stubs",
     "framework-statsd.stubs",
-    "framework-supplementalapi.stubs",
     "framework-supplementalprocess.stubs",
     "framework-tethering.stubs",
     "framework-uwb.stubs",
@@ -273,7 +272,6 @@
     "framework-scheduling.stubs.system",
     "framework-sdkextensions.stubs.system",
     "framework-statsd.stubs.system",
-    "framework-supplementalapi.stubs",
     "framework-supplementalprocess.stubs",
     "framework-tethering.stubs.system",
     "framework-uwb.stubs.system",
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/Agent.java b/apex/jobscheduler/service/java/com/android/server/tare/Agent.java
index 005c447..a6a007f 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/Agent.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/Agent.java
@@ -46,6 +46,7 @@
 import android.util.Log;
 import android.util.Slog;
 import android.util.SparseArrayMap;
+import android.util.TimeUtils;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
@@ -681,8 +682,8 @@
             final long minBalance = mIrs.getMinBalanceLocked(userId, pkgName);
             final double perc = batteryLevel / 100d;
             // TODO: maybe don't give credits to bankrupt apps until battery level >= 50%
-            if (ledger.getCurrentBalance() < minBalance) {
-                final long shortfall = minBalance - getBalanceLocked(userId, pkgName);
+            final long shortfall = minBalance - ledger.getCurrentBalance();
+            if (shortfall > 0) {
                 recordTransactionLocked(userId, pkgName, ledger,
                         new Ledger.Transaction(now, now, REGULATION_BASIC_INCOME,
                                 null, (long) (perc * shortfall)), true);
@@ -1170,5 +1171,57 @@
     void dumpLocked(IndentingPrintWriter pw) {
         pw.println();
         mBalanceThresholdAlarmQueue.dump(pw);
+
+        pw.println();
+        pw.println("Ongoing events:");
+        pw.increaseIndent();
+        boolean printedEvents = false;
+        final long nowElapsed = SystemClock.elapsedRealtime();
+        for (int u = mCurrentOngoingEvents.numMaps() - 1; u >= 0; --u) {
+            final int userId = mCurrentOngoingEvents.keyAt(u);
+            for (int p = mCurrentOngoingEvents.numElementsForKey(userId) - 1; p >= 0; --p) {
+                final String pkgName = mCurrentOngoingEvents.keyAt(u, p);
+                final SparseArrayMap<String, OngoingEvent> ongoingEvents =
+                        mCurrentOngoingEvents.get(userId, pkgName);
+
+                boolean printedApp = false;
+
+                for (int e = ongoingEvents.numMaps() - 1; e >= 0; --e) {
+                    final int eventId = ongoingEvents.keyAt(e);
+                    for (int t = ongoingEvents.numElementsForKey(eventId) - 1; t >= 0; --t) {
+                        if (!printedApp) {
+                            printedApp = true;
+                            pw.println(appToString(userId, pkgName));
+                            pw.increaseIndent();
+                        }
+                        printedEvents = true;
+
+                        OngoingEvent ongoingEvent = ongoingEvents.valueAt(e, t);
+
+                        pw.print(EconomicPolicy.eventToString(ongoingEvent.eventId));
+                        if (ongoingEvent.tag != null) {
+                            pw.print("(");
+                            pw.print(ongoingEvent.tag);
+                            pw.print(")");
+                        }
+                        pw.print(" runtime=");
+                        TimeUtils.formatDuration(nowElapsed - ongoingEvent.startTimeElapsed, pw);
+                        pw.print(" delta/sec=");
+                        pw.print(ongoingEvent.deltaPerSec);
+                        pw.print(" refCount=");
+                        pw.print(ongoingEvent.refCount);
+                        pw.println();
+                    }
+                }
+
+                if (printedApp) {
+                    pw.decreaseIndent();
+                }
+            }
+        }
+        if (!printedEvents) {
+            pw.print("N/A");
+        }
+        pw.decreaseIndent();
     }
 }
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java b/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java
index 20a300a..36895a5 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java
@@ -567,20 +567,23 @@
         final String pkgName = event.getPackageName();
         if (DEBUG) {
             Slog.d(TAG, "Processing event " + event.getEventType()
+                    + " (" + event.mInstanceId + ")"
                     + " for " + appToString(userId, pkgName));
         }
         final long nowElapsed = SystemClock.elapsedRealtime();
         switch (event.getEventType()) {
             case UsageEvents.Event.ACTIVITY_RESUMED:
                 mAgent.noteOngoingEventLocked(userId, pkgName,
-                        EconomicPolicy.REWARD_TOP_ACTIVITY, null, nowElapsed);
+                        EconomicPolicy.REWARD_TOP_ACTIVITY, String.valueOf(event.mInstanceId),
+                        nowElapsed);
                 break;
             case UsageEvents.Event.ACTIVITY_PAUSED:
             case UsageEvents.Event.ACTIVITY_STOPPED:
             case UsageEvents.Event.ACTIVITY_DESTROYED:
                 final long now = getCurrentTimeMillis();
                 mAgent.stopOngoingActionLocked(userId, pkgName,
-                        EconomicPolicy.REWARD_TOP_ACTIVITY, null, nowElapsed, now);
+                        EconomicPolicy.REWARD_TOP_ACTIVITY, String.valueOf(event.mInstanceId),
+                        nowElapsed, now);
                 break;
             case UsageEvents.Event.USER_INTERACTION:
             case UsageEvents.Event.CHOOSER_ACTION:
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/Ledger.java b/apex/jobscheduler/service/java/com/android/server/tare/Ledger.java
index a234ae6..f4917ad 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/Ledger.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/Ledger.java
@@ -138,6 +138,11 @@
             dumpTime(pw, transaction.endTimeMs);
             pw.print(": ");
             pw.print(EconomicPolicy.eventToString(transaction.eventId));
+            if (transaction.tag != null) {
+                pw.print("(");
+                pw.print(transaction.tag);
+                pw.print(")");
+            }
             pw.print(" --> ");
             pw.println(narcToString(transaction.delta));
         }
diff --git a/core/api/current.txt b/core/api/current.txt
index 154c42f..ee44198 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -17888,6 +17888,7 @@
     field public static final int RGB_565 = 4; // 0x4
     field public static final int RGB_888 = 3; // 0x3
     field public static final int S_UI8 = 53; // 0x35
+    field public static final long USAGE_COMPOSER_OVERLAY = 2048L; // 0x800L
     field public static final long USAGE_CPU_READ_OFTEN = 3L; // 0x3L
     field public static final long USAGE_CPU_READ_RARELY = 2L; // 0x2L
     field public static final long USAGE_CPU_WRITE_OFTEN = 48L; // 0x30L
@@ -49046,6 +49047,7 @@
     method @NonNull public android.view.SurfaceControl build();
     method @NonNull public android.view.SurfaceControl.Builder setBufferSize(@IntRange(from=0) int, @IntRange(from=0) int);
     method @NonNull public android.view.SurfaceControl.Builder setFormat(int);
+    method @NonNull public android.view.SurfaceControl.Builder setHidden(boolean);
     method @NonNull public android.view.SurfaceControl.Builder setName(@NonNull String);
     method @NonNull public android.view.SurfaceControl.Builder setOpaque(boolean);
     method @NonNull public android.view.SurfaceControl.Builder setParent(@Nullable android.view.SurfaceControl);
@@ -49060,11 +49062,18 @@
     method @NonNull public android.view.SurfaceControl.Transaction merge(@NonNull android.view.SurfaceControl.Transaction);
     method @NonNull public android.view.SurfaceControl.Transaction reparent(@NonNull android.view.SurfaceControl, @Nullable android.view.SurfaceControl);
     method @NonNull public android.view.SurfaceControl.Transaction setAlpha(@NonNull android.view.SurfaceControl, @FloatRange(from=0.0, to=1.0) float);
+    method @NonNull public android.view.SurfaceControl.Transaction setBuffer(@NonNull android.view.SurfaceControl, @Nullable android.hardware.HardwareBuffer);
     method @NonNull public android.view.SurfaceControl.Transaction setBufferSize(@NonNull android.view.SurfaceControl, @IntRange(from=0) int, @IntRange(from=0) int);
+    method @NonNull public android.view.SurfaceControl.Transaction setBufferTransform(@NonNull android.view.SurfaceControl, int);
+    method @NonNull public android.view.SurfaceControl.Transaction setCrop(@NonNull android.view.SurfaceControl, @Nullable android.graphics.Rect);
+    method @NonNull public android.view.SurfaceControl.Transaction setDamageRegion(@NonNull android.view.SurfaceControl, @Nullable android.graphics.Region);
     method @NonNull public android.view.SurfaceControl.Transaction setFrameRate(@NonNull android.view.SurfaceControl, @FloatRange(from=0.0) float, int);
     method @NonNull public android.view.SurfaceControl.Transaction setFrameRate(@NonNull android.view.SurfaceControl, @FloatRange(from=0.0) float, int, int);
     method @NonNull public android.view.SurfaceControl.Transaction setGeometry(@NonNull android.view.SurfaceControl, @Nullable android.graphics.Rect, @Nullable android.graphics.Rect, int);
     method @NonNull public android.view.SurfaceControl.Transaction setLayer(@NonNull android.view.SurfaceControl, @IntRange(from=java.lang.Integer.MIN_VALUE, to=java.lang.Integer.MAX_VALUE) int);
+    method @NonNull public android.view.SurfaceControl.Transaction setOpaque(@NonNull android.view.SurfaceControl, boolean);
+    method @NonNull public android.view.SurfaceControl.Transaction setPosition(@NonNull android.view.SurfaceControl, float, float);
+    method @NonNull public android.view.SurfaceControl.Transaction setScale(@NonNull android.view.SurfaceControl, float, float);
     method @NonNull public android.view.SurfaceControl.Transaction setVisibility(@NonNull android.view.SurfaceControl, boolean);
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.view.SurfaceControl.Transaction> CREATOR;
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index c9b8ba2..a33d0a2 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -70,6 +70,7 @@
     field public static final String BIND_TRANSLATION_SERVICE = "android.permission.BIND_TRANSLATION_SERVICE";
     field public static final String BIND_TRUST_AGENT = "android.permission.BIND_TRUST_AGENT";
     field public static final String BIND_TV_REMOTE_SERVICE = "android.permission.BIND_TV_REMOTE_SERVICE";
+    field public static final String BLUETOOTH_MAP = "android.permission.BLUETOOTH_MAP";
     field public static final String BRICK = "android.permission.BRICK";
     field public static final String BRIGHTNESS_SLIDER_USAGE = "android.permission.BRIGHTNESS_SLIDER_USAGE";
     field public static final String BROADCAST_CLOSE_SYSTEM_DIALOGS = "android.permission.BROADCAST_CLOSE_SYSTEM_DIALOGS";
@@ -5875,9 +5876,11 @@
     method public android.media.AudioTrack createAudioTrackSource(android.media.audiopolicy.AudioMix) throws java.lang.IllegalArgumentException;
     method public int detachMixes(@NonNull java.util.List<android.media.audiopolicy.AudioMix>);
     method public int getFocusDuckingBehavior();
+    method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public java.util.List<android.media.AudioFocusInfo> getFocusStack();
     method public int getStatus();
     method public boolean removeUidDeviceAffinity(int);
     method public boolean removeUserIdDeviceAffinity(int);
+    method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public boolean sendFocusLoss(@NonNull android.media.AudioFocusInfo) throws java.lang.IllegalStateException;
     method public int setFocusDuckingBehavior(int) throws java.lang.IllegalArgumentException, java.lang.IllegalStateException;
     method public void setRegistration(String);
     method public boolean setUidDeviceAffinity(int, @NonNull java.util.List<android.media.AudioDeviceInfo>);
@@ -6447,6 +6450,7 @@
     method public int flush();
     method public long read(long);
     method public long read(@NonNull byte[], long, long);
+    method public long seek(long);
     method public void setFileDescriptor(@NonNull android.os.ParcelFileDescriptor);
     method public int start();
     method public int stop();
@@ -6582,6 +6586,7 @@
 
   public class DownloadEvent extends android.media.tv.tuner.filter.FilterEvent {
     method public int getDataLength();
+    method public int getDownloadId();
     method public int getItemFragmentIndex();
     method public int getItemId();
     method public int getLastItemFragmentIndex();
@@ -6591,11 +6596,13 @@
   public class DownloadSettings extends android.media.tv.tuner.filter.Settings {
     method @NonNull public static android.media.tv.tuner.filter.DownloadSettings.Builder builder(int);
     method public int getDownloadId();
+    method public boolean useDownloadId();
   }
 
   public static class DownloadSettings.Builder {
     method @NonNull public android.media.tv.tuner.filter.DownloadSettings build();
     method @NonNull public android.media.tv.tuner.filter.DownloadSettings.Builder setDownloadId(int);
+    method @NonNull public android.media.tv.tuner.filter.DownloadSettings.Builder setUseDownloadId(boolean);
   }
 
   public class Filter implements java.lang.AutoCloseable {
@@ -6694,12 +6701,14 @@
     method public long getAudioHandle();
     method public long getAvDataId();
     method public long getDataLength();
+    method public long getDts();
     method @Nullable public android.media.tv.tuner.filter.AudioDescriptor getExtraMetaData();
     method @Nullable public android.media.MediaCodec.LinearBlock getLinearBlock();
     method @IntRange(from=0) public int getMpuSequenceNumber();
     method public long getOffset();
     method public long getPts();
     method public int getStreamId();
+    method public boolean isDtsPresent();
     method public boolean isPrivateData();
     method public boolean isPtsPresent();
     method public boolean isSecureMemory();
@@ -6809,7 +6818,8 @@
   }
 
   public class SectionEvent extends android.media.tv.tuner.filter.FilterEvent {
-    method public int getDataLength();
+    method @Deprecated public int getDataLength();
+    method public long getDataLengthLong();
     method public int getSectionNumber();
     method public int getTableId();
     method public int getVersion();
@@ -7541,6 +7551,7 @@
     method public int getSignalStrength();
     method public int getSnr();
     method public int getSpectralInversion();
+    method @NonNull public int[] getStreamIdList();
     method public int getSymbolRate();
     method @IntRange(from=0, to=65535) public int getSystemId();
     method public int getTransmissionMode();
@@ -7587,6 +7598,7 @@
     field public static final int FRONTEND_STATUS_TYPE_SIGNAL_STRENGTH = 6; // 0x6
     field public static final int FRONTEND_STATUS_TYPE_SNR = 1; // 0x1
     field public static final int FRONTEND_STATUS_TYPE_SPECTRAL = 10; // 0xa
+    field public static final int FRONTEND_STATUS_TYPE_STREAM_ID_LIST = 39; // 0x27
     field public static final int FRONTEND_STATUS_TYPE_SYMBOL_RATE = 7; // 0x7
     field public static final int FRONTEND_STATUS_TYPE_T2_SYSTEM_ID = 29; // 0x1d
     field public static final int FRONTEND_STATUS_TYPE_TRANSMISSION_MODE = 27; // 0x1b
diff --git a/core/api/system-removed.txt b/core/api/system-removed.txt
index 9cb9ddc..2c5acf1 100644
--- a/core/api/system-removed.txt
+++ b/core/api/system-removed.txt
@@ -60,14 +60,6 @@
 
 }
 
-package android.bluetooth {
-
-  public final class BluetoothHeadset implements android.bluetooth.BluetoothProfile {
-    method @Deprecated @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.MODIFY_PHONE_STATE}) public boolean setPriority(android.bluetooth.BluetoothDevice, int);
-  }
-
-}
-
 package android.content {
 
   public class Intent implements java.lang.Cloneable android.os.Parcelable {
diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java
index a642696..3573a56 100644
--- a/core/java/android/accessibilityservice/AccessibilityService.java
+++ b/core/java/android/accessibilityservice/AccessibilityService.java
@@ -1394,6 +1394,12 @@
          * {@link AccessibilityService#onServiceConnected()} has not yet been
          * called) or the service has been disconnected, this method will
          * return a default value of {@code 1.0f}.
+         * </p>
+         * <p>
+         * <strong>Note:</strong> This legacy API gets the scale of full-screen
+         * magnification. To get the scale of the current controlling magnifier,
+         * use {@link #getMagnificationConfig} instead.
+         * </p>
          *
          * @return the current magnification scale
          */
@@ -1422,6 +1428,12 @@
          * {@link AccessibilityService#onServiceConnected()} has not yet been
          * called) or the service has been disconnected, this method will
          * return a default value of {@code 0.0f}.
+         * </p>
+         * <p>
+         * <strong>Note:</strong> This legacy API gets the center position of full-screen
+         * magnification. To get the magnification center of the current controlling magnifier,
+         * use {@link #getMagnificationConfig} instead.
+         * </p>
          *
          * @return the unscaled screen-relative X coordinate of the center of
          *         the magnified region
@@ -1451,6 +1463,12 @@
          * {@link AccessibilityService#onServiceConnected()} has not yet been
          * called) or the service has been disconnected, this method will
          * return a default value of {@code 0.0f}.
+         * </p>
+         * <p>
+         * <strong>Note:</strong> This legacy API gets the center position of full-screen
+         * magnification. To get the magnification center of the current controlling magnifier,
+         * use {@link #getMagnificationConfig} instead.
+         * </p>
          *
          * @return the unscaled screen-relative Y coordinate of the center of
          *         the magnified region
@@ -1571,6 +1589,11 @@
          * {@link AccessibilityService#onServiceConnected()} has not yet been
          * called) or the service has been disconnected, this method will have
          * no effect and return {@code false}.
+         * <p>
+         * <strong>Note:</strong> This legacy API sets the scale of full-screen
+         * magnification. To set the scale of the specified magnifier,
+         * use {@link #setMagnificationConfig} instead.
+         * </p>
          *
          * @param scale the magnification scale to set, must be >= 1 and <= 8
          * @param animate {@code true} to animate from the current scale or
@@ -1602,6 +1625,12 @@
          * {@link AccessibilityService#onServiceConnected()} has not yet been
          * called) or the service has been disconnected, this method will have
          * no effect and return {@code false}.
+         * </p>
+         * <p>
+         * <strong>Note:</strong> This legacy API sets the center of full-screen
+         * magnification. To set the center of the specified magnifier,
+         * use {@link #setMagnificationConfig} instead.
+         * </p>
          *
          * @param centerX the unscaled screen-relative X coordinate on which to
          *                center the viewport
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index af907af..779552f1 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -5808,6 +5808,7 @@
                     p, result);
             buildCustomContentIntoTemplate(mContext, standard, customContent,
                     p, result);
+            makeHeaderExpanded(standard);
             return standard;
         }
 
diff --git a/core/java/android/bluetooth/BluetoothHeadset.java b/core/java/android/bluetooth/BluetoothHeadset.java
index c046324..8212eaa 100644
--- a/core/java/android/bluetooth/BluetoothHeadset.java
+++ b/core/java/android/bluetooth/BluetoothHeadset.java
@@ -601,49 +601,6 @@
     }
 
     /**
-     * Set priority of the profile
-     *
-     * <p> The device should already be paired.
-     * Priority can be one of {@link BluetoothProfile#PRIORITY_ON} or
-     * {@link BluetoothProfile#PRIORITY_OFF}
-     *
-     * @param device Paired bluetooth device
-     * @param priority
-     * @return true if priority is set, false on error
-     * @hide
-     * @deprecated Replaced with {@link #setConnectionPolicy(BluetoothDevice, int)}
-     * @removed
-     */
-    @Deprecated
-    @SystemApi
-    @RequiresLegacyBluetoothAdminPermission
-    @RequiresBluetoothConnectPermission
-    @RequiresPermission(allOf = {
-            android.Manifest.permission.BLUETOOTH_CONNECT,
-            android.Manifest.permission.MODIFY_PHONE_STATE,
-    })
-    public boolean setPriority(BluetoothDevice device, int priority) {
-        if (DBG) log("setPriority(" + device + ", " + priority + ")");
-        final IBluetoothHeadset service = mService;
-        if (service != null && isEnabled() && isValidDevice(device)) {
-            if (priority != BluetoothProfile.PRIORITY_OFF
-                    && priority != BluetoothProfile.PRIORITY_ON) {
-                return false;
-            }
-            try {
-                return service.setPriority(
-                        device, BluetoothAdapter.priorityToConnectionPolicy(priority),
-                        mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
-                return false;
-            }
-        }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return false;
-    }
-
-    /**
      * Set connection policy of the profile
      *
      * <p> The device should already be paired.
diff --git a/core/java/android/content/res/AssetManager.java b/core/java/android/content/res/AssetManager.java
index 24c6a5a..bfd9fd0 100644
--- a/core/java/android/content/res/AssetManager.java
+++ b/core/java/android/content/res/AssetManager.java
@@ -792,6 +792,21 @@
     }
 
     /**
+     * To get the parent theme resource id according to the parameter theme resource id.
+     * @param resId theme resource id.
+     * @return the parent theme resource id.
+     * @hide
+     */
+    @StyleRes
+    int getParentThemeIdentifier(@StyleRes int resId) {
+        synchronized (this) {
+            ensureValidLocked();
+            // name is checked in JNI.
+            return nativeGetParentThemeIdentifier(mObject, resId);
+        }
+    }
+
+    /**
      * Enable resource resolution logging to track the steps taken to resolve the last resource
      * entry retrieved. Stores the configuration and package names for each step.
      *
@@ -1600,6 +1615,8 @@
     private static native void nativeThemeDump(long ptr, long themePtr, int priority, String tag,
             String prefix);
     static native @NativeConfig int nativeThemeGetChangingConfigurations(long themePtr);
+    @StyleRes
+    private static native int nativeGetParentThemeIdentifier(long ptr, @StyleRes int styleId);
 
     // AssetInputStream related native methods.
     private static native void nativeAssetDestroy(long assetPtr);
diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java
index b5d4f09..cb53a2a 100644
--- a/core/java/android/content/res/Resources.java
+++ b/core/java/android/content/res/Resources.java
@@ -1508,6 +1508,12 @@
      * retrieve XML attributes with style and theme information applied.
      */
     public final class Theme {
+        /**
+         * To trace parent themes needs to prevent a cycle situation.
+         * e.x. A's parent is B, B's parent is C, and C's parent is A.
+         */
+        private static final int MAX_NUMBER_OF_TRACING_PARENT_THEME = 100;
+
         private final Object mLock = new Object();
 
         @GuardedBy("mLock")
@@ -1801,6 +1807,13 @@
             }
         }
 
+        @StyleRes
+        /*package*/ int getParentThemeIdentifier(@StyleRes int resId) {
+            synchronized (mLock) {
+                return mThemeImpl.getParentThemeIdentifier(resId);
+            }
+        }
+
         /**
          * @hide
          */
@@ -1948,8 +1961,27 @@
         public String toString() {
             final StringBuilder sb = new StringBuilder();
             sb.append('{');
-            sb.append("id=0x").append(Integer.toHexString(getAppliedStyleResId())).append(", ");
-            sb.append("themes=").append(Arrays.deepToString(getTheme()));
+            int themeResId = getAppliedStyleResId();
+            int i = 0;
+            sb.append("InheritanceMap=[");
+            while (themeResId > 0) {
+                if (i > MAX_NUMBER_OF_TRACING_PARENT_THEME) {
+                    sb.append(",...");
+                    break;
+                }
+
+                if (i > 0) {
+                    sb.append(", ");
+                }
+                sb.append("id=0x").append(Integer.toHexString(themeResId));
+                sb.append(getResourcePackageName(themeResId))
+                        .append(":").append(getResourceTypeName(themeResId))
+                        .append("/").append(getResourceEntryName(themeResId));
+
+                i++;
+                themeResId = getParentThemeIdentifier(themeResId);
+            }
+            sb.append("], Themes=").append(Arrays.deepToString(getTheme()));
             sb.append('}');
             return sb.toString();
         }
diff --git a/core/java/android/content/res/ResourcesImpl.java b/core/java/android/content/res/ResourcesImpl.java
index b9f93b8..4d850b0c 100644
--- a/core/java/android/content/res/ResourcesImpl.java
+++ b/core/java/android/content/res/ResourcesImpl.java
@@ -1310,6 +1310,14 @@
             return mThemeResId;
         }
 
+        @StyleRes
+        /*package*/ int getParentThemeIdentifier(@StyleRes int resId) {
+            if (resId > 0) {
+                return mAssets.getParentThemeIdentifier(resId);
+            }
+            return 0;
+        }
+
         void applyStyle(int resId, boolean force) {
             mAssets.applyStyleToTheme(mTheme, resId, force);
             mThemeResId = resId;
diff --git a/core/java/android/hardware/HardwareBuffer.java b/core/java/android/hardware/HardwareBuffer.java
index cad30dd..a4a8f31 100644
--- a/core/java/android/hardware/HardwareBuffer.java
+++ b/core/java/android/hardware/HardwareBuffer.java
@@ -25,6 +25,7 @@
 import android.os.Build;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.view.SurfaceControl;
 
 import dalvik.annotation.optimization.CriticalNative;
 import dalvik.annotation.optimization.FastNative;
@@ -129,6 +130,15 @@
     public static final long USAGE_GPU_SAMPLED_IMAGE      = 1 << 8;
     /** Usage: The buffer will be written to by the GPU */
     public static final long USAGE_GPU_COLOR_OUTPUT       = 1 << 9;
+    /**
+     * The buffer will be used as a composer HAL overlay layer.
+     *
+     * This flag is currently only needed when using
+     * {@link android.view.SurfaceControl.Transaction#setBuffer(SurfaceControl, HardwareBuffer)}
+     * to set a buffer. In all other cases, the framework adds this flag
+     * internally to buffers that could be presented in a composer overlay.
+     */
+    public static final long USAGE_COMPOSER_OVERLAY = 1 << 11;
     /** Usage: The buffer must not be used outside of a protected hardware path */
     public static final long USAGE_PROTECTED_CONTENT      = 1 << 14;
     /** Usage: The buffer will be read by a hardware video encoder */
diff --git a/core/java/android/os/GraphicsEnvironment.java b/core/java/android/os/GraphicsEnvironment.java
index 8292f26..aa4b83a 100644
--- a/core/java/android/os/GraphicsEnvironment.java
+++ b/core/java/android/os/GraphicsEnvironment.java
@@ -192,14 +192,15 @@
 
         // We only want to use ANGLE if the developer has explicitly chosen something other than
         // default driver.
-        final boolean requested = devOptIn.equals(ANGLE_GL_DRIVER_CHOICE_ANGLE);
-        if (requested) {
+        final boolean forceAngle = devOptIn.equals(ANGLE_GL_DRIVER_CHOICE_ANGLE);
+        final boolean forceNative = devOptIn.equals(ANGLE_GL_DRIVER_CHOICE_NATIVE);
+        if (forceAngle || forceNative) {
             Log.v(TAG, "ANGLE developer option for " + packageName + ": " + devOptIn);
         }
 
         final boolean gameModeEnabledAngle = isAngleEnabledByGameMode(context, packageName);
 
-        return requested || gameModeEnabledAngle;
+        return !forceNative && (forceAngle || gameModeEnabledAngle);
     }
 
     private int getVulkanVersion(PackageManager pm) {
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index 83a6bc0..73ffd66 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -44,7 +44,6 @@
 import android.graphics.BLASTBufferQueue;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
-import android.graphics.GraphicBuffer;
 import android.graphics.Matrix;
 import android.graphics.PixelFormat;
 import android.graphics.Point;
@@ -1930,9 +1929,7 @@
 
             mScreenshotSize.set(mSurfaceSize.x, mSurfaceSize.y);
 
-            GraphicBuffer graphicBuffer = GraphicBuffer.createFromHardwareBuffer(hardwareBuffer);
-
-            t.setBuffer(mScreenshotSurfaceControl, graphicBuffer);
+            t.setBuffer(mScreenshotSurfaceControl, hardwareBuffer);
             t.setColorSpace(mScreenshotSurfaceControl, screenshotBuffer.getColorSpace());
             // Place on top everything else.
             t.setLayer(mScreenshotSurfaceControl, Integer.MAX_VALUE);
diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java
index 6984e4d..4789231 100644
--- a/core/java/android/text/StaticLayout.java
+++ b/core/java/android/text/StaticLayout.java
@@ -874,6 +874,18 @@
                             ? Math.max(fmDescent, Math.round(descents[breakIndex]))
                             : fmDescent;
 
+                    // The fallback ascent/descent may be larger than top/bottom of the default font
+                    // metrics. Adjust top/bottom with ascent/descent for avoiding unexpected
+                    // clipping.
+                    if (fallbackLineSpacing) {
+                        if (ascent < fmTop) {
+                            fmTop = ascent;
+                        }
+                        if (descent > fmBottom) {
+                            fmBottom = descent;
+                        }
+                    }
+
                     v = out(source, here, endPos,
                             ascent, descent, fmTop, fmBottom,
                             v, spacingmult, spacingadd, chooseHt, chooseHtv, fm,
diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index 7e2792c..8124510 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -58,6 +58,10 @@
      */
     public static final String SETTINGS_APP_LANGUAGE_SELECTION = "settings_app_language_selection";
 
+    /** @hide */
+    public static final String SETTINGS_ENABLE_MONITOR_PHANTOM_PROCS =
+            "settings_enable_monitor_phantom_procs";
+
     private static final Map<String, String> DEFAULT_FLAGS;
 
     static {
@@ -81,6 +85,7 @@
         DEFAULT_FLAGS.put(SETTINGS_ENABLE_SECURITY_HUB, "true");
         DEFAULT_FLAGS.put(SETTINGS_SUPPORT_LARGE_SCREEN, "true");
         DEFAULT_FLAGS.put(SETTINGS_APP_LANGUAGE_SELECTION, "false");
+        DEFAULT_FLAGS.put(SETTINGS_ENABLE_MONITOR_PHANTOM_PROCS, "true");
     }
 
     private static final Set<String> PERSISTENT_FLAGS;
@@ -89,6 +94,7 @@
         PERSISTENT_FLAGS.add(SETTINGS_APP_LANGUAGE_SELECTION);
         PERSISTENT_FLAGS.add(SETTINGS_PROVIDER_MODEL);
         PERSISTENT_FLAGS.add(SETTINGS_SUPPORT_LARGE_SCREEN);
+        PERSISTENT_FLAGS.add(SETTINGS_ENABLE_MONITOR_PHANTOM_PROCS);
     }
 
     /**
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index af7d86c..3b52709 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -120,6 +120,8 @@
             long relativeToObject, int zorder);
     private static native void nativeSetPosition(long transactionObj, long nativeObject,
             float x, float y);
+    private static native void nativeSetScale(long transactionObj, long nativeObject,
+            float x, float y);
     private static native void nativeSetSize(long transactionObj, long nativeObject, int w, int h);
     private static native void nativeSetTransparentRegionHint(long transactionObj,
             long nativeObject, Region region);
@@ -202,9 +204,13 @@
     private static native void nativeReparent(long transactionObj, long nativeObject,
             long newParentNativeObject);
     private static native void nativeSetBuffer(long transactionObj, long nativeObject,
-            GraphicBuffer buffer);
+            HardwareBuffer buffer);
+    private static native void nativeSetBufferTransform(long transactionObj, long nativeObject,
+            int transform);
     private static native void nativeSetColorSpace(long transactionObj, long nativeObject,
             int colorSpace);
+    private static native void nativeSetDamageRegion(long transactionObj, long nativeObject,
+            Region region);
 
     private static native void nativeOverrideHdrTypes(IBinder displayToken, int[] modes);
 
@@ -1333,7 +1339,6 @@
          * Set the initial visibility for the SurfaceControl.
          *
          * @param hidden Whether the Surface is initially HIDDEN.
-         * @hide
          */
         @NonNull
         public Builder setHidden(boolean hidden) {
@@ -2840,16 +2845,38 @@
         }
 
         /**
-         * @hide
+         * Sets the SurfaceControl to the specified position relative to the parent
+         * SurfaceControl
+         *
+         * @param sc The SurfaceControl to change position
+         * @param x the X position
+         * @param y the Y position
+         * @return this transaction
          */
-        @UnsupportedAppUsage
-        public Transaction setPosition(SurfaceControl sc, float x, float y) {
+        @NonNull
+        public Transaction setPosition(@NonNull SurfaceControl sc, float x, float y) {
             checkPreconditions(sc);
             nativeSetPosition(mNativeObject, sc.mNativeObject, x, y);
             return this;
         }
 
         /**
+         * Sets the SurfaceControl to the specified scale with (0, 0) as the center point
+         * of the scale.
+         *
+         * @param sc The SurfaceControl to change scale
+         * @param scaleX the X scale
+         * @param scaleY the Y scale
+         * @return this transaction
+         */
+        @NonNull
+        public Transaction setScale(@NonNull SurfaceControl sc, float scaleX, float scaleY) {
+            checkPreconditions(sc);
+            nativeSetScale(mNativeObject, sc.mNativeObject, scaleX, scaleY);
+            return this;
+        }
+
+        /**
          * Set the default buffer size for the SurfaceControl, if there is a
          * {@link Surface} associated with the control, then
          * this will be the default size for buffers dequeued from it.
@@ -3056,7 +3083,9 @@
          * @param sc   SurfaceControl to set crop of.
          * @param crop Bounds of the crop to apply.
          * @hide
+         * @deprecated Use {@link #setCrop(SurfaceControl, Rect)} instead.
          */
+        @Deprecated
         @UnsupportedAppUsage
         public Transaction setWindowCrop(SurfaceControl sc, Rect crop) {
             checkPreconditions(sc);
@@ -3071,6 +3100,28 @@
         }
 
         /**
+         * Bounds the surface and its children to the bounds specified. Size of the surface will be
+         * ignored and only the crop and buffer size will be used to determine the bounds of the
+         * surface. If no crop is specified and the surface has no buffer, the surface bounds is
+         * only constrained by the size of its parent bounds.
+         *
+         * @param sc   SurfaceControl to set crop of.
+         * @param crop Bounds of the crop to apply.
+         * @return this This transaction for chaining
+         */
+        public @NonNull Transaction setCrop(@NonNull SurfaceControl sc, @Nullable Rect crop) {
+            checkPreconditions(sc);
+            if (crop != null) {
+                nativeSetWindowCrop(mNativeObject, sc.mNativeObject,
+                        crop.left, crop.top, crop.right, crop.bottom);
+            } else {
+                nativeSetWindowCrop(mNativeObject, sc.mNativeObject, 0, 0, 0, 0);
+            }
+
+            return this;
+        }
+
+        /**
          * Same as {@link Transaction#setWindowCrop(SurfaceControl, Rect)} but sets the crop rect
          * top left at 0, 0.
          *
@@ -3215,11 +3266,34 @@
         }
 
         /**
-         * Sets the opacity of the surface.  Setting the flag is equivalent to creating the
-         * Surface with the {@link #OPAQUE} flag.
-         * @hide
+         * Indicates whether the surface must be considered opaque, even if its pixel format is
+         * set to translucent. This can be useful if an application needs full RGBA 8888 support
+         * for instance but will still draw every pixel opaque.
+         * <p>
+         * This flag only determines whether opacity will be sampled from the alpha channel.
+         * Plane-alpha from calls to setAlpha() can still result in blended composition
+         * regardless of the opaque setting.
+         *
+         * Combined effects are (assuming a buffer format with an alpha channel):
+         * <ul>
+         * <li>OPAQUE + alpha(1.0) == opaque composition
+         * <li>OPAQUE + alpha(0.x) == blended composition
+         * <li>OPAQUE + alpha(0.0) == no composition
+         * <li>!OPAQUE + alpha(1.0) == blended composition
+         * <li>!OPAQUE + alpha(0.x) == blended composition
+         * <li>!OPAQUE + alpha(0.0) == no composition
+         * </ul>
+         * If the underlying buffer lacks an alpha channel, it is as if setOpaque(true)
+         * were set automatically.
+         *
+         * @see Builder#setOpaque(boolean)
+         *
+         * @param sc The SurfaceControl to update
+         * @param isOpaque true if the buffer's alpha should be ignored, false otherwise
+         * @return this
          */
-        public Transaction setOpaque(SurfaceControl sc, boolean isOpaque) {
+        @NonNull
+        public Transaction setOpaque(@NonNull SurfaceControl sc, boolean isOpaque) {
             checkPreconditions(sc);
             if (isOpaque) {
                 nativeSetFlags(mNativeObject, sc.mNativeObject, SURFACE_OPAQUE, SURFACE_OPAQUE);
@@ -3498,14 +3572,57 @@
          * created as type {@link #FX_SURFACE_BLAST}
          *
          * @hide
+         * @deprecated Use {@link #setBuffer(SurfaceControl, HardwareBuffer)} instead
          */
+        @Deprecated
         public Transaction setBuffer(SurfaceControl sc, GraphicBuffer buffer) {
+            return setBuffer(sc, HardwareBuffer.createFromGraphicBuffer(buffer));
+        }
+
+        /**
+         * Updates the HardwareBuffer displayed for the SurfaceControl.
+         *
+         * Note that the buffer must be allocated with {@link HardwareBuffer#USAGE_COMPOSER_OVERLAY}
+         * as well as {@link HardwareBuffer#USAGE_GPU_SAMPLED_IMAGE} as the surface control might
+         * be composited using either an overlay or using the GPU.
+         *
+         * @param sc The SurfaceControl to update
+         * @param buffer The buffer to be displayed
+         * @return this
+         */
+        public @NonNull Transaction setBuffer(@NonNull SurfaceControl sc,
+                @Nullable HardwareBuffer buffer) {
             checkPreconditions(sc);
             nativeSetBuffer(mNativeObject, sc.mNativeObject, buffer);
             return this;
         }
 
         /**
+         * Sets the buffer transform that should be applied to the current buffer.
+         *
+         * @param sc The SurfaceControl to update
+         * @param transform The transform to apply to the buffer.
+         * @return this
+         */
+        public @NonNull Transaction setBufferTransform(@NonNull SurfaceControl sc,
+                /* TODO: Mark the intdef */ int transform) {
+            checkPreconditions(sc);
+            nativeSetBufferTransform(mNativeObject, sc.mNativeObject, transform);
+            return this;
+        }
+
+        /**
+         * Updates the region for the content on this surface updated in this transaction.
+         *
+         * If unspecified, the complete surface is assumed to be damaged.
+         */
+        public @NonNull Transaction setDamageRegion(@NonNull SurfaceControl sc,
+                @Nullable Region region) {
+            nativeSetDamageRegion(mNativeObject, sc.mNativeObject, region);
+            return this;
+        }
+
+        /**
          * Set the color space for the SurfaceControl. The supported color spaces are SRGB
          * and Display P3, other color spaces will be treated as SRGB. This can only be used for
          * SurfaceControls that were created as type {@link #FX_SURFACE_BLAST}
diff --git a/core/java/android/view/inputmethod/InputConnection.java b/core/java/android/view/inputmethod/InputConnection.java
index 3b15db2..b85fe7c 100644
--- a/core/java/android/view/inputmethod/InputConnection.java
+++ b/core/java/android/view/inputmethod/InputConnection.java
@@ -836,20 +836,25 @@
     boolean beginBatchEdit();
 
     /**
-     * Tell the editor that you are done with a batch edit previously
-     * initiated with {@link #beginBatchEdit}. This ends the latest
-     * batch only.
+     * Tell the editor that you are done with a batch edit previously initiated with
+     * {@link #beginBatchEdit()}. This ends the latest batch only.
      *
-     * <p><strong>IME authors:</strong> make sure you call this
-     * exactly once for each call to {@link #beginBatchEdit}.</p>
+     * <p><strong>IME authors:</strong> make sure you call this exactly once for each call to
+     * {@link #beginBatchEdit()}.</p>
      *
-     * <p><strong>Editor authors:</strong> please be careful about
-     * batch edit nesting. Updates still to be held back until the end
-     * of the last batch edit.</p>
+     * <p><strong>Editor authors:</strong> please be careful about batch edit nesting. Updates still
+     * to be held back until the end of the last batch edit.  In case you are delegating this API
+     * call to the one obtained from
+     * {@link android.widget.EditText#onCreateInputConnection(EditorInfo)}, there was an off-by-one
+     * that had returned {@code true} when its nested batch edit count becomes {@code 0} as a result
+     * of invoking this API.  This bug is fixed in {@link android.os.Build.VERSION_CODES#TIRAMISU}.
+     * </p>
      *
-     * @return true if there is still a batch edit in progress after closing
-     * the latest one (in other words, if the nesting count is > 0), false
-     * otherwise or if the input connection is no longer valid.
+     * @return For editor authors, you must return {@code true} if a batch edit is still in progress
+     *         after closing the latest one (in other words, if the nesting count is still a
+     *         positive number). Return {@code false} otherwise.  For IME authors, you will
+     *         always receive {@code true} as long as the request was sent to the editor, and
+     *         receive {@code false} only if the input connection is no longer valid.
      */
     boolean endBatchEdit();
 
diff --git a/core/java/com/android/internal/inputmethod/EditableInputConnection.java b/core/java/com/android/internal/inputmethod/EditableInputConnection.java
index 410e68b..29bb311 100644
--- a/core/java/com/android/internal/inputmethod/EditableInputConnection.java
+++ b/core/java/com/android/internal/inputmethod/EditableInputConnection.java
@@ -89,7 +89,7 @@
                 // contribution to mTextView's nested batch edit count is zero.
                 mTextView.endBatchEdit();
                 mBatchEditNesting--;
-                return true;
+                return mBatchEditNesting > 0;
             }
         }
         return false;
diff --git a/core/jni/android_util_AssetManager.cpp b/core/jni/android_util_AssetManager.cpp
index 86d7810..8c23b21 100644
--- a/core/jni/android_util_AssetManager.cpp
+++ b/core/jni/android_util_AssetManager.cpp
@@ -881,6 +881,12 @@
   return static_cast<jint>(bag->entry_count);
 }
 
+static jint NativeGetParentThemeIdentifier(JNIEnv* env, jclass /*clazz*/, jlong ptr, jint resid) {
+  ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
+  const auto parentThemeResId = assetmanager->GetParentThemeResourceId(resid);
+  return parentThemeResId.value_or(0);
+}
+
 static jint NativeGetResourceIdentifier(JNIEnv* env, jclass /*clazz*/, jlong ptr, jstring name,
                                         jstring def_type, jstring def_package) {
   ScopedUtfChars name_utf8(env, name);
@@ -1464,6 +1470,8 @@
     {"nativeGetResourceIntArray", "(JI)[I", (void*)NativeGetResourceIntArray},
     {"nativeGetResourceArraySize", "(JI)I", (void*)NativeGetResourceArraySize},
     {"nativeGetResourceArray", "(JI[I)I", (void*)NativeGetResourceArray},
+    {"nativeGetParentThemeIdentifier", "(JI)I",
+     (void*)NativeGetParentThemeIdentifier},
 
     // AssetManager resource name/ID methods.
     {"nativeGetResourceIdentifier", "(JLjava/lang/String;Ljava/lang/String;Ljava/lang/String;)I",
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index fb5fded..67d0c52 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -597,6 +597,14 @@
     transaction->setPosition(ctrl, x, y);
 }
 
+static void nativeSetScale(JNIEnv* env, jclass clazz, jlong transactionObj, jlong nativeObject,
+                           jfloat xScale, jfloat yScale) {
+    auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
+
+    SurfaceControl* const ctrl = reinterpret_cast<SurfaceControl*>(nativeObject);
+    transaction->setMatrix(ctrl, xScale, 0, 0, yScale);
+}
+
 static void nativeSetGeometry(JNIEnv* env, jclass clazz, jlong transactionObj, jlong nativeObject,
         jobject sourceObj, jobject dstObj, jlong orientation) {
     auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
@@ -620,9 +628,19 @@
                             jobject bufferObject) {
     auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
     SurfaceControl* const ctrl = reinterpret_cast<SurfaceControl*>(nativeObject);
-    sp<GraphicBuffer> buffer(
-            android_graphics_GraphicBuffer_getNativeGraphicsBuffer(env, bufferObject));
-    transaction->setBuffer(ctrl, buffer);
+    sp<GraphicBuffer> graphicBuffer(GraphicBuffer::fromAHardwareBuffer(
+            android_hardware_HardwareBuffer_getNativeHardwareBuffer(env, bufferObject)));
+    transaction->setBuffer(ctrl, graphicBuffer);
+}
+
+static void nativeSetBufferTransform(JNIEnv* env, jclass clazz, jlong transactionObj,
+                                     jlong nativeObject, jint transform) {
+    auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
+    SurfaceControl* const ctrl = reinterpret_cast<SurfaceControl*>(nativeObject);
+    transaction->setTransform(ctrl, transform);
+    bool transformToInverseDisplay = (NATIVE_WINDOW_TRANSFORM_INVERSE_DISPLAY & transform) ==
+            NATIVE_WINDOW_TRANSFORM_INVERSE_DISPLAY;
+    transaction->setTransformToDisplayInverse(ctrl, transformToInverseDisplay);
 }
 
 static void nativeSetColorSpace(JNIEnv* env, jclass clazz, jlong transactionObj, jlong nativeObject,
@@ -740,6 +758,37 @@
     }
 }
 
+static void nativeSetDamageRegion(JNIEnv* env, jclass clazz, jlong transactionObj,
+                                  jlong nativeObject, jobject regionObj) {
+    SurfaceControl* const surfaceControl = reinterpret_cast<SurfaceControl*>(nativeObject);
+    auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
+
+    if (regionObj == nullptr) {
+        transaction->setSurfaceDamageRegion(surfaceControl, Region::INVALID_REGION);
+        return;
+    }
+
+    graphics::RegionIterator iterator(env, regionObj);
+    if (!iterator.isValid()) {
+        transaction->setSurfaceDamageRegion(surfaceControl, Region::INVALID_REGION);
+        return;
+    }
+
+    Region region;
+    while (!iterator.isDone()) {
+        ARect rect = iterator.getRect();
+        region.orSelf(static_cast<const Rect&>(rect));
+        iterator.next();
+    }
+
+    if (region.getBounds().isEmpty()) {
+        transaction->setSurfaceDamageRegion(surfaceControl, Region::INVALID_REGION);
+        return;
+    }
+
+    transaction->setSurfaceDamageRegion(surfaceControl, region);
+}
+
 static void nativeSetAlpha(JNIEnv* env, jclass clazz, jlong transactionObj,
         jlong nativeObject, jfloat alpha) {
     auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
@@ -1905,10 +1954,14 @@
             (void*)nativeSetRelativeLayer },
     {"nativeSetPosition", "(JJFF)V",
             (void*)nativeSetPosition },
+    {"nativeSetScale", "(JJFF)V",
+            (void*)nativeSetScale },
     {"nativeSetSize", "(JJII)V",
             (void*)nativeSetSize },
     {"nativeSetTransparentRegionHint", "(JJLandroid/graphics/Region;)V",
             (void*)nativeSetTransparentRegionHint },
+    { "nativeSetDamageRegion", "(JJLandroid/graphics/Region;)V",
+            (void*)nativeSetDamageRegion },
     {"nativeSetAlpha", "(JJF)V",
             (void*)nativeSetAlpha },
     {"nativeSetColor", "(JJ[F)V",
@@ -2018,8 +2071,9 @@
             (void*)nativeGetDisplayedContentSample },
     {"nativeSetGeometry", "(JJLandroid/graphics/Rect;Landroid/graphics/Rect;J)V",
             (void*)nativeSetGeometry },
-    {"nativeSetBuffer", "(JJLandroid/graphics/GraphicBuffer;)V",
+    {"nativeSetBuffer", "(JJLandroid/hardware/HardwareBuffer;)V",
             (void*)nativeSetBuffer },
+    {"nativeSetBufferTransform", "(JJI)V", (void*) nativeSetBufferTransform},
     {"nativeSetColorSpace", "(JJI)V",
             (void*)nativeSetColorSpace },
     {"nativeSyncInputWindows", "(J)V",
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 4c36732..0894877 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -2066,7 +2066,7 @@
     <permission android:name="android.permission.BLUETOOTH_PRIVILEGED"
         android:protectionLevel="signature|privileged" />
 
-    <!-- Control access to email providers exclusively for Bluetooth
+    <!-- @SystemApi Control access to email providers exclusively for Bluetooth
          @hide
     -->
     <permission android:name="android.permission.BLUETOOTH_MAP"
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BadgedImageView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BadgedImageView.java
index d92e2cc..686fbbf 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BadgedImageView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BadgedImageView.java
@@ -15,23 +15,22 @@
  */
 package com.android.wm.shell.bubbles;
 
-import static android.graphics.Paint.ANTI_ALIAS_FLAG;
-import static android.graphics.Paint.DITHER_FLAG;
-import static android.graphics.Paint.FILTER_BITMAP_FLAG;
-
+import android.annotation.DrawableRes;
 import android.annotation.Nullable;
 import android.content.Context;
+import android.content.res.TypedArray;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
 import android.graphics.Outline;
-import android.graphics.Paint;
-import android.graphics.PaintFlagsDrawFilter;
 import android.graphics.Path;
 import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
 import android.util.AttributeSet;
 import android.util.PathParser;
+import android.view.Gravity;
 import android.view.View;
 import android.view.ViewOutlineProvider;
+import android.widget.FrameLayout;
 import android.widget.ImageView;
 
 import com.android.launcher3.icons.DotRenderer;
@@ -47,7 +46,7 @@
  * Badge = the icon associated with the app that created this bubble, this will show work profile
  * badge if appropriate.
  */
-public class BadgedImageView extends ImageView {
+public class BadgedImageView extends FrameLayout {
 
     /** Same value as Launcher3 dot code */
     public static final float WHITE_SCRIM_ALPHA = 0.54f;
@@ -74,6 +73,9 @@
     private final EnumSet<SuppressionFlag> mDotSuppressionFlags =
             EnumSet.of(SuppressionFlag.FLYOUT_VISIBLE);
 
+    private final ImageView mBubbleIcon;
+    private final ImageView mAppIcon;
+
     private float mDotScale = 0f;
     private float mAnimatingToDotScale = 0f;
     private boolean mDotIsAnimating = false;
@@ -86,7 +88,6 @@
     private DotRenderer.DrawParams mDrawParams;
     private int mDotColor;
 
-    private Paint mPaint = new Paint(ANTI_ALIAS_FLAG);
     private Rect mTempBounds = new Rect();
 
     public BadgedImageView(Context context) {
@@ -104,6 +105,17 @@
     public BadgedImageView(Context context, AttributeSet attrs, int defStyleAttr,
             int defStyleRes) {
         super(context, attrs, defStyleAttr, defStyleRes);
+
+        mBubbleIcon = new ImageView(context);
+        addView(mBubbleIcon);
+        mAppIcon = new ImageView(context);
+        addView(mAppIcon);
+
+        final TypedArray ta = mContext.obtainStyledAttributes(attrs, new int[]{android.R.attr.src},
+                defStyleAttr, defStyleRes);
+        mBubbleIcon.setImageResource(ta.getResourceId(0, 0));
+        ta.recycle();
+
         mDrawParams = new DotRenderer.DrawParams();
 
         setFocusable(true);
@@ -135,7 +147,6 @@
     public void showDotAndBadge(boolean onLeft) {
         removeDotSuppressionFlag(BadgedImageView.SuppressionFlag.BEHIND_STACK);
         animateDotBadgePositions(onLeft);
-
     }
 
     public void hideDotAndBadge(boolean onLeft) {
@@ -149,6 +160,7 @@
      */
     public void setRenderedBubble(BubbleViewProvider bubble) {
         mBubble = bubble;
+        mBubbleIcon.setImageBitmap(bubble.getBubbleIcon());
         if (mDotSuppressionFlags.contains(SuppressionFlag.BEHIND_STACK)) {
             hideBadge();
         } else {
@@ -176,6 +188,20 @@
         mDotRenderer.draw(canvas, mDrawParams);
     }
 
+    /**
+     * Set drawable resource shown as the icon
+     */
+    public void setIconImageResource(@DrawableRes int drawable) {
+        mBubbleIcon.setImageResource(drawable);
+    }
+
+    /**
+     * Get icon drawable
+     */
+    public Drawable getIconDrawable() {
+        return mBubbleIcon.getDrawable();
+    }
+
     /** Adds a dot suppression flag, updating dot visibility if needed. */
     void addDotSuppressionFlag(SuppressionFlag flag) {
         if (mDotSuppressionFlags.add(flag)) {
@@ -279,7 +305,6 @@
         showBadge();
     }
 
-
     /** Whether to draw the dot in onDraw(). */
     private boolean shouldDrawDot() {
         // Always render the dot if it's animating, since it could be animating out. Otherwise, show
@@ -325,29 +350,28 @@
     void showBadge() {
         Bitmap badge = mBubble.getAppBadge();
         if (badge == null) {
-            setImageBitmap(mBubble.getBubbleIcon());
+            mAppIcon.setVisibility(GONE);
             return;
         }
-        Canvas bubbleCanvas = new Canvas();
-        Bitmap noBadgeBubble = mBubble.getBubbleIcon();
-        Bitmap bubble = noBadgeBubble.copy(noBadgeBubble.getConfig(), /* isMutable */ true);
 
-        bubbleCanvas.setDrawFilter(new PaintFlagsDrawFilter(DITHER_FLAG, FILTER_BITMAP_FLAG));
-        bubbleCanvas.setBitmap(bubble);
-        final int bubbleSize = bubble.getWidth();
+        final int bubbleSize = mBubble.getBubbleIcon().getWidth();
         final int badgeSize = (int) (ICON_BADGE_SCALE * bubbleSize);
-        Rect dest = new Rect();
+
+        FrameLayout.LayoutParams appIconParams = (LayoutParams) mAppIcon.getLayoutParams();
+        appIconParams.height = badgeSize;
+        appIconParams.width = badgeSize;
         if (mOnLeft) {
-            dest.set(0, bubbleSize - badgeSize, badgeSize, bubbleSize);
+            appIconParams.gravity = Gravity.BOTTOM | Gravity.LEFT;
         } else {
-            dest.set(bubbleSize - badgeSize, bubbleSize - badgeSize, bubbleSize, bubbleSize);
+            appIconParams.gravity = Gravity.BOTTOM | Gravity.RIGHT;
         }
-        bubbleCanvas.drawBitmap(badge, null /* src */, dest, mPaint);
-        bubbleCanvas.setBitmap(null);
-        setImageBitmap(bubble);
+        mAppIcon.setLayoutParams(appIconParams);
+
+        mAppIcon.setImageBitmap(badge);
+        mAppIcon.setVisibility(VISIBLE);
     }
 
     void hideBadge() {
-        setImageBitmap(mBubble.getBubbleIcon());
+        mAppIcon.setVisibility(GONE);
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
index 519a856..cd635c1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
@@ -328,6 +328,7 @@
         if (prevBubble == null) {
             // Create a new bubble
             bubble.setSuppressFlyout(suppressFlyout);
+            bubble.markUpdatedAt(mTimeSource.currentTimeMillis());
             doAdd(bubble);
             trim();
         } else {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt
index 5161092..a175929 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt
@@ -66,7 +66,7 @@
         updateResources()
         getExpandedView()?.applyThemeAttrs()
         // Apply inset and new style to fresh icon drawable.
-        getIconView()?.setImageResource(R.drawable.bubble_ic_overflow_button)
+        getIconView()?.setIconImageResource(R.drawable.bubble_ic_overflow_button)
         updateBtnTheme()
     }
 
@@ -89,19 +89,19 @@
         dotColor = colorAccent
 
         val shapeColor = res.getColor(android.R.color.system_accent1_1000)
-        overflowBtn?.drawable?.setTint(shapeColor)
+        overflowBtn?.iconDrawable?.setTint(shapeColor)
 
         val iconFactory = BubbleIconFactory(context)
 
         // Update bitmap
-        val fg = InsetDrawable(overflowBtn?.drawable, overflowIconInset)
+        val fg = InsetDrawable(overflowBtn?.iconDrawable, overflowIconInset)
         bitmap = iconFactory.createBadgedIconBitmap(
                 AdaptiveIconDrawable(ColorDrawable(colorAccent), fg)).icon
 
         // Update dot path
         dotPath = PathParser.createPathFromPathData(
             res.getString(com.android.internal.R.string.config_icon_mask))
-        val scale = iconFactory.normalizer.getScale(iconView!!.drawable,
+        val scale = iconFactory.normalizer.getScale(iconView!!.iconDrawable,
             null /* outBounds */, null /* path */, null /* outMaskShape */)
         val radius = BadgedImageView.DEFAULT_PATH_SIZE / 2f
         val matrix = Matrix()
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/ScreenshotUtils.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/ScreenshotUtils.java
index eea6e3c..c4bd73b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/ScreenshotUtils.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/ScreenshotUtils.java
@@ -16,7 +16,6 @@
 
 package com.android.wm.shell.common;
 
-import android.graphics.GraphicBuffer;
 import android.graphics.PixelFormat;
 import android.graphics.Rect;
 import android.view.SurfaceControl;
@@ -63,8 +62,6 @@
             if (buffer == null || buffer.getHardwareBuffer() == null) {
                 return;
             }
-            final GraphicBuffer graphicBuffer = GraphicBuffer.createFromHardwareBuffer(
-                buffer.getHardwareBuffer());
             mScreenshot = new SurfaceControl.Builder()
                 .setName("ScreenshotUtils screenshot")
                 .setFormat(PixelFormat.TRANSLUCENT)
@@ -73,7 +70,7 @@
                 .setBLASTLayer()
                 .build();
 
-            mTransaction.setBuffer(mScreenshot, graphicBuffer);
+            mTransaction.setBuffer(mScreenshot, buffer.getHardwareBuffer());
             mTransaction.setColorSpace(mScreenshot, buffer.getColorSpace());
             mTransaction.reparent(mScreenshot, mSurfaceControl);
             mTransaction.setLayer(mScreenshot, mLayer);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
index 040fffae..b8ac87f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
@@ -93,7 +93,7 @@
     private final InsetsState mInsetsState = new InsetsState();
 
     private Context mContext;
-    private DividerSnapAlgorithm mDividerSnapAlgorithm;
+    @VisibleForTesting DividerSnapAlgorithm mDividerSnapAlgorithm;
     private WindowContainerToken mWinToken1;
     private WindowContainerToken mWinToken2;
     private int mDividePosition;
@@ -294,20 +294,22 @@
         mSplitLayoutHandler.onLayoutSizeChanging(this);
     }
 
-    void setDividePosition(int position) {
+    void setDividePosition(int position, boolean applyLayoutChange) {
         mDividePosition = position;
         updateBounds(mDividePosition);
-        mSplitLayoutHandler.onLayoutSizeChanged(this);
+        if (applyLayoutChange) {
+            mSplitLayoutHandler.onLayoutSizeChanged(this);
+        }
     }
 
-    /** Sets divide position base on the ratio within root bounds. */
+    /** Updates divide position and split bounds base on the ratio within root bounds. */
     public void setDivideRatio(float ratio) {
         final int position = isLandscape()
                 ? mRootBounds.left + (int) (mRootBounds.width() * ratio)
                 : mRootBounds.top + (int) (mRootBounds.height() * ratio);
-        DividerSnapAlgorithm.SnapTarget snapTarget =
+        final DividerSnapAlgorithm.SnapTarget snapTarget =
                 mDividerSnapAlgorithm.calculateNonDismissingSnapTarget(position);
-        setDividePosition(snapTarget.position);
+        setDividePosition(snapTarget.position, false /* applyLayoutChange */);
     }
 
     /** Resets divider position. */
@@ -336,7 +338,7 @@
                 break;
             default:
                 flingDividePosition(currentPosition, snapTarget.position,
-                        () -> setDividePosition(snapTarget.position));
+                        () -> setDividePosition(snapTarget.position, true /* applyLayoutChange */));
                 break;
         }
     }
@@ -389,7 +391,7 @@
 
             @Override
             public void onAnimationCancel(Animator animation) {
-                setDividePosition(to);
+                setDividePosition(to, true /* applyLayoutChange */);
             }
         });
         animator.start();
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 ed5de0d..cdaa54c 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
@@ -342,12 +342,12 @@
         sideOptions = sideOptions != null ? sideOptions : new Bundle();
         setSideStagePosition(sidePosition, wct);
 
+        mSplitLayout.setDivideRatio(splitRatio);
         // Build a request WCT that will launch both apps such that task 0 is on the main stage
         // while task 1 is on the side stage.
         mMainStage.activate(getMainStageBounds(), wct, false /* reparent */);
         mSideStage.setBounds(getSideStageBounds(), wct);
 
-        mSplitLayout.setDivideRatio(splitRatio);
         // Make sure the launch options will put tasks in the corresponding split roots
         addActivityOptions(mainOptions, mMainStage);
         addActivityOptions(sideOptions, mSideStage);
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 6643ca1..4ecc0b6 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
@@ -380,9 +380,7 @@
     }
 
     private void drawSizeMatchSnapshot() {
-        GraphicBuffer graphicBuffer = GraphicBuffer.createFromHardwareBuffer(
-                mSnapshot.getHardwareBuffer());
-        mTransaction.setBuffer(mSurfaceControl, graphicBuffer)
+        mTransaction.setBuffer(mSurfaceControl, mSnapshot.getHardwareBuffer())
                 .setColorSpace(mSurfaceControl, mSnapshot.getColorSpace())
                 .apply();
     }
@@ -428,20 +426,20 @@
         // Scale the mismatch dimensions to fill the task bounds
         mSnapshotMatrix.setRectToRect(mTmpSnapshotSize, mTmpDstFrame, Matrix.ScaleToFit.FILL);
         mTransaction.setMatrix(childSurfaceControl, mSnapshotMatrix, mTmpFloat9);
-        GraphicBuffer graphicBuffer = GraphicBuffer.createFromHardwareBuffer(
-                mSnapshot.getHardwareBuffer());
         mTransaction.setColorSpace(childSurfaceControl, mSnapshot.getColorSpace());
-        mTransaction.setBuffer(childSurfaceControl, graphicBuffer);
+        mTransaction.setBuffer(childSurfaceControl, mSnapshot.getHardwareBuffer());
 
         if (aspectRatioMismatch) {
             GraphicBuffer background = GraphicBuffer.create(mFrame.width(), mFrame.height(),
                     PixelFormat.RGBA_8888,
                     GraphicBuffer.USAGE_HW_TEXTURE | GraphicBuffer.USAGE_HW_COMPOSER
                             | GraphicBuffer.USAGE_SW_WRITE_RARELY);
+            // TODO: Support this on HardwareBuffer
             final Canvas c = background.lockCanvas();
             drawBackgroundAndBars(c, frame);
             background.unlockCanvasAndPost(c);
-            mTransaction.setBuffer(mSurfaceControl, background);
+            mTransaction.setBuffer(mSurfaceControl,
+                    HardwareBuffer.createFromGraphicBuffer(background));
         }
         mTransaction.apply();
         childSurfaceControl.release();
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java
index bc701d0..8bc1223 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java
@@ -39,6 +39,7 @@
 import android.service.notification.StatusBarNotification;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
+import android.util.Log;
 import android.util.Pair;
 import android.view.WindowManager;
 
@@ -913,6 +914,31 @@
         assertSelectionChangedTo(mBubbleA2);
     }
 
+    /**
+      * - have a maxed out bubble stack & all of the bubbles have been recently accessed
+      * - bubble a notification that was posted before any of those bubbles were accessed
+      * => that bubble should be added
+     *
+      */
+    @Test
+    public void test_addOldNotifWithNewerBubbles() {
+        sendUpdatedEntryAtTime(mEntryA1, 2000);
+        sendUpdatedEntryAtTime(mEntryA2, 3000);
+        sendUpdatedEntryAtTime(mEntryA3, 4000);
+        sendUpdatedEntryAtTime(mEntryB1, 5000);
+        sendUpdatedEntryAtTime(mEntryB2, 6000);
+
+        mBubbleData.setListener(mListener);
+        sendUpdatedEntryAtTime(mEntryB3, 1000 /* postTime */, 7000 /* currentTime */);
+        verifyUpdateReceived();
+
+        // B3 is in the stack
+        assertThat(mBubbleData.getBubbleInStackWithKey(mBubbleB3.getKey())).isNotNull();
+        // A1 is the oldest so it's in the overflow
+        assertThat(mBubbleData.getOverflowBubbleWithKey(mEntryA1.getKey())).isNotNull();
+        assertOrderChangedTo(mBubbleB3, mBubbleB2, mBubbleB1, mBubbleA3, mBubbleA2);
+    }
+
     private void verifyUpdateReceived() {
         verify(mListener).applyUpdate(mUpdateCaptor.capture());
         reset(mListener);
@@ -1014,6 +1040,12 @@
     }
 
     private void sendUpdatedEntryAtTime(BubbleEntry entry, long postTime) {
+        setCurrentTime(postTime);
+        sendUpdatedEntryAtTime(entry, postTime, true /* isTextChanged */);
+    }
+
+    private void sendUpdatedEntryAtTime(BubbleEntry entry, long postTime, long currentTime) {
+        setCurrentTime(currentTime);
         sendUpdatedEntryAtTime(entry, postTime, true /* isTextChanged */);
     }
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java
index 453050f..83d5f04 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java
@@ -24,6 +24,7 @@
 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.spy;
 import static org.mockito.Mockito.verify;
 
@@ -101,14 +102,21 @@
 
     @Test
     public void testSetDividePosition() {
-        mSplitLayout.setDividePosition(anyInt());
+        mSplitLayout.setDividePosition(100, false /* applyLayoutChange */);
+        assertThat(mSplitLayout.getDividePosition()).isEqualTo(100);
+        verify(mSplitLayoutHandler, never()).onLayoutSizeChanged(any(SplitLayout.class));
+
+        mSplitLayout.setDividePosition(200, true /* applyLayoutChange */);
+        assertThat(mSplitLayout.getDividePosition()).isEqualTo(200);
         verify(mSplitLayoutHandler).onLayoutSizeChanged(any(SplitLayout.class));
     }
 
     @Test
     public void testSetDivideRatio() {
+        mSplitLayout.setDividePosition(200, false /* applyLayoutChange */);
         mSplitLayout.setDivideRatio(0.5f);
-        verify(mSplitLayoutHandler).onLayoutSizeChanged(any(SplitLayout.class));
+        assertThat(mSplitLayout.getDividePosition()).isEqualTo(
+                mSplitLayout.mDividerSnapAlgorithm.getMiddleTarget().position);
     }
 
     @Test
diff --git a/libs/androidfw/AssetManager2.cpp b/libs/androidfw/AssetManager2.cpp
index 22904a0..136fc6c 100644
--- a/libs/androidfw/AssetManager2.cpp
+++ b/libs/androidfw/AssetManager2.cpp
@@ -902,6 +902,27 @@
   return log_stream.str();
 }
 
+base::expected<uint32_t, NullOrIOError> AssetManager2::GetParentThemeResourceId(uint32_t resid)
+const {
+  auto entry = FindEntry(resid, 0u /* density_override */,
+                         false /* stop_at_first_match */,
+                         false /* ignore_configuration */);
+  if (!entry.has_value()) {
+    return base::unexpected(entry.error());
+  }
+
+  auto entry_map = std::get_if<incfs::verified_map_ptr<ResTable_map_entry>>(&entry->entry);
+  if (entry_map == nullptr) {
+    // Not a bag, nothing to do.
+    return base::unexpected(std::nullopt);
+  }
+
+  auto map = *entry_map;
+  const uint32_t parent_resid = dtohl(map->parent.ident);
+
+  return parent_resid;
+}
+
 base::expected<AssetManager2::ResourceName, NullOrIOError> AssetManager2::GetResourceName(
     uint32_t resid) const {
   auto result = FindEntry(resid, 0u /* density_override */, true /* stop_at_first_match */,
diff --git a/libs/androidfw/TEST_MAPPING b/libs/androidfw/TEST_MAPPING
index 9ebc996..8abe79d 100644
--- a/libs/androidfw/TEST_MAPPING
+++ b/libs/androidfw/TEST_MAPPING
@@ -2,6 +2,9 @@
   "presubmit": [
     {
       "name": "CtsResourcesLoaderTests"
+    },
+    {
+      "name": "libandroidfw_tests"
     }
   ]
 }
diff --git a/libs/androidfw/include/androidfw/AssetManager2.h b/libs/androidfw/include/androidfw/AssetManager2.h
index a3b42df..1bde792 100644
--- a/libs/androidfw/include/androidfw/AssetManager2.h
+++ b/libs/androidfw/include/androidfw/AssetManager2.h
@@ -192,6 +192,12 @@
   std::unique_ptr<Asset> OpenNonAsset(const std::string& filename, ApkAssetsCookie cookie,
                                       Asset::AccessMode mode) const;
 
+  // Returns the resource id of parent style of the specified theme.
+  //
+  // Returns a null error if the name is missing/corrupt, or an I/O error if reading resource data
+  // failed.
+  base::expected<uint32_t, NullOrIOError> GetParentThemeResourceId(uint32_t resid) const;
+
   // Returns the resource name of the specified resource ID.
   //
   // Utf8 strings are preferred, and only if they are unavailable are the Utf16 variants populated.
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index 147e735..7f6fb90 100755
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -460,4 +460,8 @@
             boolean register);
 
     void setTestDeviceConnectionState(in AudioDeviceAttributes device, boolean connected);
+
+    List<AudioFocusInfo> getFocusStack();
+
+    boolean sendFocusLoss(in AudioFocusInfo focusLoser, in IAudioPolicyCallback apcb);
 }
diff --git a/media/java/android/media/audiopolicy/AudioPolicy.java b/media/java/android/media/audiopolicy/AudioPolicy.java
index 0f08d79..3ba1d1f 100644
--- a/media/java/android/media/audiopolicy/AudioPolicy.java
+++ b/media/java/android/media/audiopolicy/AudioPolicy.java
@@ -19,6 +19,7 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
 import android.annotation.UserIdInt;
@@ -230,7 +231,7 @@
          * If set to {@code true}, it is mandatory to set an
          * {@link AudioPolicy.AudioPolicyFocusListener} in order to successfully build
          * an {@code AudioPolicy} instance.
-         * @param enforce true if the policy will govern audio focus decisions.
+         * @param isFocusPolicy true if the policy will govern audio focus decisions.
          * @return the same Builder instance.
          */
         @NonNull
@@ -723,6 +724,45 @@
     }
 
     /**
+     * Returns the list of entries in the focus stack.
+     * The list is ordered with increasing rank of focus ownership, where the last entry is at the
+     * top of the focus stack and is the current focus owner.
+     * @return the ordered list of focus owners
+     * @see AudioManager#registerAudioPolicy(AudioPolicy)
+     */
+    @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+    public @NonNull List<AudioFocusInfo> getFocusStack() {
+        try {
+            return getService().getFocusStack();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Send AUDIOFOCUS_LOSS to a specific stack entry, causing it to be notified of the focus
+     * loss, and for it to exit the focus stack (its focus listener will not be invoked after that).
+     * This operation is only valid for a registered policy (with
+     * {@link AudioManager#registerAudioPolicy(AudioPolicy)}) that is also set as the policy focus
+     * listener (with {@link Builder#setAudioPolicyFocusListener(AudioPolicyFocusListener)}.
+     * @param focusLoser the stack entry that is exiting the stack through a focus loss
+     * @return false if the focusLoser wasn't found in the stack, true otherwise
+     * @throws IllegalStateException if used on an unregistered policy, or a registered policy
+     *     with no {@link AudioPolicyFocusListener} set
+     * @see AudioManager#registerAudioPolicy(AudioPolicy)
+     * @see Builder#setAudioPolicyStatusListener(AudioPolicyStatusListener)
+     */
+    @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+    public boolean sendFocusLoss(@NonNull AudioFocusInfo focusLoser) throws IllegalStateException {
+        Objects.requireNonNull(focusLoser);
+        try {
+            return getService().sendFocusLoss(focusLoser, cb());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Create an {@link AudioRecord} instance that is associated with the given {@link AudioMix}.
      * Audio buffers recorded through the created instance will contain the mix of the audio
      * streams that fed the given mixer.
diff --git a/media/java/android/media/tv/interactive/ITvIAppManager.aidl b/media/java/android/media/tv/interactive/ITvIAppManager.aidl
index cd87a09..9859a5f 100644
--- a/media/java/android/media/tv/interactive/ITvIAppManager.aidl
+++ b/media/java/android/media/tv/interactive/ITvIAppManager.aidl
@@ -22,6 +22,7 @@
 import android.media.tv.interactive.ITvIAppManagerCallback;
 import android.media.tv.interactive.TvIAppInfo;
 import android.net.Uri;
+import android.os.Bundle;
 import android.view.Surface;
 
 /**
@@ -31,6 +32,7 @@
 interface ITvIAppManager {
     List<TvIAppInfo> getTvIAppServiceList(int userId);
     void prepare(String tiasId, int type, int userId);
+    void notifyAppLinkInfo(String tiasId, in Bundle info, int userId);
     void startIApp(in IBinder sessionToken, int userId);
     void createSession(
             in ITvIAppClient client, in String iAppServiceId, int type, int seq, int userId);
diff --git a/media/java/android/media/tv/interactive/ITvIAppService.aidl b/media/java/android/media/tv/interactive/ITvIAppService.aidl
index af15dd8..72db3fa 100644
--- a/media/java/android/media/tv/interactive/ITvIAppService.aidl
+++ b/media/java/android/media/tv/interactive/ITvIAppService.aidl
@@ -18,6 +18,7 @@
 
 import android.media.tv.interactive.ITvIAppServiceCallback;
 import android.media.tv.interactive.ITvIAppSessionCallback;
+import android.os.Bundle;
 import android.view.InputChannel;
 
 /**
@@ -31,4 +32,5 @@
     void createSession(in InputChannel channel, in ITvIAppSessionCallback callback,
             in String iAppServiceId, int type);
     void prepare(int type);
+    void notifyAppLinkInfo(in Bundle info);
 }
\ No newline at end of file
diff --git a/media/java/android/media/tv/interactive/TvIAppManager.java b/media/java/android/media/tv/interactive/TvIAppManager.java
index 2272084..8766055 100644
--- a/media/java/android/media/tv/interactive/TvIAppManager.java
+++ b/media/java/android/media/tv/interactive/TvIAppManager.java
@@ -26,6 +26,7 @@
 import android.media.tv.BroadcastInfoResponse;
 import android.media.tv.TvInputManager;
 import android.net.Uri;
+import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Looper;
@@ -87,6 +88,51 @@
      */
     public static final int TV_IAPP_RTE_STATE_ERROR = 4;
 
+    /**
+     * Key for package name in app link.
+     * <p>Type: String
+     *
+     * @see #notifyAppLinkInfo(String, Bundle)
+     * @hide
+     */
+    public static final String KEY_PACKAGE_NAME = "package_name";
+
+    /**
+     * Key for class name in app link.
+     * <p>Type: String
+     *
+     * @see #notifyAppLinkInfo(String, Bundle)
+     * @hide
+     */
+    public static final String KEY_CLASS_NAME = "class_name";
+
+    /**
+     * Key for URI scheme in app link.
+     * <p>Type: String
+     *
+     * @see #notifyAppLinkInfo(String, Bundle)
+     * @hide
+     */
+    public static final String KEY_URI_SCHEME = "uri_scheme";
+
+    /**
+     * Key for URI host in app link.
+     * <p>Type: String
+     *
+     * @see #notifyAppLinkInfo(String, Bundle)
+     * @hide
+     */
+    public static final String KEY_URI_HOST = "uri_host";
+
+    /**
+     * Key for URI prefix in app link.
+     * <p>Type: String
+     *
+     * @see #notifyAppLinkInfo(String, Bundle)
+     * @hide
+     */
+    public static final String KEY_URI_PREFIX = "uri_prefix";
+
     private final ITvIAppManager mService;
     private final int mUserId;
 
@@ -418,6 +464,18 @@
     }
 
     /**
+     * Notifies app link info.
+     * @hide
+     */
+    public void notifyAppLinkInfo(String tvIAppServiceId, Bundle appLinkInfo) {
+        try {
+            mService.notifyAppLinkInfo(tvIAppServiceId, appLinkInfo, mUserId);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Registers a {@link TvIAppManager.TvIAppCallback}.
      *
      * @param callback A callback used to monitor status of the TV IApp services.
diff --git a/media/java/android/media/tv/interactive/TvIAppService.java b/media/java/android/media/tv/interactive/TvIAppService.java
index f93b597..6bf8028 100644
--- a/media/java/android/media/tv/interactive/TvIAppService.java
+++ b/media/java/android/media/tv/interactive/TvIAppService.java
@@ -30,6 +30,7 @@
 import android.media.tv.BroadcastInfoResponse;
 import android.net.Uri;
 import android.os.AsyncTask;
+import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Looper;
@@ -123,6 +124,11 @@
             public void prepare(int type) {
                 onPrepare(type);
             }
+
+            @Override
+            public void notifyAppLinkInfo(Bundle appLinkInfo) {
+                onAppLinkInfo(appLinkInfo);
+            }
         };
         return tvIAppServiceBinder;
     }
@@ -135,6 +141,14 @@
         // TODO: make it abstract when unhide
     }
 
+    /**
+     * Registers App link info.
+     * @hide
+     */
+    public void onAppLinkInfo(Bundle appLinkInfo) {
+        // TODO: make it abstract when unhide
+    }
+
 
     /**
      * Returns a concrete implementation of {@link Session}.
diff --git a/media/java/android/media/tv/tuner/Tuner.java b/media/java/android/media/tv/tuner/Tuner.java
index 94de7fa..255b391b 100644
--- a/media/java/android/media/tv/tuner/Tuner.java
+++ b/media/java/android/media/tv/tuner/Tuner.java
@@ -285,7 +285,7 @@
     @Nullable
     private FrontendInfo mFrontendInfo;
     private Integer mFrontendHandle;
-    private Boolean mIsSharedFrontend = false;
+    private Tuner mFeOwnerTuner = null;
     private int mFrontendType = FrontendSettings.TYPE_UNDEFINED;
     private int mUserId;
     private Lnb mLnb;
@@ -442,11 +442,10 @@
         mFrontendLock.lock();
         try {
             mTunerResourceManager.shareFrontend(mClientId, tuner.mClientId);
-            synchronized (mIsSharedFrontend) {
-                mFrontendHandle = tuner.mFrontendHandle;
-                mFrontend = tuner.mFrontend;
-                mIsSharedFrontend = true;
-            }
+            mFeOwnerTuner = tuner;
+            mFeOwnerTuner.registerFrontendCallbackListener(this);
+            mFrontendHandle = mFeOwnerTuner.mFrontendHandle;
+            mFrontend = mFeOwnerTuner.mFrontend;
             nativeShareFrontend(mFrontend.mId);
         } finally {
             releaseTRMSLock();
@@ -513,6 +512,27 @@
     private long mNativeContext; // used by native jMediaTuner
 
     /**
+     * Registers a tuner as a listener for frontend callbacks.
+     */
+    private void registerFrontendCallbackListener(Tuner tuner) {
+        nativeRegisterFeCbListener(tuner.getNativeContext());
+    }
+
+    /**
+     * Unregisters a tuner as a listener for frontend callbacks.
+     */
+    private void unregisterFrontendCallbackListener(Tuner tuner) {
+        nativeUnregisterFeCbListener(tuner.getNativeContext());
+    }
+
+    /**
+     * Returns the pointer to the associated JTuner.
+     */
+    long getNativeContext() {
+        return mNativeContext;
+    }
+
+    /**
      * Releases the Tuner instance.
      */
     @Override
@@ -526,19 +546,21 @@
         }
     }
 
-    private void releaseAll() {
+    private void releaseFrontend() {
         mFrontendLock.lock();
         try {
             if (mFrontendHandle != null) {
-                synchronized (mIsSharedFrontend) {
-                    if (!mIsSharedFrontend) {
-                        int res = nativeCloseFrontend(mFrontendHandle);
-                        if (res != Tuner.RESULT_SUCCESS) {
-                            TunerUtils.throwExceptionForResult(res, "failed to close frontend");
-                        }
-                        mTunerResourceManager.releaseFrontend(mFrontendHandle, mClientId);
+                if (mFeOwnerTuner != null) {
+                    // unregister self from the Frontend callback
+                    mFeOwnerTuner.unregisterFrontendCallbackListener(this);
+                    mFeOwnerTuner = null;
+                } else {
+                    // close resource as owner
+                    int res = nativeCloseFrontend(mFrontendHandle);
+                    if (res != Tuner.RESULT_SUCCESS) {
+                        TunerUtils.throwExceptionForResult(res, "failed to close frontend");
                     }
-                    mIsSharedFrontend = false;
+                    mTunerResourceManager.releaseFrontend(mFrontendHandle, mClientId);
                 }
                 FrameworkStatsLog
                         .write(FrameworkStatsLog.TV_TUNER_STATE_CHANGED, mUserId,
@@ -549,9 +571,14 @@
         } finally {
             mFrontendLock.unlock();
         }
+    }
+
+    private void releaseAll() {
+        releaseFrontend();
 
         mLnbLock.lock();
         try {
+            // mLnb will be non-null only for owner tuner
             if (mLnb != null) {
                 mLnb.close();
             }
@@ -641,6 +668,8 @@
      */
     private native Frontend nativeOpenFrontendByHandle(int handle);
     private native int nativeShareFrontend(int id);
+    private native void nativeRegisterFeCbListener(long nativeContext);
+    private native void nativeUnregisterFeCbListener(long nativeContext);
     @Result
     private native int nativeTune(int type, FrontendSettings settings);
     private native int nativeStopTune();
@@ -1276,7 +1305,7 @@
     }
 
     private void onFrontendEvent(int eventType) {
-        Log.d(TAG, "Got event from tuning. Event type: " + eventType);
+        Log.d(TAG, "Got event from tuning. Event type: " + eventType + " for " + this);
         synchronized (mOnTuneEventLock) {
             if (mOnTuneEventExecutor != null && mOnTuneEventListener != null) {
                 mOnTuneEventExecutor.execute(() -> {
diff --git a/media/java/android/media/tv/tuner/dvr/DvrPlayback.java b/media/java/android/media/tv/tuner/dvr/DvrPlayback.java
index cfd8583..6f3ab03 100644
--- a/media/java/android/media/tv/tuner/dvr/DvrPlayback.java
+++ b/media/java/android/media/tv/tuner/dvr/DvrPlayback.java
@@ -98,6 +98,7 @@
     private native void nativeSetFileDescriptor(int fd);
     private native long nativeRead(long size);
     private native long nativeRead(byte[] bytes, long offset, long size);
+    private native long nativeSeek(long pos);
 
     private DvrPlayback() {
         mUserId = Process.myUid();
@@ -243,7 +244,7 @@
      *
      * @param fd the file descriptor to read data.
      * @see #read(long)
-     * @see #read(byte[], long, long)
+     * @see #seek(long)
      */
     public void setFileDescriptor(@NonNull ParcelFileDescriptor fd) {
         nativeSetFileDescriptor(fd.getFd());
@@ -261,19 +262,30 @@
     }
 
     /**
-     * Reads data from the buffer for DVR playback and copies to the given byte array.
+     * Reads data from the buffer for DVR playback.
      *
-     * @param bytes the byte array to store the data.
-     * @param offset the index of the first byte in {@code bytes} to copy to.
+     * @param buffer the byte array where DVR reads data from.
+     * @param offset the index of the first byte in {@code buffer} to read.
      * @param size the maximum number of bytes to read.
      * @return the number of bytes read.
      */
     @BytesLong
-    public long read(@NonNull byte[] bytes, @BytesLong long offset, @BytesLong long size) {
-        if (size + offset > bytes.length) {
+    public long read(@NonNull byte[] buffer, @BytesLong long offset, @BytesLong long size) {
+        if (size + offset > buffer.length) {
             throw new ArrayIndexOutOfBoundsException(
-                    "Array length=" + bytes.length + ", offset=" + offset + ", size=" + size);
+                    "Array length=" + buffer.length + ", offset=" + offset + ", size=" + size);
         }
-        return nativeRead(bytes, offset, size);
+        return nativeRead(buffer, offset, size);
+    }
+
+    /**
+     * Sets the file pointer offset of the file descriptor.
+     *
+     * @param pos the offset position, measured in bytes from the beginning of the file.
+     * @return the new offset position.
+     */
+    @BytesLong
+    public long seek(@BytesLong long pos) {
+        return nativeSeek(pos);
     }
 }
diff --git a/media/java/android/media/tv/tuner/dvr/DvrRecorder.java b/media/java/android/media/tv/tuner/dvr/DvrRecorder.java
index 212a713..e72026a 100644
--- a/media/java/android/media/tv/tuner/dvr/DvrRecorder.java
+++ b/media/java/android/media/tv/tuner/dvr/DvrRecorder.java
@@ -216,7 +216,6 @@
      *
      * @param fd the file descriptor to write data.
      * @see #write(long)
-     * @see #write(byte[], long, long)
      */
     public void setFileDescriptor(@NonNull ParcelFileDescriptor fd) {
         nativeSetFileDescriptor(fd.getFd());
@@ -236,17 +235,17 @@
     /**
      * Writes recording data to buffer.
      *
-     * @param bytes the byte array stores the data to be written to DVR.
-     * @param offset the index of the first byte in {@code bytes} to be written to DVR.
+     * @param buffer the byte array stores the data from DVR.
+     * @param offset the index of the first byte in {@code buffer} to write the data from DVR.
      * @param size the maximum number of bytes to write.
      * @return the number of bytes written.
      */
     @BytesLong
-    public long write(@NonNull byte[] bytes, @BytesLong long offset, @BytesLong long size) {
-        if (size + offset > bytes.length) {
+    public long write(@NonNull byte[] buffer, @BytesLong long offset, @BytesLong long size) {
+        if (size + offset > buffer.length) {
             throw new ArrayIndexOutOfBoundsException(
-                    "Array length=" + bytes.length + ", offset=" + offset + ", size=" + size);
+                    "Array length=" + buffer.length + ", offset=" + offset + ", size=" + size);
         }
-        return nativeWrite(bytes, offset, size);
+        return nativeWrite(buffer, offset, size);
     }
 }
diff --git a/media/java/android/media/tv/tuner/filter/DownloadEvent.java b/media/java/android/media/tv/tuner/filter/DownloadEvent.java
index 394211be..25989db 100644
--- a/media/java/android/media/tv/tuner/filter/DownloadEvent.java
+++ b/media/java/android/media/tv/tuner/filter/DownloadEvent.java
@@ -27,15 +27,17 @@
 @SystemApi
 public class DownloadEvent extends FilterEvent {
     private final int mItemId;
+    private final int mDownloadId;
     private final int mMpuSequenceNumber;
     private final int mItemFragmentIndex;
     private final int mLastItemFragmentIndex;
     private final int mDataLength;
 
     // This constructor is used by JNI code only
-    private DownloadEvent(int itemId, int mpuSequenceNumber, int itemFragmentIndex,
+    private DownloadEvent(int itemId, int downloadId, int mpuSequenceNumber, int itemFragmentIndex,
             int lastItemFragmentIndex, int dataLength) {
         mItemId = itemId;
+        mDownloadId = downloadId;
         mMpuSequenceNumber = mpuSequenceNumber;
         mItemFragmentIndex = itemFragmentIndex;
         mLastItemFragmentIndex = lastItemFragmentIndex;
@@ -50,6 +52,15 @@
     }
 
     /**
+     * Gets download ID.
+     *
+     * <p>This query is only supported in Tuner 2.0 or higher version. Unsupported version will
+     * return {@code -1}.
+     * Use {@link TunerVersionChecker#getTunerVersion()} to get the version information.
+     */
+    public int getDownloadId() { return mDownloadId; }
+
+    /**
      * Gets MPU sequence number of filtered data.
      */
     @IntRange(from = 0)
@@ -80,4 +91,3 @@
         return mDataLength;
     }
 }
-
diff --git a/media/java/android/media/tv/tuner/filter/DownloadSettings.java b/media/java/android/media/tv/tuner/filter/DownloadSettings.java
index 7ba923e..e2cfd7c 100644
--- a/media/java/android/media/tv/tuner/filter/DownloadSettings.java
+++ b/media/java/android/media/tv/tuner/filter/DownloadSettings.java
@@ -19,6 +19,7 @@
 import android.annotation.NonNull;
 import android.annotation.SystemApi;
 import android.media.tv.tuner.TunerUtils;
+import android.media.tv.tuner.TunerVersionChecker;
 
 /**
  * Filter Settings for a Download.
@@ -27,10 +28,12 @@
  */
 @SystemApi
 public class DownloadSettings extends Settings {
+    private final boolean mUseDownloadId;
     private final int mDownloadId;
 
-    private DownloadSettings(int mainType, int downloadId) {
+    private DownloadSettings(int mainType, boolean useDownloadId, int downloadId) {
         super(TunerUtils.getFilterSubtype(mainType, Filter.SUBTYPE_DOWNLOAD));
+        mUseDownloadId = useDownloadId;
         mDownloadId = downloadId;
     }
 
@@ -42,6 +45,15 @@
     }
 
     /**
+     * Gets whether download ID is used.
+     *
+     * <p>This query is only supported in Tuner 2.0 or higher version. Unsupported version will
+     * return {@code false}.
+     * Use {@link TunerVersionChecker#getTunerVersion()} to get the version information.
+     */
+    public boolean useDownloadId() { return mUseDownloadId; }
+
+    /**
      * Creates a builder for {@link DownloadSettings}.
      *
      * @param mainType the filter main type.
@@ -56,6 +68,7 @@
      */
     public static class Builder {
         private final int mMainType;
+        private boolean mUseDownloadId = false;
         private int mDownloadId;
 
         private Builder(int mainType) {
@@ -63,6 +76,24 @@
         }
 
         /**
+         * Sets whether download ID is used or not.
+         *
+         * <p>This configuration is only supported in Tuner 2.0 or higher version. Unsupported
+         * version will cause no-op. Use {@link TunerVersionChecker#getTunerVersion()} to get the
+         * version information.
+         *
+         * <p>Default value is {@code false}.
+         */
+        @NonNull
+        public Builder setUseDownloadId(boolean useDownloadId) {
+            if (TunerVersionChecker.checkHigherOrEqualVersionTo(
+                        TunerVersionChecker.TUNER_VERSION_2_0, "setUseDownloadId")) {
+                mUseDownloadId = useDownloadId;
+            }
+            return this;
+        }
+
+        /**
          * Sets download ID.
          */
         @NonNull
@@ -76,7 +107,7 @@
          */
         @NonNull
         public DownloadSettings build() {
-            return new DownloadSettings(mMainType, mDownloadId);
+            return new DownloadSettings(mMainType, mUseDownloadId, mDownloadId);
         }
     }
 }
diff --git a/media/java/android/media/tv/tuner/filter/MediaEvent.java b/media/java/android/media/tv/tuner/filter/MediaEvent.java
index dbd85e9..79d4062 100644
--- a/media/java/android/media/tv/tuner/filter/MediaEvent.java
+++ b/media/java/android/media/tv/tuner/filter/MediaEvent.java
@@ -40,6 +40,8 @@
     private final int mStreamId;
     private final boolean mIsPtsPresent;
     private final long mPts;
+    private final boolean mIsDtsPresent;
+    private final long mDts;
     private final long mDataLength;
     private final long mOffset;
     private LinearBlock mLinearBlock;
@@ -50,12 +52,14 @@
     private final AudioDescriptor mExtraMetaData;
 
     // This constructor is used by JNI code only
-    private MediaEvent(int streamId, boolean isPtsPresent, long pts, long dataLength, long offset,
-            LinearBlock buffer, boolean isSecureMemory, long dataId, int mpuSequenceNumber,
-            boolean isPrivateData, AudioDescriptor extraMetaData) {
+    private MediaEvent(int streamId, boolean isPtsPresent, long pts, boolean isDtsPresent, long dts,
+            long dataLength, long offset, LinearBlock buffer, boolean isSecureMemory, long dataId,
+            int mpuSequenceNumber, boolean isPrivateData, AudioDescriptor extraMetaData) {
         mStreamId = streamId;
         mIsPtsPresent = isPtsPresent;
         mPts = pts;
+        mIsDtsPresent = isDtsPresent;
+        mDts = dts;
         mDataLength = dataLength;
         mOffset = offset;
         mLinearBlock = buffer;
@@ -90,6 +94,26 @@
     }
 
     /**
+     * Returns whether DTS (Decode Time Stamp) is present.
+     *
+     * <p>This query is only supported in Tuner 2.0 or higher version. Unsupported version will
+     * return {@code false}.
+     * Use {@link TunerVersionChecker#getTunerVersion()} to get the version information.
+     *
+     * @return {@code true} if DTS is present in PES header; {@code false} otherwise.
+     */
+    public boolean isDtsPresent() { return mIsDtsPresent; }
+
+    /**
+     * Gets DTS (Decode Time Stamp) for audio or video frame.
+     *
+     * * <p>This query is only supported in Tuner 2.0 or higher version. Unsupported version will
+     * return {@code -1}.
+     * Use {@link TunerVersionChecker#getTunerVersion()} to get the version information.
+     */
+    public long getDts() { return mDts; }
+
+    /**
      * Gets data size in bytes of audio or video frame.
      */
     @BytesLong
diff --git a/media/java/android/media/tv/tuner/filter/SectionEvent.java b/media/java/android/media/tv/tuner/filter/SectionEvent.java
index ff12492..182bb94 100644
--- a/media/java/android/media/tv/tuner/filter/SectionEvent.java
+++ b/media/java/android/media/tv/tuner/filter/SectionEvent.java
@@ -28,10 +28,10 @@
     private final int mTableId;
     private final int mVersion;
     private final int mSectionNum;
-    private final int mDataLength;
+    private final long mDataLength;
 
     // This constructor is used by JNI code only
-    private SectionEvent(int tableId, int version, int sectionNum, int dataLength) {
+    private SectionEvent(int tableId, int version, int sectionNum, long dataLength) {
         mTableId = tableId;
         mVersion = version;
         mSectionNum = sectionNum;
@@ -61,8 +61,13 @@
 
     /**
      * Gets data size in bytes of filtered data.
+     *
+     * @deprecated Use {@link #getDataLengthLong()}
      */
+    @Deprecated
     public int getDataLength() {
-        return mDataLength;
+        return (int) getDataLengthLong();
     }
+
+    public long getDataLengthLong() { return mDataLength; }
 }
diff --git a/media/java/android/media/tv/tuner/frontend/FrontendStatus.java b/media/java/android/media/tv/tuner/frontend/FrontendStatus.java
index 31f1a63..582e4f5 100644
--- a/media/java/android/media/tv/tuner/frontend/FrontendStatus.java
+++ b/media/java/android/media/tv/tuner/frontend/FrontendStatus.java
@@ -19,10 +19,10 @@
 import android.annotation.IntDef;
 import android.annotation.IntRange;
 import android.annotation.NonNull;
+import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
 import android.media.tv.tuner.Lnb;
 import android.media.tv.tuner.TunerVersionChecker;
-
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 
@@ -53,7 +53,7 @@
             FRONTEND_STATUS_TYPE_MODULATIONS_EXT, FRONTEND_STATUS_TYPE_ROLL_OFF,
             FRONTEND_STATUS_TYPE_IS_MISO_ENABLED, FRONTEND_STATUS_TYPE_IS_LINEAR,
             FRONTEND_STATUS_TYPE_IS_SHORT_FRAMES_ENABLED, FRONTEND_STATUS_TYPE_ISDBT_MODE,
-            FRONTEND_STATUS_TYPE_ISDBT_PARTIAL_RECEPTION_FLAG})
+            FRONTEND_STATUS_TYPE_ISDBT_PARTIAL_RECEPTION_FLAG, FRONTEND_STATUS_TYPE_STREAM_ID_LIST})
     @Retention(RetentionPolicy.SOURCE)
     public @interface FrontendStatusType {}
 
@@ -254,6 +254,12 @@
     public static final int FRONTEND_STATUS_TYPE_ISDBT_PARTIAL_RECEPTION_FLAG =
             android.hardware.tv.tuner.FrontendStatusType.ISDBT_PARTIAL_RECEPTION_FLAG;
 
+    /**
+     * Stream ID list included in a transponder.
+     */
+    public static final int FRONTEND_STATUS_TYPE_STREAM_ID_LIST =
+            android.hardware.tv.tuner.FrontendStatusType.STREAM_ID_LIST;
+
     /** @hide */
     @IntDef(value = {
             AtscFrontendSettings.MODULATION_UNDEFINED,
@@ -493,6 +499,7 @@
     private Boolean mIsShortFrames;
     private Integer mIsdbtMode;
     private Integer mIsdbtPartialReceptionFlag;
+    private int[] mStreamIds;
 
     // Constructed and fields set by JNI code.
     private FrontendStatus() {
@@ -1001,6 +1008,24 @@
     }
 
     /**
+     * Gets stream id list included in a transponder.
+     *
+     * <p>This query is only supported by Tuner HAL 2.0 or higher. Unsupported version or if HAL
+     * doesn't return stream id list status will throw IllegalStateException. Use
+     * {@link TunerVersionChecker#getTunerVersion()} to check the version.
+     */
+    @SuppressLint("ArrayReturn")
+    @NonNull
+    public int[] getStreamIdList() {
+        TunerVersionChecker.checkHigherOrEqualVersionTo(
+                TunerVersionChecker.TUNER_VERSION_2_0, "stream id list status");
+        if (mStreamIds == null) {
+            throw new IllegalStateException("stream id list status is empty");
+        }
+        return mStreamIds;
+    }
+
+    /**
      * Information of each tuning Physical Layer Pipes.
      */
     public static class Atsc3PlpTuningInfo {
diff --git a/media/jni/android_media_tv_Tuner.cpp b/media/jni/android_media_tv_Tuner.cpp
index ded9652..e91e238 100644
--- a/media/jni/android_media_tv_Tuner.cpp
+++ b/media/jni/android_media_tv_Tuner.cpp
@@ -588,13 +588,13 @@
                                                const DemuxFilterEvent &event) {
     JNIEnv *env = AndroidRuntime::getJNIEnv();
     jclass eventClazz = env->FindClass("android/media/tv/tuner/filter/SectionEvent");
-    jmethodID eventInit = env->GetMethodID(eventClazz, "<init>", "(IIII)V");
+    jmethodID eventInit = env->GetMethodID(eventClazz, "<init>", "(IIIJ)V");
 
     const DemuxFilterSectionEvent &sectionEvent = event.get<DemuxFilterEvent::Tag::section>();
     jint tableId = sectionEvent.tableId;
     jint version = sectionEvent.version;
     jint sectionNum = sectionEvent.sectionNum;
-    jint dataLength = sectionEvent.dataLength;
+    jlong dataLength = sectionEvent.dataLength;
 
     jobject obj = env->NewObject(eventClazz, eventInit, tableId, version, sectionNum, dataLength);
     env->SetObjectArrayElement(arr, size, obj);
@@ -604,10 +604,9 @@
                                              const DemuxFilterEvent &event) {
     JNIEnv *env = AndroidRuntime::getJNIEnv();
     jclass eventClazz = env->FindClass("android/media/tv/tuner/filter/MediaEvent");
-    jmethodID eventInit = env->GetMethodID(eventClazz,
-            "<init>",
-            "(IZJJJLandroid/media/MediaCodec$LinearBlock;"
-            "ZJIZLandroid/media/tv/tuner/filter/AudioDescriptor;)V");
+    jmethodID eventInit = env->GetMethodID(eventClazz, "<init>",
+                                           "(IZJZJJJLandroid/media/MediaCodec$LinearBlock;"
+                                           "ZJIZLandroid/media/tv/tuner/filter/AudioDescriptor;)V");
     jfieldID eventContext = env->GetFieldID(eventClazz, "mNativeContext", "J");
 
     const DemuxFilterMediaEvent &mediaEvent = event.get<DemuxFilterEvent::Tag::media>();
@@ -633,15 +632,17 @@
     jint streamId = mediaEvent.streamId;
     jboolean isPtsPresent = mediaEvent.isPtsPresent;
     jlong pts = mediaEvent.pts;
+    jboolean isDtsPresent = mediaEvent.isDtsPresent;
+    jlong dts = mediaEvent.dts;
     jlong offset = mediaEvent.offset;
     jboolean isSecureMemory = mediaEvent.isSecureMemory;
     jlong avDataId = mediaEvent.avDataId;
     jint mpuSequenceNumber = mediaEvent.mpuSequenceNumber;
     jboolean isPesPrivateData = mediaEvent.isPesPrivateData;
 
-    jobject obj = env->NewObject(eventClazz, eventInit, streamId, isPtsPresent, pts, dataLength,
-                                 offset, nullptr, isSecureMemory, avDataId, mpuSequenceNumber,
-                                 isPesPrivateData, audioDescriptor);
+    jobject obj = env->NewObject(eventClazz, eventInit, streamId, isPtsPresent, pts, isDtsPresent,
+                                 dts, dataLength, offset, nullptr, isSecureMemory, avDataId,
+                                 mpuSequenceNumber, isPesPrivateData, audioDescriptor);
 
     uint64_t avSharedMemSize = mFilterClient->getAvSharedHandleInfo().size;
     if (mediaEvent.avMemory.fds.size() > 0 || mediaEvent.avDataId != 0 ||
@@ -733,16 +734,17 @@
                                                 const DemuxFilterEvent &event) {
     JNIEnv *env = AndroidRuntime::getJNIEnv();
     jclass eventClazz = env->FindClass("android/media/tv/tuner/filter/DownloadEvent");
-    jmethodID eventInit = env->GetMethodID(eventClazz, "<init>", "(IIIII)V");
+    jmethodID eventInit = env->GetMethodID(eventClazz, "<init>", "(IIIIII)V");
 
     const DemuxFilterDownloadEvent &downloadEvent = event.get<DemuxFilterEvent::Tag::download>();
     jint itemId = downloadEvent.itemId;
+    jint downloadId = downloadEvent.downloadId;
     jint mpuSequenceNumber = downloadEvent.mpuSequenceNumber;
     jint itemFragmentIndex = downloadEvent.itemFragmentIndex;
     jint lastItemFragmentIndex = downloadEvent.lastItemFragmentIndex;
     jint dataLength = downloadEvent.dataLength;
 
-    jobject obj = env->NewObject(eventClazz, eventInit, itemId, mpuSequenceNumber,
+    jobject obj = env->NewObject(eventClazz, eventInit, itemId, downloadId, mpuSequenceNumber,
                                  itemFragmentIndex, lastItemFragmentIndex, dataLength);
     env->SetObjectArrayElement(arr, size, obj);
 }
@@ -951,20 +953,45 @@
 }
 
 /////////////// FrontendClientCallbackImpl ///////////////////////
-FrontendClientCallbackImpl::FrontendClientCallbackImpl(jweak tunerObj) : mObject(tunerObj) {}
+FrontendClientCallbackImpl::FrontendClientCallbackImpl(JTuner* jtuner, jweak listener) {
+    ALOGV("FrontendClientCallbackImpl() with listener:%p", listener);
+    addCallbackListener(jtuner, listener);
+}
+
+void FrontendClientCallbackImpl::addCallbackListener(JTuner* jtuner, jweak listener) {
+    JNIEnv *env = AndroidRuntime::getJNIEnv();
+    jweak listenerRef = env->NewWeakGlobalRef(listener);
+    ALOGV("addCallbackListener() with listener:%p and ref:%p @%p", listener, listenerRef, this);
+    std::scoped_lock<std::mutex> lock(mMutex);
+    mListenersMap[jtuner] = listenerRef;
+}
+
+void FrontendClientCallbackImpl::removeCallbackListener(JTuner* listener) {
+    ALOGV("removeCallbackListener for listener:%p", listener);
+    JNIEnv *env = AndroidRuntime::getJNIEnv();
+    std::scoped_lock<std::mutex> lock(mMutex);
+    if (mListenersMap.find(listener) != mListenersMap.end() && mListenersMap[listener]) {
+        env->DeleteWeakGlobalRef(mListenersMap[listener]);
+        mListenersMap.erase(listener);
+    }
+}
 
 void FrontendClientCallbackImpl::onEvent(FrontendEventType frontendEventType) {
     ALOGV("FrontendClientCallbackImpl::onEvent, type=%d", frontendEventType);
     JNIEnv *env = AndroidRuntime::getJNIEnv();
-    jobject frontend(env->NewLocalRef(mObject));
-    if (!env->IsSameObject(frontend, nullptr)) {
-        env->CallVoidMethod(
-                frontend,
-                gFields.onFrontendEventID,
-                (jint)frontendEventType);
-    } else {
-        ALOGE("FrontendClientCallbackImpl::onEvent:"
-                "Frontend object has been freed. Ignoring callback.");
+    std::scoped_lock<std::mutex> lock(mMutex);
+    for (const auto& mapEntry : mListenersMap) {
+        ALOGV("JTuner:%p, jweak:%p", mapEntry.first, mapEntry.second);
+        jobject frontend(env->NewLocalRef(mapEntry.second));
+        if (!env->IsSameObject(frontend, nullptr)) {
+            env->CallVoidMethod(
+                    frontend,
+                    gFields.onFrontendEventID,
+                    (jint)frontendEventType);
+        } else {
+            ALOGW("FrontendClientCallbackImpl::onEvent:"
+                    "Frontend object has been freed. Ignoring callback.");
+        }
     }
 }
 
@@ -973,12 +1000,25 @@
     ALOGV("FrontendClientCallbackImpl::onScanMessage, type=%d", type);
     JNIEnv *env = AndroidRuntime::getJNIEnv();
     jclass clazz = env->FindClass("android/media/tv/tuner/Tuner");
-    jobject frontend(env->NewLocalRef(mObject));
-    if (env->IsSameObject(frontend, nullptr)) {
-        ALOGE("FrontendClientCallbackImpl::onScanMessage:"
-                "Frontend object has been freed. Ignoring callback.");
-        return;
+
+    std::scoped_lock<std::mutex> lock(mMutex);
+    for (const auto& mapEntry : mListenersMap) {
+        jobject frontend(env->NewLocalRef(mapEntry.second));
+        if (env->IsSameObject(frontend, nullptr)) {
+            ALOGE("FrontendClientCallbackImpl::onScanMessage:"
+                    "Tuner object has been freed. Ignoring callback.");
+            continue;
+        }
+        executeOnScanMessage(env, clazz, frontend, type, message);
     }
+}
+
+void FrontendClientCallbackImpl::executeOnScanMessage(
+         JNIEnv *env, const jclass& clazz, const jobject& frontend,
+         FrontendScanMessageType type,
+         const FrontendScanMessage& message) {
+    ALOGV("FrontendClientCallbackImpl::executeOnScanMessage, type=%d", type);
+
     switch(type) {
         case FrontendScanMessageType::LOCKED: {
             if (message.get<FrontendScanMessage::Tag::isLocked>()) {
@@ -1157,11 +1197,14 @@
 }
 
 FrontendClientCallbackImpl::~FrontendClientCallbackImpl() {
-    JNIEnv *env = AndroidRuntime::getJNIEnv();
-    if (mObject != nullptr) {
-        env->DeleteWeakGlobalRef(mObject);
-        mObject = nullptr;
+    JNIEnv *env = android::AndroidRuntime::getJNIEnv();
+    ALOGV("~FrontendClientCallbackImpl()");
+    std::scoped_lock<std::mutex> lock(mMutex);
+    for (const auto& mapEntry : mListenersMap) {
+        ALOGV("deleteRef :%p at @ %p", mapEntry.second, this);
+        env->DeleteWeakGlobalRef(mapEntry.second);
     }
+    mListenersMap.clear();
 }
 
 /////////////// Tuner ///////////////////////
@@ -1180,6 +1223,10 @@
     mSharedFeId = (int)Constant::INVALID_FRONTEND_ID;
 }
 
+jweak JTuner::getObject() {
+    return mObject;
+}
+
 JTuner::~JTuner() {
     if (mFeClient != nullptr) {
         mFeClient->close();
@@ -1192,6 +1239,7 @@
     env->DeleteWeakGlobalRef(mObject);
     env->DeleteGlobalRef(mClass);
     mFeClient = nullptr;
+    mFeClientCb = nullptr;
     mDemuxClient = nullptr;
     mClass = nullptr;
     mObject = nullptr;
@@ -1247,9 +1295,8 @@
         return nullptr;
     }
 
-    sp<FrontendClientCallbackImpl> feClientCb =
-            new FrontendClientCallbackImpl(env->NewWeakGlobalRef(mObject));
-    mFeClient->setCallback(feClientCb);
+    mFeClientCb = new FrontendClientCallbackImpl(this, mObject);
+    mFeClient->setCallback(mFeClientCb);
     // TODO: add more fields to frontend
     return env->NewObject(
             env->FindClass("android/media/tv/tuner/Tuner$Frontend"),
@@ -1269,6 +1316,18 @@
     return (int)Result::SUCCESS;
 }
 
+void JTuner::registerFeCbListener(JTuner* jtuner) {
+    if (mFeClientCb != nullptr && jtuner != nullptr) {
+        mFeClientCb->addCallbackListener(jtuner, jtuner->getObject());
+    }
+}
+
+void JTuner::unregisterFeCbListener(JTuner* jtuner) {
+    if (mFeClientCb != nullptr && jtuner != nullptr) {
+        mFeClientCb->removeCallbackListener(jtuner);
+    }
+}
+
 jobject JTuner::getAnalogFrontendCaps(JNIEnv *env, FrontendCapabilities &caps) {
     jclass clazz = env->FindClass("android/media/tv/tuner/frontend/AnalogFrontendCapabilities");
     jmethodID capsInit = env->GetMethodID(clazz, "<init>", "(II)V");
@@ -2450,7 +2509,14 @@
                 env->SetObjectField(statusObj, field, newIntegerObj);
                 break;
             }
-            default: {
+            case FrontendStatus::Tag::streamIdList: {
+                jfieldID field = env->GetFieldID(clazz, "mStreamIds", "[I");
+                std::vector<int32_t> ids = s.get<FrontendStatus::Tag::streamIdList>();
+
+                jintArray valObj = env->NewIntArray(v.size());
+                env->SetIntArrayRegion(valObj, 0, v.size(), reinterpret_cast<jint *>(&ids[0]));
+
+                env->SetObjectField(statusObj, field, valObj);
                 break;
             }
         }
@@ -3188,6 +3254,20 @@
     return tuner->shareFrontend(id);
 }
 
+static void android_media_tv_Tuner_register_fe_cb_listener(
+        JNIEnv *env, jobject thiz, jlong shareeJTuner) {
+    sp<JTuner> tuner = getTuner(env, thiz);
+    JTuner *jtuner = (JTuner *)shareeJTuner;
+    tuner->registerFeCbListener(jtuner);
+}
+
+static void android_media_tv_Tuner_unregister_fe_cb_listener(
+        JNIEnv *env, jobject thiz, jlong shareeJTuner) {
+    sp<JTuner> tuner = getTuner(env, thiz);
+    JTuner *jtuner = (JTuner *)shareeJTuner;
+    tuner->unregisterFeCbListener(jtuner);
+}
+
 static int android_media_tv_Tuner_tune(JNIEnv *env, jobject thiz, jint type, jobject settings) {
     sp<JTuner> tuner = getTuner(env, thiz);
     FrontendSettings setting = getFrontendSettings(env, type, settings);
@@ -3469,10 +3549,13 @@
 
 static DemuxFilterDownloadSettings getFilterDownloadSettings(JNIEnv *env, const jobject& settings) {
     jclass clazz = env->FindClass("android/media/tv/tuner/filter/DownloadSettings");
+    bool useDownloadId =
+            env->GetBooleanField(settings, env->GetFieldID(clazz, "mUseDownloadId", "Z"));
     int32_t downloadId = env->GetIntField(settings, env->GetFieldID(clazz, "mDownloadId", "I"));
 
-    DemuxFilterDownloadSettings filterDownloadSettings {
-        .downloadId = downloadId,
+    DemuxFilterDownloadSettings filterDownloadSettings{
+            .useDownloadId = useDownloadId,
+            .downloadId = downloadId,
     };
     return filterDownloadSettings;
 }
@@ -4253,6 +4336,17 @@
     return (jlong)dvrClient->readFromFile(size);
 }
 
+static jlong android_media_tv_Tuner_seek_dvr(JNIEnv *env, jobject dvr, jlong pos) {
+    sp<DvrClient> dvrClient = getDvrClient(env, dvr);
+    if (dvrClient == nullptr) {
+        jniThrowException(env, "java/lang/IllegalStateException",
+                          "Failed to seek dvr: dvr client not found");
+        return -1;
+    }
+
+    return (jlong)dvrClient->seekFile(pos);
+}
+
 static jlong android_media_tv_Tuner_read_dvr_from_array(
         JNIEnv* env, jobject dvr, jbyteArray buffer, jlong offset, jlong size) {
     sp<DvrClient> dvrClient = getDvrClient(env, dvr);
@@ -4361,6 +4455,10 @@
             (void *)android_media_tv_Tuner_open_frontend_by_handle },
     { "nativeShareFrontend", "(I)I",
             (void *)android_media_tv_Tuner_share_frontend },
+    { "nativeRegisterFeCbListener", "(J)V",
+            (void*)android_media_tv_Tuner_register_fe_cb_listener },
+    { "nativeUnregisterFeCbListener", "(J)V",
+            (void*)android_media_tv_Tuner_unregister_fe_cb_listener },
     { "nativeTune", "(ILandroid/media/tv/tuner/frontend/FrontendSettings;)I",
             (void *)android_media_tv_Tuner_tune },
     { "nativeStopTune", "()I", (void *)android_media_tv_Tuner_stop_tune },
@@ -4403,38 +4501,37 @@
     { "nativeClose", "()I", (void *)android_media_tv_Tuner_close_tuner },
     { "nativeCloseFrontend", "(I)I", (void *)android_media_tv_Tuner_close_frontend },
     { "nativeCloseDemux", "(I)I", (void *)android_media_tv_Tuner_close_demux },
-    {"nativeOpenSharedFilter",
+    { "nativeOpenSharedFilter",
             "(Ljava/lang/String;)Landroid/media/tv/tuner/filter/SharedFilter;",
             (void *)android_media_tv_Tuner_open_shared_filter},
 };
 
 static const JNINativeMethod gFilterMethods[] = {
     { "nativeConfigureFilter", "(IILandroid/media/tv/tuner/filter/FilterConfiguration;)I",
-            (void *)android_media_tv_Tuner_configure_filter },
-    { "nativeGetId", "()I", (void *)android_media_tv_Tuner_get_filter_id },
-    { "nativeGetId64Bit", "()J",
-            (void *)android_media_tv_Tuner_get_filter_64bit_id },
+            (void *)android_media_tv_Tuner_configure_filter},
+    { "nativeGetId", "()I", (void *)android_media_tv_Tuner_get_filter_id},
+    { "nativeGetId64Bit", "()J", (void *)android_media_tv_Tuner_get_filter_64bit_id},
     { "nativeConfigureMonitorEvent", "(I)I",
-            (void *)android_media_tv_Tuner_configure_monitor_event },
+            (void *)android_media_tv_Tuner_configure_monitor_event},
     { "nativeSetDataSource", "(Landroid/media/tv/tuner/filter/Filter;)I",
-            (void *)android_media_tv_Tuner_set_filter_data_source },
-    { "nativeStartFilter", "()I", (void *)android_media_tv_Tuner_start_filter },
-    { "nativeStopFilter", "()I", (void *)android_media_tv_Tuner_stop_filter },
-    { "nativeFlushFilter", "()I", (void *)android_media_tv_Tuner_flush_filter },
-    { "nativeRead", "([BJJ)I", (void *)android_media_tv_Tuner_read_filter_fmq },
-    { "nativeClose", "()I", (void *)android_media_tv_Tuner_close_filter },
-    {"nativeAcquireSharedFilterToken", "()Ljava/lang/String;",
+            (void *)android_media_tv_Tuner_set_filter_data_source},
+    { "nativeStartFilter", "()I", (void *)android_media_tv_Tuner_start_filter},
+    { "nativeStopFilter", "()I", (void *)android_media_tv_Tuner_stop_filter},
+    { "nativeFlushFilter", "()I", (void *)android_media_tv_Tuner_flush_filter},
+    { "nativeRead", "([BJJ)I", (void *)android_media_tv_Tuner_read_filter_fmq},
+    { "nativeClose", "()I", (void *)android_media_tv_Tuner_close_filter},
+    { "nativeAcquireSharedFilterToken", "()Ljava/lang/String;",
             (void *)android_media_tv_Tuner_acquire_shared_filter_token},
-    {"nativeFreeSharedFilterToken", "(Ljava/lang/String;)V",
+    { "nativeFreeSharedFilterToken", "(Ljava/lang/String;)V",
             (void *)android_media_tv_Tuner_free_shared_filter_token},
 };
 
 static const JNINativeMethod gSharedFilterMethods[] = {
-    {"nativeStartSharedFilter", "()I", (void *)android_media_tv_Tuner_start_filter},
-    {"nativeStopSharedFilter", "()I", (void *)android_media_tv_Tuner_stop_filter},
-    {"nativeFlushSharedFilter", "()I", (void *)android_media_tv_Tuner_flush_filter},
-    {"nativeSharedRead", "([BJJ)I", (void *)android_media_tv_Tuner_read_filter_fmq},
-    {"nativeSharedClose", "()I", (void *)android_media_tv_Tuner_close_filter},
+    { "nativeStartSharedFilter", "()I", (void *)android_media_tv_Tuner_start_filter},
+    { "nativeStopSharedFilter", "()I", (void *)android_media_tv_Tuner_stop_filter},
+    { "nativeFlushSharedFilter", "()I", (void *)android_media_tv_Tuner_flush_filter},
+    { "nativeSharedRead", "([BJJ)I", (void *)android_media_tv_Tuner_read_filter_fmq},
+    { "nativeSharedClose", "()I", (void *)android_media_tv_Tuner_close_filter},
 };
 
 static const JNINativeMethod gTimeFilterMethods[] = {
@@ -4474,18 +4571,19 @@
 
 static const JNINativeMethod gDvrPlaybackMethods[] = {
     { "nativeAttachFilter", "(Landroid/media/tv/tuner/filter/Filter;)I",
-            (void *)android_media_tv_Tuner_attach_filter },
+            (void *)android_media_tv_Tuner_attach_filter},
     { "nativeDetachFilter", "(Landroid/media/tv/tuner/filter/Filter;)I",
-            (void *)android_media_tv_Tuner_detach_filter },
+            (void *)android_media_tv_Tuner_detach_filter},
     { "nativeConfigureDvr", "(Landroid/media/tv/tuner/dvr/DvrSettings;)I",
-            (void *)android_media_tv_Tuner_configure_dvr },
-    { "nativeStartDvr", "()I", (void *)android_media_tv_Tuner_start_dvr },
-    { "nativeStopDvr", "()I", (void *)android_media_tv_Tuner_stop_dvr },
-    { "nativeFlushDvr", "()I", (void *)android_media_tv_Tuner_flush_dvr },
-    { "nativeClose", "()I", (void *)android_media_tv_Tuner_close_dvr },
-    { "nativeSetFileDescriptor", "(I)V", (void *)android_media_tv_Tuner_dvr_set_fd },
-    { "nativeRead", "(J)J", (void *)android_media_tv_Tuner_read_dvr },
-    { "nativeRead", "([BJJ)J", (void *)android_media_tv_Tuner_read_dvr_from_array },
+            (void *)android_media_tv_Tuner_configure_dvr},
+    { "nativeStartDvr", "()I", (void *)android_media_tv_Tuner_start_dvr},
+    { "nativeStopDvr", "()I", (void *)android_media_tv_Tuner_stop_dvr},
+    { "nativeFlushDvr", "()I", (void *)android_media_tv_Tuner_flush_dvr},
+    { "nativeClose", "()I", (void *)android_media_tv_Tuner_close_dvr},
+    { "nativeSetFileDescriptor", "(I)V", (void *)android_media_tv_Tuner_dvr_set_fd},
+    { "nativeRead", "(J)J", (void *)android_media_tv_Tuner_read_dvr},
+    { "nativeRead", "([BJJ)J", (void *)android_media_tv_Tuner_read_dvr_from_array},
+    { "nativeSeek", "(J)J", (void *)android_media_tv_Tuner_seek_dvr},
 };
 
 static const JNINativeMethod gLnbMethods[] = {
diff --git a/media/jni/android_media_tv_Tuner.h b/media/jni/android_media_tv_Tuner.h
index 31d24ee..06e2492 100644
--- a/media/jni/android_media_tv_Tuner.h
+++ b/media/jni/android_media_tv_Tuner.h
@@ -149,14 +149,21 @@
     void getRestartEvent(jobjectArray& arr, const int size, const DemuxFilterEvent& event);
 };
 
+struct JTuner;
 struct FrontendClientCallbackImpl : public FrontendClientCallback {
-    FrontendClientCallbackImpl(jweak tunerObj);
+    FrontendClientCallbackImpl(JTuner*, jweak);
     ~FrontendClientCallbackImpl();
     virtual void onEvent(FrontendEventType frontendEventType);
     virtual void onScanMessage(
             FrontendScanMessageType type, const FrontendScanMessage& message);
 
-    jweak mObject;
+    void executeOnScanMessage(JNIEnv *env, const jclass& clazz, const jobject& frontend,
+                              FrontendScanMessageType type,
+                              const FrontendScanMessage& message);
+    void addCallbackListener(JTuner*, jweak obj);
+    void removeCallbackListener(JTuner* jtuner);
+    std::unordered_map<JTuner*, jweak> mListenersMap;
+    std::mutex mMutex;
 };
 
 struct JTuner : public RefBase {
@@ -171,6 +178,8 @@
     jobject getFrontendIds();
     jobject openFrontendByHandle(int feHandle);
     int shareFrontend(int feId);
+    void registerFeCbListener(JTuner* jtuner);
+    void unregisterFeCbListener(JTuner* jtuner);
     jint closeFrontendById(int id);
     jobject getFrontendInfo(int id);
     int tune(const FrontendSettings& settings);
@@ -192,6 +201,8 @@
     jint closeFrontend();
     jint closeDemux();
 
+    jweak getObject();
+
 protected:
     virtual ~JTuner();
 
@@ -200,6 +211,7 @@
     jweak mObject;
     static sp<TunerClient> mTunerClient;
     sp<FrontendClient> mFeClient;
+    sp<FrontendClientCallbackImpl> mFeClientCb;
     int mFeId;
     int mSharedFeId;
     sp<LnbClient> mLnbClient;
diff --git a/media/jni/tuner/DvrClient.cpp b/media/jni/tuner/DvrClient.cpp
index 3027838..05683b6 100644
--- a/media/jni/tuner/DvrClient.cpp
+++ b/media/jni/tuner/DvrClient.cpp
@@ -22,6 +22,8 @@
 #include <aidl/android/hardware/tv/tuner/DemuxQueueNotifyBits.h>
 #include <android-base/logging.h>
 #include <inttypes.h>
+#include <sys/types.h>
+#include <unistd.h>
 #include <utils/Log.h>
 
 #include "ClientHelper.h"
@@ -200,6 +202,14 @@
     return size;
 }
 
+int64_t DvrClient::seekFile(int64_t pos) {
+    if (mFd < 0) {
+        ALOGE("Failed to seekFile. File is not configured");
+        return -1;
+    }
+    return lseek64(mFd, pos, SEEK_SET);
+}
+
 Result DvrClient::configure(DvrSettings settings) {
     if (mTunerDvr != nullptr) {
         Status s = mTunerDvr->configure(settings);
diff --git a/media/jni/tuner/DvrClient.h b/media/jni/tuner/DvrClient.h
index 9080c72..61c0325 100644
--- a/media/jni/tuner/DvrClient.h
+++ b/media/jni/tuner/DvrClient.h
@@ -82,6 +82,11 @@
     int64_t writeToFile(int64_t size);
 
     /**
+     * Seeks the Dvr file descriptor from the beginning of the file.
+     */
+    int64_t seekFile(int64_t pos);
+
+    /**
      * Write data to the given buffer with given size. Return the actual write size.
      */
     int64_t writeToBuffer(int8_t* buffer, int64_t size);
diff --git a/native/android/surface_control.cpp b/native/android/surface_control.cpp
index ceba4d6..f76811e 100644
--- a/native/android/surface_control.cpp
+++ b/native/android/surface_control.cpp
@@ -364,7 +364,7 @@
     sp<SurfaceControl> surfaceControl = ASurfaceControl_to_SurfaceControl(aSurfaceControl);
     Transaction* transaction = ASurfaceTransaction_to_Transaction(aSurfaceTransaction);
 
-    sp<GraphicBuffer> graphic_buffer(reinterpret_cast<GraphicBuffer*>(buffer));
+    sp<GraphicBuffer> graphic_buffer(GraphicBuffer::fromAHardwareBuffer(buffer));
 
     std::optional<sp<Fence>> fence = std::nullopt;
     if (acquire_fence_fd != -1) {
diff --git a/packages/ConnectivityT/OWNERS b/packages/ConnectivityT/OWNERS
index 4862377..e267d19 100644
--- a/packages/ConnectivityT/OWNERS
+++ b/packages/ConnectivityT/OWNERS
@@ -1 +1,2 @@
-file:platform/packages/modules/Connectivity:master:/OWNERS_core_networking
\ No newline at end of file
+file:platform/packages/modules/Connectivity:master:/OWNERS_core_networking
+per-file **IpSec* = file:/services/core/java/com/android/server/vcn/OWNERS
diff --git a/packages/ConnectivityT/framework-t/Android.bp b/packages/ConnectivityT/framework-t/Android.bp
index 3e82b28..931a55b 100644
--- a/packages/ConnectivityT/framework-t/Android.bp
+++ b/packages/ConnectivityT/framework-t/Android.bp
@@ -100,6 +100,20 @@
     ],
 }
 
+// IpSec related libraries.
+
+filegroup {
+    name: "framework-connectivity-ipsec-sources",
+    srcs: [
+        "src/android/net/IIpSecService.aidl",
+        "src/android/net/IpSec*.*",
+    ],
+    path: "src",
+    visibility: [
+        "//visibility:private",
+    ],
+}
+
 // Connectivity-T common libraries.
 
 filegroup {
@@ -116,11 +130,10 @@
 filegroup {
     name: "framework-connectivity-tiramisu-sources",
     srcs: [
+        ":framework-connectivity-ipsec-sources",
         ":framework-connectivity-netstats-sources",
         ":framework-connectivity-nsd-sources",
         ":framework-connectivity-tiramisu-internal-sources",
     ],
-    visibility: [
-        "//frameworks/base",
-    ],
+    visibility: ["//frameworks/base"],
 }
diff --git a/core/java/android/net/IIpSecService.aidl b/packages/ConnectivityT/framework-t/src/android/net/IIpSecService.aidl
similarity index 100%
rename from core/java/android/net/IIpSecService.aidl
rename to packages/ConnectivityT/framework-t/src/android/net/IIpSecService.aidl
diff --git a/core/java/android/net/IpSecAlgorithm.java b/packages/ConnectivityT/framework-t/src/android/net/IpSecAlgorithm.java
similarity index 100%
rename from core/java/android/net/IpSecAlgorithm.java
rename to packages/ConnectivityT/framework-t/src/android/net/IpSecAlgorithm.java
diff --git a/core/java/android/net/IpSecConfig.aidl b/packages/ConnectivityT/framework-t/src/android/net/IpSecConfig.aidl
similarity index 100%
rename from core/java/android/net/IpSecConfig.aidl
rename to packages/ConnectivityT/framework-t/src/android/net/IpSecConfig.aidl
diff --git a/core/java/android/net/IpSecConfig.java b/packages/ConnectivityT/framework-t/src/android/net/IpSecConfig.java
similarity index 100%
rename from core/java/android/net/IpSecConfig.java
rename to packages/ConnectivityT/framework-t/src/android/net/IpSecConfig.java
diff --git a/core/java/android/net/IpSecManager.java b/packages/ConnectivityT/framework-t/src/android/net/IpSecManager.java
similarity index 100%
rename from core/java/android/net/IpSecManager.java
rename to packages/ConnectivityT/framework-t/src/android/net/IpSecManager.java
diff --git a/core/java/android/net/IpSecSpiResponse.aidl b/packages/ConnectivityT/framework-t/src/android/net/IpSecSpiResponse.aidl
similarity index 100%
rename from core/java/android/net/IpSecSpiResponse.aidl
rename to packages/ConnectivityT/framework-t/src/android/net/IpSecSpiResponse.aidl
diff --git a/core/java/android/net/IpSecSpiResponse.java b/packages/ConnectivityT/framework-t/src/android/net/IpSecSpiResponse.java
similarity index 100%
rename from core/java/android/net/IpSecSpiResponse.java
rename to packages/ConnectivityT/framework-t/src/android/net/IpSecSpiResponse.java
diff --git a/core/java/android/net/IpSecTransform.java b/packages/ConnectivityT/framework-t/src/android/net/IpSecTransform.java
similarity index 100%
rename from core/java/android/net/IpSecTransform.java
rename to packages/ConnectivityT/framework-t/src/android/net/IpSecTransform.java
diff --git a/core/java/android/net/IpSecTransformResponse.aidl b/packages/ConnectivityT/framework-t/src/android/net/IpSecTransformResponse.aidl
similarity index 100%
rename from core/java/android/net/IpSecTransformResponse.aidl
rename to packages/ConnectivityT/framework-t/src/android/net/IpSecTransformResponse.aidl
diff --git a/core/java/android/net/IpSecTransformResponse.java b/packages/ConnectivityT/framework-t/src/android/net/IpSecTransformResponse.java
similarity index 100%
rename from core/java/android/net/IpSecTransformResponse.java
rename to packages/ConnectivityT/framework-t/src/android/net/IpSecTransformResponse.java
diff --git a/core/java/android/net/IpSecTunnelInterfaceResponse.aidl b/packages/ConnectivityT/framework-t/src/android/net/IpSecTunnelInterfaceResponse.aidl
similarity index 100%
rename from core/java/android/net/IpSecTunnelInterfaceResponse.aidl
rename to packages/ConnectivityT/framework-t/src/android/net/IpSecTunnelInterfaceResponse.aidl
diff --git a/core/java/android/net/IpSecTunnelInterfaceResponse.java b/packages/ConnectivityT/framework-t/src/android/net/IpSecTunnelInterfaceResponse.java
similarity index 100%
rename from core/java/android/net/IpSecTunnelInterfaceResponse.java
rename to packages/ConnectivityT/framework-t/src/android/net/IpSecTunnelInterfaceResponse.java
diff --git a/core/java/android/net/IpSecUdpEncapResponse.aidl b/packages/ConnectivityT/framework-t/src/android/net/IpSecUdpEncapResponse.aidl
similarity index 100%
rename from core/java/android/net/IpSecUdpEncapResponse.aidl
rename to packages/ConnectivityT/framework-t/src/android/net/IpSecUdpEncapResponse.aidl
diff --git a/core/java/android/net/IpSecUdpEncapResponse.java b/packages/ConnectivityT/framework-t/src/android/net/IpSecUdpEncapResponse.java
similarity index 100%
rename from core/java/android/net/IpSecUdpEncapResponse.java
rename to packages/ConnectivityT/framework-t/src/android/net/IpSecUdpEncapResponse.java
diff --git a/packages/ConnectivityT/framework-t/src/android/net/nsd/NsdManager.java b/packages/ConnectivityT/framework-t/src/android/net/nsd/NsdManager.java
index 6c597e2..0f21e55 100644
--- a/packages/ConnectivityT/framework-t/src/android/net/nsd/NsdManager.java
+++ b/packages/ConnectivityT/framework-t/src/android/net/nsd/NsdManager.java
@@ -16,10 +16,6 @@
 
 package android.net.nsd;
 
-import static com.android.internal.util.Preconditions.checkArgument;
-import static com.android.internal.util.Preconditions.checkNotNull;
-import static com.android.internal.util.Preconditions.checkStringNotEmpty;
-
 import android.annotation.SdkConstant;
 import android.annotation.SdkConstant.SdkConstantType;
 import android.annotation.SystemService;
@@ -32,11 +28,13 @@
 import android.os.Looper;
 import android.os.Message;
 import android.os.RemoteException;
+import android.text.TextUtils;
 import android.util.Log;
 import android.util.SparseArray;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.Protocol;
+
+import java.util.Objects;
 
 /**
  * The Network Service Discovery Manager class provides the API to discover services
@@ -175,65 +173,63 @@
      */
     public static final int NSD_STATE_ENABLED = 2;
 
-    private static final int BASE = Protocol.BASE_NSD_MANAGER;
+    /** @hide */
+    public static final int DISCOVER_SERVICES                       = 1;
+    /** @hide */
+    public static final int DISCOVER_SERVICES_STARTED               = 2;
+    /** @hide */
+    public static final int DISCOVER_SERVICES_FAILED                = 3;
+    /** @hide */
+    public static final int SERVICE_FOUND                           = 4;
+    /** @hide */
+    public static final int SERVICE_LOST                            = 5;
 
     /** @hide */
-    public static final int DISCOVER_SERVICES                       = BASE + 1;
+    public static final int STOP_DISCOVERY                          = 6;
     /** @hide */
-    public static final int DISCOVER_SERVICES_STARTED               = BASE + 2;
+    public static final int STOP_DISCOVERY_FAILED                   = 7;
     /** @hide */
-    public static final int DISCOVER_SERVICES_FAILED                = BASE + 3;
-    /** @hide */
-    public static final int SERVICE_FOUND                           = BASE + 4;
-    /** @hide */
-    public static final int SERVICE_LOST                            = BASE + 5;
+    public static final int STOP_DISCOVERY_SUCCEEDED                = 8;
 
     /** @hide */
-    public static final int STOP_DISCOVERY                          = BASE + 6;
+    public static final int REGISTER_SERVICE                        = 9;
     /** @hide */
-    public static final int STOP_DISCOVERY_FAILED                   = BASE + 7;
+    public static final int REGISTER_SERVICE_FAILED                 = 10;
     /** @hide */
-    public static final int STOP_DISCOVERY_SUCCEEDED                = BASE + 8;
+    public static final int REGISTER_SERVICE_SUCCEEDED              = 11;
 
     /** @hide */
-    public static final int REGISTER_SERVICE                        = BASE + 9;
+    public static final int UNREGISTER_SERVICE                      = 12;
     /** @hide */
-    public static final int REGISTER_SERVICE_FAILED                 = BASE + 10;
+    public static final int UNREGISTER_SERVICE_FAILED               = 13;
     /** @hide */
-    public static final int REGISTER_SERVICE_SUCCEEDED              = BASE + 11;
+    public static final int UNREGISTER_SERVICE_SUCCEEDED            = 14;
 
     /** @hide */
-    public static final int UNREGISTER_SERVICE                      = BASE + 12;
+    public static final int RESOLVE_SERVICE                         = 15;
     /** @hide */
-    public static final int UNREGISTER_SERVICE_FAILED               = BASE + 13;
+    public static final int RESOLVE_SERVICE_FAILED                  = 16;
     /** @hide */
-    public static final int UNREGISTER_SERVICE_SUCCEEDED            = BASE + 14;
+    public static final int RESOLVE_SERVICE_SUCCEEDED               = 17;
 
     /** @hide */
-    public static final int RESOLVE_SERVICE                         = BASE + 18;
-    /** @hide */
-    public static final int RESOLVE_SERVICE_FAILED                  = BASE + 19;
-    /** @hide */
-    public static final int RESOLVE_SERVICE_SUCCEEDED               = BASE + 20;
+    public static final int DAEMON_CLEANUP                          = 18;
 
     /** @hide */
-    public static final int DAEMON_CLEANUP                          = BASE + 21;
+    public static final int DAEMON_STARTUP                          = 19;
 
     /** @hide */
-    public static final int DAEMON_STARTUP                          = BASE + 22;
+    public static final int ENABLE                                  = 20;
+    /** @hide */
+    public static final int DISABLE                                 = 21;
 
     /** @hide */
-    public static final int ENABLE                                  = BASE + 24;
-    /** @hide */
-    public static final int DISABLE                                 = BASE + 25;
+    public static final int NATIVE_DAEMON_EVENT                     = 22;
 
     /** @hide */
-    public static final int NATIVE_DAEMON_EVENT                     = BASE + 26;
-
+    public static final int REGISTER_CLIENT                         = 23;
     /** @hide */
-    public static final int REGISTER_CLIENT                         = BASE + 27;
-    /** @hide */
-    public static final int UNREGISTER_CLIENT                       = BASE + 28;
+    public static final int UNREGISTER_CLIENT                       = 24;
 
     /** Dns based service discovery protocol */
     public static final int PROTOCOL_DNS_SD = 0x0001;
@@ -550,7 +546,9 @@
         final int key;
         synchronized (mMapLock) {
             int valueIndex = mListenerMap.indexOfValue(listener);
-            checkArgument(valueIndex == -1, "listener already in use");
+            if (valueIndex != -1) {
+                throw new IllegalArgumentException("listener already in use");
+            }
             key = nextListenerKey();
             mListenerMap.put(key, listener);
             mServiceMap.put(key, s);
@@ -569,7 +567,9 @@
         checkListener(listener);
         synchronized (mMapLock) {
             int valueIndex = mListenerMap.indexOfValue(listener);
-            checkArgument(valueIndex != -1, "listener not registered");
+            if (valueIndex == -1) {
+                throw new IllegalArgumentException("listener not registered");
+            }
             return mListenerMap.keyAt(valueIndex);
         }
     }
@@ -598,7 +598,9 @@
      */
     public void registerService(NsdServiceInfo serviceInfo, int protocolType,
             RegistrationListener listener) {
-        checkArgument(serviceInfo.getPort() > 0, "Invalid port number");
+        if (serviceInfo.getPort() <= 0) {
+            throw new IllegalArgumentException("Invalid port number");
+        }
         checkServiceInfo(serviceInfo);
         checkProtocol(protocolType);
         int key = putListener(listener, serviceInfo);
@@ -660,7 +662,9 @@
      * Cannot be null. Cannot be in use for an active service discovery.
      */
     public void discoverServices(String serviceType, int protocolType, DiscoveryListener listener) {
-        checkStringNotEmpty(serviceType, "Service type cannot be empty");
+        if (TextUtils.isEmpty(serviceType)) {
+            throw new IllegalArgumentException("Service type cannot be empty");
+        }
         checkProtocol(protocolType);
 
         NsdServiceInfo s = new NsdServiceInfo();
@@ -719,16 +723,22 @@
     }
 
     private static void checkListener(Object listener) {
-        checkNotNull(listener, "listener cannot be null");
+        Objects.requireNonNull(listener, "listener cannot be null");
     }
 
     private static void checkProtocol(int protocolType) {
-        checkArgument(protocolType == PROTOCOL_DNS_SD, "Unsupported protocol");
+        if (protocolType != PROTOCOL_DNS_SD) {
+            throw new IllegalArgumentException("Unsupported protocol");
+        }
     }
 
     private static void checkServiceInfo(NsdServiceInfo serviceInfo) {
-        checkNotNull(serviceInfo, "NsdServiceInfo cannot be null");
-        checkStringNotEmpty(serviceInfo.getServiceName(), "Service name cannot be empty");
-        checkStringNotEmpty(serviceInfo.getServiceType(), "Service type cannot be empty");
+        Objects.requireNonNull(serviceInfo, "NsdServiceInfo cannot be null");
+        if (TextUtils.isEmpty(serviceInfo.getServiceName())) {
+            throw new IllegalArgumentException("Service name cannot be empty");
+        }
+        if (TextUtils.isEmpty(serviceInfo.getServiceType())) {
+            throw new IllegalArgumentException("Service type cannot be empty");
+        }
     }
 }
diff --git a/packages/ConnectivityT/service/Android.bp b/packages/ConnectivityT/service/Android.bp
index 6a64910..7b88176 100644
--- a/packages/ConnectivityT/service/Android.bp
+++ b/packages/ConnectivityT/service/Android.bp
@@ -48,16 +48,28 @@
     ],
 }
 
+// IpSec related libraries.
+
+filegroup {
+    name: "services.connectivity-ipsec-sources",
+    srcs: [
+        "src/com/android/server/IpSecService.java",
+    ],
+    path: "src",
+    visibility: [
+        "//visibility:private",
+    ],
+}
+
 // Connectivity-T common libraries.
 
 filegroup {
     name: "services.connectivity-tiramisu-sources",
     srcs: [
+        ":services.connectivity-ipsec-sources",
         ":services.connectivity-netstats-sources",
         ":services.connectivity-nsd-sources",
     ],
     path: "src",
-    visibility: [
-        "//frameworks/base/services/core",
-    ],
-}
\ No newline at end of file
+    visibility: ["//frameworks/base/services/core"],
+}
diff --git a/services/core/java/com/android/server/IpSecService.java b/packages/ConnectivityT/service/src/com/android/server/IpSecService.java
similarity index 100%
rename from services/core/java/com/android/server/IpSecService.java
rename to packages/ConnectivityT/service/src/com/android/server/IpSecService.java
diff --git a/packages/SystemUI/res-keyguard/drawable/super_lock_icon.xml b/packages/SystemUI/res-keyguard/drawable/super_lock_icon.xml
index 67a70bb..b3987f1 100644
--- a/packages/SystemUI/res-keyguard/drawable/super_lock_icon.xml
+++ b/packages/SystemUI/res-keyguard/drawable/super_lock_icon.xml
@@ -94,4 +94,9 @@
         android:fromId="@id/unlocked"
         android:toId="@id/unlocked_aod"
         android:drawable="@drawable/unlocked_ls_to_aod" />
+
+    <transition
+        android:fromId="@id/unlocked"
+        android:toId="@id/locked_aod"
+        android:drawable="@drawable/unlocked_to_aod_lock" />
 </animated-selector>
diff --git a/packages/SystemUI/res-keyguard/drawable/unlocked_to_aod_lock.xml b/packages/SystemUI/res-keyguard/drawable/unlocked_to_aod_lock.xml
new file mode 100644
index 0000000..b6d76e0
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/drawable/unlocked_to_aod_lock.xml
@@ -0,0 +1,169 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2021 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.
+-->
+<animated-vector xmlns:aapt="http://schemas.android.com/aapt" xmlns:android="http://schemas.android.com/apk/res/android">
+    <aapt:attr name="android:drawable">
+        <vector android:height="65dp" android:width="46dp" android:viewportHeight="65" android:viewportWidth="46">
+            <group android:name="_R_G">
+                <group android:name="_R_G_L_2_G_T_1" android:translateX="22.75" android:translateY="22.25" android:scaleX="1.02" android:scaleY="1.02">
+                    <group android:name="_R_G_L_2_G" android:translateX="-8.75" android:translateY="-8.75">
+                        <path android:name="_R_G_L_2_G_D_0_P_0" android:strokeColor="#FF000000"
+                              android:strokeLineJoin="round"
+                              android:strokeWidth="2.5"
+                              android:strokeAlpha="1"
+                              android:pathData=" M27.19 14.81 C27.19,14.81 27.19,8.3 27.19,8.3 C27.19,4.92 24.44,2.88 21.19,2.75 C17.74,2.62 15,4.74 15,8.11 C15,8.11 15,15 15,15 " />
+                    </group>
+                </group>
+                <group android:name="_R_G_L_1_G" android:translateX="23" android:translateY="32.125">
+                    <path android:name="_R_G_L_1_G_D_0_P_0"
+                          android:strokeColor="#FF000000"
+                          android:strokeLineJoin="round"
+                          android:strokeWidth="2"
+                          android:strokeAlpha="1"
+                          android:pathData=" M11.25 -0.64 C11.25,-0.64 11.25,13.64 11.25,13.64 C11.25,15.22 9.97,16.5 8.39,16.5 C8.39,16.5 -8.39,16.5 -8.39,16.5 C-9.97,16.5 -11.25,15.22 -11.25,13.64 C-11.25,13.64 -11.25,-0.64 -11.25,-0.64 C-11.25,-2.22 -9.97,-3.5 -8.39,-3.5 C-8.39,-3.5 8.39,-3.5 8.39,-3.5 C9.97,-3.5 11.25,-2.22 11.25,-0.64c " />
+                </group>
+                <group android:name="_R_G_L_0_G" android:translateX="23" android:translateY="32.125">
+                    <path android:name="_R_G_L_0_G_D_0_P_0"
+                          android:fillColor="#FF000000"
+                          android:fillAlpha="1"
+                          android:fillType="nonZero"
+                          android:pathData=" M-0.09 8.63 C1.2,8.63 2.25,7.57 2.25,6.28 C2.25,4.99 1.2,3.94 -0.09,3.94 C-1.39,3.94 -2.44,4.99 -2.44,6.28 C-2.44,7.57 -1.39,8.63 -0.09,8.63c " />
+                </group>
+            </group>
+            <group android:name="time_group" />
+        </vector>
+    </aapt:attr>
+    <target android:name="_R_G_L_2_G_D_0_P_0">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator android:propertyName="strokeWidth"
+                                android:duration="333" android:startOffset="0" android:valueFrom="2.5" android:valueTo="2" android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.2,0 0.833,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="_R_G_L_2_G_D_0_P_0">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator android:propertyName="pathData"
+                                android:duration="83" android:startOffset="0" android:valueFrom="M27.19 14.81 C27.19,14.81 27.19,8.3 27.19,8.3 C27.19,4.92 24.44,2.88 21.19,2.75 C17.74,2.62 15,4.74 15,8.11 C15,8.11 15,15 15,15 " android:valueTo="M27.13 10.19 C27.13,10.19 27.13,3.67 27.13,3.67 C27.13,0.3 24.38,-1.75 21.13,-1.87 C17.68,-2.01 14.94,0.11 14.94,3.49 C14.94,3.49 15,15 15,15 " android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.456,0 0.464,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator android:propertyName="pathData"
+                                android:duration="133" android:startOffset="83" android:valueFrom="M27.13 10.19 C27.13,10.19 27.13,3.67 27.13,3.67 C27.13,0.3 24.38,-1.75 21.13,-1.87 C17.68,-2.01 14.94,0.11 14.94,3.49 C14.94,3.49 15,15 15,15 " android:valueTo="M2.5 10.38 C2.5,10.38 2.5,3.99 2.5,3.99 C2.5,0.61 5.3,-2.12 8.75,-2.12 C12.2,-2.12 15,0.61 15,3.99 C15,3.99 15,15 15,15 " android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.606,0 0.035,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator android:propertyName="pathData"
+                                android:duration="117" android:startOffset="217" android:valueFrom="M2.5 10.38 C2.5,10.38 2.5,3.99 2.5,3.99 C2.5,0.61 5.3,-2.12 8.75,-2.12 C12.2,-2.12 15,0.61 15,3.99 C15,3.99 15,15 15,15 " android:valueTo="M2.5 15 C2.5,15 2.5,8.61 2.5,8.61 C2.5,5.24 5.3,2.5 8.75,2.5 C12.2,2.5 15,5.24 15,8.61 C15,8.61 15,15 15,15 " android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.511,0 0.409,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="_R_G_L_2_G_T_1">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator android:propertyName="translateX"
+                                android:duration="333" android:startOffset="0" android:valueFrom="22.75" android:valueTo="23" android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.2,0 0,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="_R_G_L_2_G_T_1">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator android:propertyName="translateY" android:duration="333" android:startOffset="0" android:valueFrom="22.25" android:valueTo="25.5" android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.2,0 0,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="_R_G_L_2_G_T_1">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator android:propertyName="scaleX"
+                                android:duration="333" android:startOffset="0" android:valueFrom="1.02" android:valueTo="0.72" android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.2,0 0,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator android:propertyName="scaleY"
+                                android:duration="333" android:startOffset="0" android:valueFrom="1.02" android:valueTo="0.72" android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.2,0 0,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="_R_G_L_1_G_D_0_P_0">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator android:propertyName="strokeWidth"
+                                android:duration="333" android:startOffset="0" android:valueFrom="2" android:valueTo="1.5" android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.38,0 0.274,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="_R_G_L_1_G_D_0_P_0">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator android:propertyName="pathData"
+                                android:duration="333" android:startOffset="0" android:valueFrom="M11.25 -0.64 C11.25,-0.64 11.25,13.64 11.25,13.64 C11.25,15.22 9.97,16.5 8.39,16.5 C8.39,16.5 -8.39,16.5 -8.39,16.5 C-9.97,16.5 -11.25,15.22 -11.25,13.64 C-11.25,13.64 -11.25,-0.64 -11.25,-0.64 C-11.25,-2.22 -9.97,-3.5 -8.39,-3.5 C-8.39,-3.5 8.39,-3.5 8.39,-3.5 C9.97,-3.5 11.25,-2.22 11.25,-0.64c " android:valueTo="M7.88 -0.62 C7.88,-0.62 7.88,9.38 7.88,9.38 C7.88,10.48 6.98,11.38 5.88,11.38 C5.88,11.38 -5.87,11.38 -5.87,11.38 C-6.98,11.38 -7.87,10.48 -7.87,9.38 C-7.87,9.38 -7.87,-0.62 -7.87,-0.62 C-7.87,-1.73 -6.98,-2.62 -5.87,-2.62 C-5.87,-2.62 5.88,-2.62 5.88,-2.62 C6.98,-2.62 7.88,-1.73 7.88,-0.62c " android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.431,0 0.133,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="_R_G_L_0_G_D_0_P_0">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator android:propertyName="pathData"
+                                android:duration="333" android:startOffset="0" android:valueFrom="M-0.09 8.63 C1.2,8.63 2.25,7.57 2.25,6.28 C2.25,4.99 1.2,3.94 -0.09,3.94 C-1.39,3.94 -2.44,4.99 -2.44,6.28 C-2.44,7.57 -1.39,8.63 -0.09,8.63c " android:valueTo="M0 6.13 C0.97,6.13 1.75,5.34 1.75,4.38 C1.75,3.41 0.97,2.63 0,2.63 C-0.97,2.63 -1.75,3.41 -1.75,4.38 C-1.75,5.34 -0.97,6.13 0,6.13c " android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.431,0 0.133,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="time_group">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator android:propertyName="translateX"
+                                android:duration="850" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType" />
+            </set>
+        </aapt:attr>
+    </target>
+</animated-vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_user_switcher.xml b/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_user_switcher.xml
index a2b8bf6..4f0925f 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_user_switcher.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_user_switcher.xml
@@ -20,19 +20,18 @@
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:id="@+id/keyguard_bouncer_user_switcher"
     android:layout_width="match_parent"
-    android:layout_height="match_parent"
+    android:layout_height="wrap_content"
     android:clipChildren="false"
     android:clipToPadding="false"
     android:orientation="vertical"
     android:gravity="center"
-    android:paddingTop="12dp"
     android:importantForAccessibility="yes"> <!-- Needed because TYPE_WINDOW_STATE_CHANGED is sent
                                                   from this view when bouncer is shown -->
 
-  <ImageView
-      android:id="@+id/user_icon"
-      android:layout_width="@dimen/keyguard_user_switcher_icon_size"
-      android:layout_height="@dimen/keyguard_user_switcher_icon_size" />
+    <ImageView
+        android:id="@+id/user_icon"
+        android:layout_width="@dimen/keyguard_user_switcher_icon_size"
+        android:layout_height="@dimen/keyguard_user_switcher_icon_size" />
 
     <!-- need to keep this outer view in order to have a correctly sized anchor
          for the dropdown menu, as well as dropdown background in the right place -->
@@ -41,7 +40,7 @@
         android:orientation="horizontal"
         android:layout_height="wrap_content"
         android:layout_width="wrap_content"
-        android:layout_marginTop="32dp"
+        android:layout_marginTop="30dp"
         android:minHeight="48dp">
       <TextView
           style="@style/Keyguard.UserSwitcher.Spinner.Header"
diff --git a/packages/SystemUI/res-keyguard/values/dimens.xml b/packages/SystemUI/res-keyguard/values/dimens.xml
index 9533040..2819dc9 100644
--- a/packages/SystemUI/res-keyguard/values/dimens.xml
+++ b/packages/SystemUI/res-keyguard/values/dimens.xml
@@ -108,10 +108,10 @@
     <dimen name="one_handed_bouncer_move_animation_translation">120dp</dimen>
 
 
-    <dimen name="keyguard_user_switcher_header_text_size">24sp</dimen>
-    <dimen name="keyguard_user_switcher_item_text_size">18sp</dimen>
-    <dimen name="keyguard_user_switcher_width">300dp</dimen>
-    <dimen name="keyguard_user_switcher_icon_size">250dp</dimen>
+    <dimen name="keyguard_user_switcher_header_text_size">32sp</dimen>
+    <dimen name="keyguard_user_switcher_item_text_size">32sp</dimen>
+    <dimen name="keyguard_user_switcher_width">320dp</dimen>
+    <dimen name="keyguard_user_switcher_icon_size">310dp</dimen>
     <dimen name="keyguard_user_switcher_corner">32dp</dimen>
     <dimen name="keyguard_user_switcher_popup_corner">24dp</dimen>
     <dimen name="keyguard_user_switcher_item_padding_vertical">15dp</dimen>
diff --git a/packages/SystemUI/res/drawable/ic_signal_wifi_off.xml b/packages/SystemUI/res/drawable/ic_signal_wifi_off.xml
new file mode 100644
index 0000000..a93abb7
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_signal_wifi_off.xml
@@ -0,0 +1,24 @@
+<!--
+  ~ Copyright (C) 2021 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">
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M18.575,15.2L7.55,4.175q1.1,-0.325 2.212,-0.462 1.113,-0.138 2.238,-0.138 3.45,0 6.55,1.45Q21.65,6.475 24,9zM20.225,23.575l-4.75,-4.75 -3.475,4.1L0,9q0.675,-0.725 1.438,-1.4 0.762,-0.675 1.612,-1.225l-2.625,-2.6L2.1,2.1l19.8,19.8z"/>
+</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/dream_overlay_container.xml b/packages/SystemUI/res/layout/dream_overlay_container.xml
new file mode 100644
index 0000000..b611ffa
--- /dev/null
+++ b/packages/SystemUI/res/layout/dream_overlay_container.xml
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2021 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.
+-->
+<com.android.systemui.dreams.DreamOverlayContainerView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:id="@+id/dream_overlay_container"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <androidx.constraintlayout.widget.ConstraintLayout
+        android:id="@+id/dream_overlay_content"
+        android:layout_width="match_parent"
+        android:layout_height="0dp"
+        app:layout_constraintTop_toTopOf="parent"
+        app:layout_constraintBottom_toBottomOf="parent" />
+
+    <com.android.systemui.dreams.DreamOverlayStatusBarView
+        android:id="@+id/dream_overlay_status_bar"
+        android:layout_width="match_parent"
+        android:layout_height="@dimen/dream_overlay_status_bar_height"
+        android:layout_marginEnd="@dimen/dream_overlay_status_bar_margin"
+        android:layout_marginStart="@dimen/dream_overlay_status_bar_margin"
+        app:layout_constraintTop_toTopOf="parent">
+
+        <androidx.constraintlayout.widget.ConstraintLayout
+            android:id="@+id/dream_overlay_system_status"
+            android:layout_width="wrap_content"
+            android:layout_height="match_parent"
+            app:layout_constraintEnd_toEndOf="parent">
+
+            <com.android.systemui.statusbar.AlphaOptimizedImageView
+                android:id="@+id/dream_overlay_wifi_status"
+                android:layout_width="@dimen/status_bar_wifi_signal_size"
+                android:layout_height="match_parent"
+                android:layout_marginEnd="@dimen/dream_overlay_status_icon_margin"
+                android:visibility="gone"
+                app:layout_constraintEnd_toStartOf="@id/dream_overlay_battery" />
+
+            <com.android.systemui.battery.BatteryMeterView
+                android:id="@+id/dream_overlay_battery"
+                android:layout_width="wrap_content"
+                android:layout_height="match_parent"
+                app:layout_constraintEnd_toEndOf="parent" />
+
+        </androidx.constraintlayout.widget.ConstraintLayout>
+    </com.android.systemui.dreams.DreamOverlayStatusBarView>
+</com.android.systemui.dreams.DreamOverlayContainerView>
\ No newline at end of file
diff --git a/packages/SystemUI/res/values-h800dp/dimens.xml b/packages/SystemUI/res/values-h800dp/dimens.xml
index f057603..1d6f279 100644
--- a/packages/SystemUI/res/values-h800dp/dimens.xml
+++ b/packages/SystemUI/res/values-h800dp/dimens.xml
@@ -22,5 +22,5 @@
     <dimen name="large_clock_text_size">200dp</dimen>
 
     <!-- With the large clock, move up slightly from the center -->
-    <dimen name="keyguard_large_clock_top_margin">-104dp</dimen>
+    <dimen name="keyguard_large_clock_top_margin">-112dp</dimen>
 </resources>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index c7350a1..d4398a8 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -30,10 +30,10 @@
     <dimen name="navigation_bar_deadzone_size_max">32dp</dimen>
 
     <!-- dimensions for the navigation bar handle -->
-    <dimen name="navigation_handle_radius">1dp</dimen>
-    <dimen name="navigation_handle_bottom">6dp</dimen>
+    <dimen name="navigation_handle_radius">2dp</dimen>
+    <dimen name="navigation_handle_bottom">10dp</dimen>
     <dimen name="navigation_handle_sample_horizontal_margin">10dp</dimen>
-    <dimen name="navigation_home_handle_width">72dp</dimen>
+    <dimen name="navigation_home_handle_width">108dp</dimen>
 
     <!-- Size of the nav bar edge panels, should be greater to the
          edge sensitivity + the drag threshold -->
@@ -602,7 +602,7 @@
     <!-- When large clock is showing, offset the smartspace by this amount -->
     <dimen name="keyguard_smartspace_top_offset">12dp</dimen>
     <!-- With the large clock, move up slightly from the center -->
-    <dimen name="keyguard_large_clock_top_margin">-52dp</dimen>
+    <dimen name="keyguard_large_clock_top_margin">-60dp</dimen>
 
     <!-- Default line spacing multiplier between hours and minutes of the keyguard clock -->
     <item name="keyguard_clock_line_spacing_scale" type="dimen" format="float">.7</item>
@@ -1303,4 +1303,9 @@
     <dimen name="keyguard_unfold_translation_x">16dp</dimen>
 
     <dimen name="fgs_manager_min_width_minor">100%</dimen>
+
+    <!-- Dream overlay related dimensions -->
+    <dimen name="dream_overlay_status_bar_height">80dp</dimen>
+    <dimen name="dream_overlay_status_bar_margin">40dp</dimen>
+    <dimen name="dream_overlay_status_icon_margin">8dp</dimen>
 </resources>
diff --git a/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java b/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java
index ac463eb..1571913 100644
--- a/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java
+++ b/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java
@@ -168,6 +168,12 @@
         if (!mIsDozing) mView.animateAppearOnLockscreen();
     }
 
+    /** Animate the clock appearance when a foldable device goes from fully-open/half-open state to
+     * fully folded state and it goes to sleep (always on display screen) */
+    public void animateFoldAppear() {
+        mView.animateFoldAppear();
+    }
+
     /**
      * Updates the time for the view.
      */
diff --git a/packages/SystemUI/src/com/android/keyguard/AnimatableClockView.java b/packages/SystemUI/src/com/android/keyguard/AnimatableClockView.java
deleted file mode 100644
index 2a0c285..0000000
--- a/packages/SystemUI/src/com/android/keyguard/AnimatableClockView.java
+++ /dev/null
@@ -1,315 +0,0 @@
-/*
- * Copyright (C) 2020 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.keyguard;
-
-import android.annotation.FloatRange;
-import android.annotation.IntRange;
-import android.content.Context;
-import android.content.res.Resources;
-import android.content.res.TypedArray;
-import android.graphics.Canvas;
-import android.text.format.DateFormat;
-import android.util.AttributeSet;
-import android.widget.TextView;
-
-import com.android.systemui.R;
-
-import java.util.Calendar;
-import java.util.Locale;
-import java.util.TimeZone;
-
-import kotlin.Unit;
-
-/**
- * Displays the time with the hour positioned above the minutes. (ie: 09 above 30 is 9:30)
- * The time's text color is a gradient that changes its colors based on its controller.
- */
-public class AnimatableClockView extends TextView {
-    private static final CharSequence DOUBLE_LINE_FORMAT_12_HOUR = "hh\nmm";
-    private static final CharSequence DOUBLE_LINE_FORMAT_24_HOUR = "HH\nmm";
-    private static final long DOZE_ANIM_DURATION = 300;
-    private static final long APPEAR_ANIM_DURATION = 350;
-    private static final long CHARGE_ANIM_DURATION_PHASE_0 = 500;
-    private static final long CHARGE_ANIM_DURATION_PHASE_1 = 1000;
-
-    private final Calendar mTime = Calendar.getInstance();
-
-    private final int mDozingWeight;
-    private final int mLockScreenWeight;
-    private CharSequence mFormat;
-    private CharSequence mDescFormat;
-    private int mDozingColor;
-    private int mLockScreenColor;
-    private float mLineSpacingScale = 1f;
-    private int mChargeAnimationDelay = 0;
-
-    private TextAnimator mTextAnimator = null;
-    private Runnable mOnTextAnimatorInitialized;
-
-    private boolean mIsSingleLine;
-
-    public AnimatableClockView(Context context) {
-        this(context, null, 0, 0);
-    }
-
-    public AnimatableClockView(Context context, AttributeSet attrs) {
-        this(context, attrs, 0, 0);
-    }
-
-    public AnimatableClockView(Context context, AttributeSet attrs, int defStyleAttr) {
-        this(context, attrs, defStyleAttr, 0);
-    }
-
-    public AnimatableClockView(Context context, AttributeSet attrs, int defStyleAttr,
-            int defStyleRes) {
-        super(context, attrs, defStyleAttr, defStyleRes);
-        TypedArray ta = context.obtainStyledAttributes(
-                attrs, R.styleable.AnimatableClockView, defStyleAttr, defStyleRes);
-        try {
-            mDozingWeight = ta.getInt(R.styleable.AnimatableClockView_dozeWeight, 100);
-            mLockScreenWeight = ta.getInt(R.styleable.AnimatableClockView_lockScreenWeight, 300);
-            mChargeAnimationDelay = ta.getInt(
-                    R.styleable.AnimatableClockView_chargeAnimationDelay, 200);
-        } finally {
-            ta.recycle();
-        }
-
-        ta = context.obtainStyledAttributes(
-                attrs, android.R.styleable.TextView, defStyleAttr, defStyleRes);
-        try {
-            mIsSingleLine = ta.getBoolean(android.R.styleable.TextView_singleLine, false);
-        } finally {
-            ta.recycle();
-        }
-
-        refreshFormat();
-    }
-
-    @Override
-    public void onAttachedToWindow() {
-        super.onAttachedToWindow();
-        refreshFormat();
-    }
-
-    @Override
-    public void onDetachedFromWindow() {
-        super.onDetachedFromWindow();
-    }
-
-    int getDozingWeight() {
-        if (useBoldedVersion()) {
-            return mDozingWeight + 100;
-        }
-        return mDozingWeight;
-    }
-
-    int getLockScreenWeight() {
-        if (useBoldedVersion()) {
-            return mLockScreenWeight + 100;
-        }
-        return mLockScreenWeight;
-    }
-
-    /**
-     * Whether to use a bolded version based on the user specified fontWeightAdjustment.
-     */
-    boolean useBoldedVersion() {
-        // "Bold text" fontWeightAdjustment is 300.
-        return getResources().getConfiguration().fontWeightAdjustment > 100;
-    }
-
-    void refreshTime() {
-        mTime.setTimeInMillis(System.currentTimeMillis());
-        setText(DateFormat.format(mFormat, mTime));
-        setContentDescription(DateFormat.format(mDescFormat, mTime));
-    }
-
-    void onTimeZoneChanged(TimeZone timeZone) {
-        mTime.setTimeZone(timeZone);
-        refreshFormat();
-    }
-
-    @Override
-    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
-        if (mTextAnimator == null) {
-            mTextAnimator = new TextAnimator(
-                    getLayout(),
-                    () -> {
-                        invalidate();
-                        return Unit.INSTANCE;
-                    });
-            if (mOnTextAnimatorInitialized != null) {
-                mOnTextAnimatorInitialized.run();
-                mOnTextAnimatorInitialized = null;
-            }
-        } else {
-            mTextAnimator.updateLayout(getLayout());
-        }
-    }
-
-    @Override
-    protected void onDraw(Canvas canvas) {
-        mTextAnimator.draw(canvas);
-    }
-
-    void setLineSpacingScale(float scale) {
-        mLineSpacingScale = scale;
-        setLineSpacing(0, mLineSpacingScale);
-    }
-
-    void setColors(int dozingColor, int lockScreenColor) {
-        mDozingColor = dozingColor;
-        mLockScreenColor = lockScreenColor;
-    }
-
-    void animateAppearOnLockscreen() {
-        if (mTextAnimator == null) {
-            return;
-        }
-
-        setTextStyle(
-                getDozingWeight(),
-                -1 /* text size, no update */,
-                mLockScreenColor,
-                false /* animate */,
-                0 /* duration */,
-                0 /* delay */,
-                null /* onAnimationEnd */);
-
-        setTextStyle(
-                getLockScreenWeight(),
-                -1 /* text size, no update */,
-                mLockScreenColor,
-                true, /* animate */
-                APPEAR_ANIM_DURATION,
-                0 /* delay */,
-                null /* onAnimationEnd */);
-    }
-
-    void animateCharge(DozeStateGetter dozeStateGetter) {
-        if (mTextAnimator == null || mTextAnimator.isRunning()) {
-            // Skip charge animation if dozing animation is already playing.
-            return;
-        }
-        Runnable startAnimPhase2 = () -> setTextStyle(
-                dozeStateGetter.isDozing() ? getDozingWeight() : getLockScreenWeight() /* weight */,
-                -1,
-                null,
-                true /* animate */,
-                CHARGE_ANIM_DURATION_PHASE_1,
-                0 /* delay */,
-                null /* onAnimationEnd */);
-        setTextStyle(dozeStateGetter.isDozing()
-                        ? getLockScreenWeight()
-                        : getDozingWeight()/* weight */,
-                -1,
-                null,
-                true /* animate */,
-                CHARGE_ANIM_DURATION_PHASE_0,
-                mChargeAnimationDelay,
-                startAnimPhase2);
-    }
-
-    void animateDoze(boolean isDozing, boolean animate) {
-        setTextStyle(isDozing ? getDozingWeight() : getLockScreenWeight() /* weight */,
-                -1,
-                isDozing ? mDozingColor : mLockScreenColor,
-                animate,
-                DOZE_ANIM_DURATION,
-                0 /* delay */,
-                null /* onAnimationEnd */);
-    }
-
-    /**
-     * Set text style with an optional animation.
-     *
-     * By passing -1 to weight, the view preserves its current weight.
-     * By passing -1 to textSize, the view preserves its current text size.
-     *
-     * @param weight text weight.
-     * @param textSize font size.
-     * @param animate true to animate the text style change, otherwise false.
-     */
-    private void setTextStyle(
-            @IntRange(from = 0, to = 1000) int weight,
-            @FloatRange(from = 0) float textSize,
-            Integer color,
-            boolean animate,
-            long duration,
-            long delay,
-            Runnable onAnimationEnd) {
-        if (mTextAnimator != null) {
-            mTextAnimator.setTextStyle(weight, textSize, color, animate, duration, null,
-                    delay, onAnimationEnd);
-        } else {
-            // when the text animator is set, update its start values
-            mOnTextAnimatorInitialized =
-                    () -> mTextAnimator.setTextStyle(
-                            weight, textSize, color, false, duration, null,
-                            delay, onAnimationEnd);
-        }
-    }
-
-    void refreshFormat() {
-        Patterns.update(mContext);
-
-        final boolean use24HourFormat = DateFormat.is24HourFormat(getContext());
-        if (mIsSingleLine && use24HourFormat) {
-            mFormat = Patterns.sClockView24;
-        } else if (!mIsSingleLine && use24HourFormat) {
-            mFormat = DOUBLE_LINE_FORMAT_24_HOUR;
-        } else if (mIsSingleLine && !use24HourFormat) {
-            mFormat = Patterns.sClockView12;
-        } else {
-            mFormat = DOUBLE_LINE_FORMAT_12_HOUR;
-        }
-
-        mDescFormat = use24HourFormat ? Patterns.sClockView24 : Patterns.sClockView12;
-        refreshTime();
-    }
-
-    // DateFormat.getBestDateTimePattern is extremely expensive, and refresh is called often.
-    // This is an optimization to ensure we only recompute the patterns when the inputs change.
-    private static final class Patterns {
-        static String sClockView12;
-        static String sClockView24;
-        static String sCacheKey;
-
-        static void update(Context context) {
-            final Locale locale = Locale.getDefault();
-            final Resources res = context.getResources();
-            final String clockView12Skel = res.getString(R.string.clock_12hr_format);
-            final String clockView24Skel = res.getString(R.string.clock_24hr_format);
-            final String key = locale.toString() + clockView12Skel + clockView24Skel;
-            if (key.equals(sCacheKey)) return;
-            sClockView12 = DateFormat.getBestDateTimePattern(locale, clockView12Skel);
-
-            // CLDR insists on adding an AM/PM indicator even though it wasn't in the skeleton
-            // format.  The following code removes the AM/PM indicator if we didn't want it.
-            if (!clockView12Skel.contains("a")) {
-                sClockView12 = sClockView12.replaceAll("a", "").trim();
-            }
-            sClockView24 = DateFormat.getBestDateTimePattern(locale, clockView24Skel);
-            sCacheKey = key;
-        }
-    }
-
-    interface DozeStateGetter {
-        boolean isDozing();
-    }
-}
diff --git a/packages/SystemUI/src/com/android/keyguard/AnimatableClockView.kt b/packages/SystemUI/src/com/android/keyguard/AnimatableClockView.kt
new file mode 100644
index 0000000..357be25
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/AnimatableClockView.kt
@@ -0,0 +1,370 @@
+/*
+ * Copyright (C) 2021 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.keyguard
+
+import android.animation.TimeInterpolator
+import android.annotation.ColorInt
+import android.annotation.FloatRange
+import android.annotation.IntRange
+import android.annotation.SuppressLint
+import android.content.Context
+import android.graphics.Canvas
+import android.text.format.DateFormat
+import android.util.AttributeSet
+import android.widget.TextView
+import com.android.systemui.R
+import com.android.systemui.animation.Interpolators
+import com.android.systemui.statusbar.notification.stack.StackStateAnimator
+import java.util.Calendar
+import java.util.Locale
+import java.util.TimeZone
+
+/**
+ * Displays the time with the hour positioned above the minutes. (ie: 09 above 30 is 9:30)
+ * The time's text color is a gradient that changes its colors based on its controller.
+ */
+class AnimatableClockView @JvmOverloads constructor(
+    context: Context,
+    attrs: AttributeSet? = null,
+    defStyleAttr: Int = 0,
+    defStyleRes: Int = 0
+) : TextView(context, attrs, defStyleAttr, defStyleRes) {
+
+    private val time = Calendar.getInstance()
+
+    private val dozingWeightInternal: Int
+    private val lockScreenWeightInternal: Int
+    private val isSingleLineInternal: Boolean
+
+    private var format: CharSequence? = null
+    private var descFormat: CharSequence? = null
+
+    @ColorInt
+    private var dozingColor = 0
+
+    @ColorInt
+    private var lockScreenColor = 0
+
+    private var lineSpacingScale = 1f
+    private val chargeAnimationDelay: Int
+    private var textAnimator: TextAnimator? = null
+    private var onTextAnimatorInitialized: Runnable? = null
+
+    val dozingWeight: Int
+        get() = if (useBoldedVersion()) dozingWeightInternal + 100 else dozingWeightInternal
+
+    val lockScreenWeight: Int
+        get() = if (useBoldedVersion()) lockScreenWeightInternal + 100 else lockScreenWeightInternal
+
+    init {
+        val animatableClockViewAttributes = context.obtainStyledAttributes(
+            attrs, R.styleable.AnimatableClockView, defStyleAttr, defStyleRes
+        )
+
+        try {
+            dozingWeightInternal = animatableClockViewAttributes.getInt(
+                R.styleable.AnimatableClockView_dozeWeight,
+                100
+            )
+            lockScreenWeightInternal = animatableClockViewAttributes.getInt(
+                R.styleable.AnimatableClockView_lockScreenWeight,
+                300
+            )
+            chargeAnimationDelay = animatableClockViewAttributes.getInt(
+                R.styleable.AnimatableClockView_chargeAnimationDelay, 200
+            )
+        } finally {
+            animatableClockViewAttributes.recycle()
+        }
+
+        val textViewAttributes = context.obtainStyledAttributes(
+            attrs, android.R.styleable.TextView,
+            defStyleAttr, defStyleRes
+        )
+
+        isSingleLineInternal =
+            try {
+                textViewAttributes.getBoolean(android.R.styleable.TextView_singleLine, false)
+            } finally {
+                textViewAttributes.recycle()
+            }
+
+        refreshFormat()
+    }
+
+    override fun onAttachedToWindow() {
+        super.onAttachedToWindow()
+        refreshFormat()
+    }
+
+    /**
+     * Whether to use a bolded version based on the user specified fontWeightAdjustment.
+     */
+    fun useBoldedVersion(): Boolean {
+        // "Bold text" fontWeightAdjustment is 300.
+        return resources.configuration.fontWeightAdjustment > 100
+    }
+
+    fun refreshTime() {
+        time.timeInMillis = System.currentTimeMillis()
+        text = DateFormat.format(format, time)
+        contentDescription = DateFormat.format(descFormat, time)
+    }
+
+    fun onTimeZoneChanged(timeZone: TimeZone?) {
+        time.timeZone = timeZone
+        refreshFormat()
+    }
+
+    @SuppressLint("DrawAllocation")
+    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec)
+        val animator = textAnimator
+        if (animator == null) {
+            textAnimator = TextAnimator(layout) { invalidate() }
+            onTextAnimatorInitialized?.run()
+            onTextAnimatorInitialized = null
+        } else {
+            animator.updateLayout(layout)
+        }
+    }
+
+    override fun onDraw(canvas: Canvas) {
+        textAnimator?.draw(canvas)
+    }
+
+    fun setLineSpacingScale(scale: Float) {
+        lineSpacingScale = scale
+        setLineSpacing(0f, lineSpacingScale)
+    }
+
+    fun setColors(@ColorInt dozingColor: Int, lockScreenColor: Int) {
+        this.dozingColor = dozingColor
+        this.lockScreenColor = lockScreenColor
+    }
+
+    fun animateAppearOnLockscreen() {
+        if (textAnimator == null) {
+            return
+        }
+        setTextStyle(
+            weight = dozingWeight,
+            textSize = -1f,
+            color = lockScreenColor,
+            animate = false,
+            duration = 0,
+            delay = 0,
+            onAnimationEnd = null
+        )
+        setTextStyle(
+            weight = lockScreenWeight,
+            textSize = -1f,
+            color = lockScreenColor,
+            animate = true,
+            duration = APPEAR_ANIM_DURATION,
+            delay = 0,
+            onAnimationEnd = null
+        )
+    }
+
+    fun animateFoldAppear() {
+        if (textAnimator == null) {
+            return
+        }
+        setTextStyle(
+            weight = lockScreenWeightInternal,
+            textSize = -1f,
+            color = lockScreenColor,
+            animate = false,
+            duration = 0,
+            delay = 0,
+            onAnimationEnd = null
+        )
+        setTextStyle(
+            weight = dozingWeightInternal,
+            textSize = -1f,
+            color = dozingColor,
+            animate = true,
+            interpolator = Interpolators.EMPHASIZED_DECELERATE,
+            duration = StackStateAnimator.ANIMATION_DURATION_FOLD_TO_AOD.toLong(),
+            delay = 0,
+            onAnimationEnd = null
+        )
+    }
+
+    fun animateCharge(dozeStateGetter: DozeStateGetter) {
+        if (textAnimator == null || textAnimator!!.isRunning()) {
+            // Skip charge animation if dozing animation is already playing.
+            return
+        }
+        val startAnimPhase2 = Runnable {
+            setTextStyle(
+                weight = if (dozeStateGetter.isDozing) dozingWeight else lockScreenWeight,
+                textSize = -1f,
+                color = null,
+                animate = true,
+                duration = CHARGE_ANIM_DURATION_PHASE_1,
+                delay = 0,
+                onAnimationEnd = null
+            )
+        }
+        setTextStyle(
+            weight = if (dozeStateGetter.isDozing) lockScreenWeight else dozingWeight,
+            textSize = -1f,
+            color = null,
+            animate = true,
+            duration = CHARGE_ANIM_DURATION_PHASE_0,
+            delay = chargeAnimationDelay.toLong(),
+            onAnimationEnd = startAnimPhase2
+        )
+    }
+
+    fun animateDoze(isDozing: Boolean, animate: Boolean) {
+        setTextStyle(
+            weight = if (isDozing) dozingWeight else lockScreenWeight,
+            textSize = -1f,
+            color = if (isDozing) dozingColor else lockScreenColor,
+            animate = animate,
+            duration = DOZE_ANIM_DURATION,
+            delay = 0,
+            onAnimationEnd = null
+        )
+    }
+
+    /**
+     * Set text style with an optional animation.
+     *
+     * By passing -1 to weight, the view preserves its current weight.
+     * By passing -1 to textSize, the view preserves its current text size.
+     *
+     * @param weight text weight.
+     * @param textSize font size.
+     * @param animate true to animate the text style change, otherwise false.
+     */
+    private fun setTextStyle(
+        @IntRange(from = 0, to = 1000) weight: Int,
+        @FloatRange(from = 0.0) textSize: Float,
+        color: Int?,
+        animate: Boolean,
+        interpolator: TimeInterpolator?,
+        duration: Long,
+        delay: Long,
+        onAnimationEnd: Runnable?
+    ) {
+        if (textAnimator != null) {
+            textAnimator?.setTextStyle(
+                weight = weight,
+                textSize = textSize,
+                color = color,
+                animate = animate,
+                duration = duration,
+                interpolator = interpolator,
+                delay = delay,
+                onAnimationEnd = onAnimationEnd
+            )
+        } else {
+            // when the text animator is set, update its start values
+            onTextAnimatorInitialized = Runnable {
+                textAnimator?.setTextStyle(
+                    weight = weight,
+                    textSize = textSize,
+                    color = color,
+                    animate = false,
+                    duration = duration,
+                    interpolator = interpolator,
+                    delay = delay,
+                    onAnimationEnd = onAnimationEnd
+                )
+            }
+        }
+    }
+
+    private fun setTextStyle(
+        @IntRange(from = 0, to = 1000) weight: Int,
+        @FloatRange(from = 0.0) textSize: Float,
+        color: Int?,
+        animate: Boolean,
+        duration: Long,
+        delay: Long,
+        onAnimationEnd: Runnable?
+    ) {
+        setTextStyle(
+            weight = weight,
+            textSize = textSize,
+            color = color,
+            animate = animate,
+            interpolator = null,
+            duration = duration,
+            delay = delay,
+            onAnimationEnd = onAnimationEnd
+        )
+    }
+
+    fun refreshFormat() {
+        Patterns.update(context)
+        val use24HourFormat = DateFormat.is24HourFormat(context)
+
+        format = when {
+            isSingleLineInternal && use24HourFormat -> Patterns.sClockView24
+            !isSingleLineInternal && use24HourFormat -> DOUBLE_LINE_FORMAT_24_HOUR
+            isSingleLineInternal && !use24HourFormat -> Patterns.sClockView12
+            else -> DOUBLE_LINE_FORMAT_12_HOUR
+        }
+
+        descFormat = if (use24HourFormat) Patterns.sClockView24 else Patterns.sClockView12
+
+        refreshTime()
+    }
+
+    // DateFormat.getBestDateTimePattern is extremely expensive, and refresh is called often.
+    // This is an optimization to ensure we only recompute the patterns when the inputs change.
+    private object Patterns {
+        var sClockView12: String? = null
+        var sClockView24: String? = null
+        var sCacheKey: String? = null
+
+        fun update(context: Context) {
+            val locale = Locale.getDefault()
+            val res = context.resources
+            val clockView12Skel = res.getString(R.string.clock_12hr_format)
+            val clockView24Skel = res.getString(R.string.clock_24hr_format)
+            val key = locale.toString() + clockView12Skel + clockView24Skel
+            if (key == sCacheKey) return
+
+            val clockView12 = DateFormat.getBestDateTimePattern(locale, clockView12Skel)
+            sClockView12 = clockView12
+
+            // CLDR insists on adding an AM/PM indicator even though it wasn't in the skeleton
+            // format.  The following code removes the AM/PM indicator if we didn't want it.
+            if (!clockView12Skel.contains("a")) {
+                sClockView12 = clockView12.replace("a".toRegex(), "").trim { it <= ' ' }
+            }
+            sClockView24 = DateFormat.getBestDateTimePattern(locale, clockView24Skel)
+            sCacheKey = key
+        }
+    }
+
+    interface DozeStateGetter {
+        val isDozing: Boolean
+    }
+}
+
+private const val DOUBLE_LINE_FORMAT_12_HOUR = "hh\nmm"
+private const val DOUBLE_LINE_FORMAT_24_HOUR = "HH\nmm"
+private const val DOZE_ANIM_DURATION: Long = 300
+private const val APPEAR_ANIM_DURATION: Long = 350
+private const val CHARGE_ANIM_DURATION_PHASE_0: Long = 500
+private const val CHARGE_ANIM_DURATION_PHASE_1: Long = 1000
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
index 9238b82..25dcdf9 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
@@ -190,11 +190,15 @@
         }
     }
 
-    private void animateClockChange(boolean useLargeClock) {
+    private void updateClockViews(boolean useLargeClock, boolean animate) {
         if (mClockInAnim != null) mClockInAnim.cancel();
         if (mClockOutAnim != null) mClockOutAnim.cancel();
         if (mStatusAreaAnim != null) mStatusAreaAnim.cancel();
 
+        mClockInAnim = null;
+        mClockOutAnim = null;
+        mStatusAreaAnim = null;
+
         View in, out;
         int direction = 1;
         float statusAreaYTranslation;
@@ -214,6 +218,14 @@
             removeView(out);
         }
 
+        if (!animate) {
+            out.setAlpha(0f);
+            in.setAlpha(1f);
+            in.setVisibility(VISIBLE);
+            mStatusArea.setTranslationY(statusAreaYTranslation);
+            return;
+        }
+
         mClockOutAnim = new AnimatorSet();
         mClockOutAnim.setDuration(CLOCK_OUT_MILLIS);
         mClockOutAnim.setInterpolator(Interpolators.FAST_OUT_LINEAR_IN);
@@ -273,7 +285,7 @@
      *
      * @return true if desired clock appeared and false if it was already visible
      */
-    boolean switchToClock(@ClockSize int clockSize) {
+    boolean switchToClock(@ClockSize int clockSize, boolean animate) {
         if (mDisplayedClockSize != null && clockSize == mDisplayedClockSize) {
             return false;
         }
@@ -281,7 +293,7 @@
         // let's make sure clock is changed only after all views were laid out so we can
         // translate them properly
         if (mChildrenAreLaidOut) {
-            animateClockChange(clockSize == LARGE);
+            updateClockViews(clockSize == LARGE, animate);
         }
 
         mDisplayedClockSize = clockSize;
@@ -293,7 +305,7 @@
         super.onLayout(changed, l, t, r, b);
 
         if (mDisplayedClockSize != null && !mChildrenAreLaidOut) {
-            animateClockChange(mDisplayedClockSize == LARGE);
+            updateClockViews(mDisplayedClockSize == LARGE, /* animate */ true);
         }
 
         mChildrenAreLaidOut = true;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
index 032da78..b7a5aae 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
@@ -275,11 +275,9 @@
     }
 
     private void updateClockLayout() {
-        int largeClockTopMargin = 0;
-        if (mSmartspaceController.isEnabled()) {
-            largeClockTopMargin = getContext().getResources().getDimensionPixelSize(
-                    R.dimen.keyguard_large_clock_top_margin);
-        }
+        int largeClockTopMargin = getContext().getResources().getDimensionPixelSize(
+                R.dimen.keyguard_large_clock_top_margin);
+
         RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams(MATCH_PARENT,
                 MATCH_PARENT);
         lp.topMargin = largeClockTopMargin;
@@ -290,17 +288,24 @@
      * Set which clock should be displayed on the keyguard. The other one will be automatically
      * hidden.
      */
-    public void displayClock(@KeyguardClockSwitch.ClockSize int clockSize) {
+    public void displayClock(@KeyguardClockSwitch.ClockSize int clockSize, boolean animate) {
         if (!mCanShowDoubleLineClock && clockSize == KeyguardClockSwitch.LARGE) {
             return;
         }
 
-        boolean appeared = mView.switchToClock(clockSize);
-        if (appeared && clockSize == LARGE) {
+        boolean appeared = mView.switchToClock(clockSize, animate);
+        if (animate && appeared && clockSize == LARGE) {
             mLargeClockViewController.animateAppear();
         }
     }
 
+    public void animateFoldToAod() {
+        if (mClockViewController != null) {
+            mClockViewController.animateFoldAppear();
+            mLargeClockViewController.animateFoldAppear();
+        }
+    }
+
     /**
      * If we're presenting a custom clock of just the default one.
      */
@@ -443,7 +448,7 @@
             Settings.Secure.LOCKSCREEN_USE_DOUBLE_LINE_CLOCK, 1) != 0;
 
         if (!mCanShowDoubleLineClock) {
-            mUiExecutor.execute(() -> displayClock(KeyguardClockSwitch.SMALL));
+            mUiExecutor.execute(() -> displayClock(KeyguardClockSwitch.SMALL, /* animate */ true));
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
index 67ef02a..b84cb19 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
@@ -866,7 +866,6 @@
                 ((LayerDrawable) mUserSwitcher.getBackground()).getDrawable(1).setAlpha(255);
             }
 
-            anchor.setClickable(true);
             anchor.setOnClickListener((v) -> {
                 if (mFalsingManager.isFalseTap(LOW_PENALTY)) return;
 
@@ -877,8 +876,7 @@
                         public void onItemClick(AdapterView parent, View view, int pos, long id) {
                             if (mFalsingManager.isFalseTap(LOW_PENALTY)) return;
 
-                            // - 1 to account for the header view
-                            UserRecord user = adapter.getItem(pos - 1);
+                            UserRecord user = adapter.getItem(pos);
                             if (!user.isCurrent) {
                                 adapter.onUserListItemClicked(user);
                             }
@@ -907,9 +905,16 @@
                     == Configuration.ORIENTATION_PORTRAIT) {
                 updateViewGravity(mViewFlipper, Gravity.CENTER_HORIZONTAL);
                 updateViewGravity(mUserSwitcherViewGroup, Gravity.CENTER_HORIZONTAL);
+                mUserSwitcherViewGroup.setTranslationY(0);
             } else {
                 updateViewGravity(mViewFlipper, Gravity.RIGHT | Gravity.BOTTOM);
-                updateViewGravity(mUserSwitcherViewGroup, Gravity.LEFT | Gravity.TOP);
+                updateViewGravity(mUserSwitcherViewGroup, Gravity.LEFT | Gravity.CENTER_VERTICAL);
+
+                // Attempt to reposition a bit higher to make up for this frame being a bit lower
+                // on the device
+                int yTrans = mView.getContext().getResources().getDimensionPixelSize(
+                        R.dimen.status_bar_height);
+                mUserSwitcherViewGroup.setTranslationY(-yTrans);
             }
         }
 
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
index 986d0de..8bf890d 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
@@ -123,8 +123,17 @@
      * Set which clock should be displayed on the keyguard. The other one will be automatically
      * hidden.
      */
-    public void displayClock(@ClockSize int clockSize) {
-        mKeyguardClockSwitchController.displayClock(clockSize);
+    public void displayClock(@ClockSize int clockSize, boolean animate) {
+        mKeyguardClockSwitchController.displayClock(clockSize, animate);
+    }
+
+    /**
+     * Performs fold to aod animation of the clocks (changes font weight from bold to thin).
+     * This animation is played when AOD is enabled and foldable device is fully folded, it is
+     * displayed on the outer screen
+     */
+    public void animateFoldToAod() {
+        mKeyguardClockSwitchController.animateFoldToAod();
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUserSwitcherPopupMenu.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUserSwitcherPopupMenu.java
index dfb4667..7b6ce3e 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUserSwitcherPopupMenu.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUserSwitcherPopupMenu.java
@@ -18,12 +18,10 @@
 import android.annotation.NonNull;
 import android.content.Context;
 import android.content.res.Resources;
-import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.View;
 import android.widget.ListPopupWindow;
 import android.widget.ListView;
-import android.widget.TextView;
 
 import com.android.systemui.R;
 import com.android.systemui.plugins.FalsingManager;
@@ -68,12 +66,6 @@
         // This will force the popupwindow to show upward instead of drop down
         listView.addOnLayoutChangeListener(mLayoutListener);
 
-        TextView header = (TextView) LayoutInflater.from(mContext).inflate(
-                R.layout.keyguard_bouncer_user_switcher_item, listView, false);
-        header.setText(mContext.getResources().getString(
-                R.string.accessibility_multi_user_switch_switcher));
-        listView.addHeaderView(header);
-
         listView.setOnTouchListener((v, ev) -> {
             if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) {
                 return mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY);
diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
index cd57af4..6626f59 100644
--- a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
@@ -278,10 +278,6 @@
             mView.setContentDescription(mUnlockedLabel);
             mView.setVisibility(View.VISIBLE);
         } else if (mShowAodLockIcon) {
-            if (wasShowingUnlock) {
-                // transition to the unlock icon first
-                mView.updateIcon(ICON_LOCK, false);
-            }
             mView.updateIcon(ICON_LOCK, true);
             mView.setContentDescription(mLockedLabel);
             mView.setVisibility(View.VISIBLE);
diff --git a/packages/SystemUI/src/com/android/systemui/communal/CommunalSourceMonitor.java b/packages/SystemUI/src/com/android/systemui/communal/CommunalSourceMonitor.java
index 4aa46f18..58cf35f 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/CommunalSourceMonitor.java
+++ b/packages/SystemUI/src/com/android/systemui/communal/CommunalSourceMonitor.java
@@ -16,12 +16,14 @@
 
 package com.android.systemui.communal;
 
+import static com.android.systemui.communal.dagger.CommunalModule.COMMUNAL_CONDITIONS;
+
 import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.systemui.communal.conditions.CommunalConditionsMonitor;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.util.condition.Monitor;
 
 import com.google.android.collect.Lists;
 
@@ -31,6 +33,7 @@
 import java.util.concurrent.Executor;
 
 import javax.inject.Inject;
+import javax.inject.Named;
 
 /**
  * A Monitor for reporting a {@link CommunalSource} presence.
@@ -42,7 +45,7 @@
 
     // A list of {@link Callback} that have registered to receive updates.
     private final ArrayList<WeakReference<Callback>> mCallbacks = Lists.newArrayList();
-    private final CommunalConditionsMonitor mConditionsMonitor;
+    private final Monitor mConditionsMonitor;
     private final Executor mExecutor;
 
     private CommunalSource mCurrentSource;
@@ -53,7 +56,7 @@
     // Whether the class is currently listening for condition changes.
     private boolean mListeningForConditions = false;
 
-    private final CommunalConditionsMonitor.Callback mConditionsCallback =
+    private final Monitor.Callback mConditionsCallback =
             allConditionsMet -> {
                 if (mAllCommunalConditionsMet != allConditionsMet) {
                     if (DEBUG) Log.d(TAG, "communal conditions changed: " + allConditionsMet);
@@ -66,7 +69,7 @@
     @VisibleForTesting
     @Inject
     public CommunalSourceMonitor(@Main Executor executor,
-            CommunalConditionsMonitor communalConditionsMonitor) {
+            @Named(COMMUNAL_CONDITIONS) Monitor communalConditionsMonitor) {
         mExecutor = executor;
         mConditionsMonitor = communalConditionsMonitor;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/conditions/CommunalConditionsMonitor.java b/packages/SystemUI/src/com/android/systemui/communal/conditions/CommunalConditionsMonitor.java
deleted file mode 100644
index 1197816..0000000
--- a/packages/SystemUI/src/com/android/systemui/communal/conditions/CommunalConditionsMonitor.java
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.communal.conditions;
-
-
-import static com.android.systemui.communal.dagger.CommunalModule.COMMUNAL_CONDITIONS;
-
-import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.util.condition.Condition;
-import com.android.systemui.util.condition.Monitor;
-
-import java.util.Set;
-
-import javax.inject.Inject;
-import javax.inject.Named;
-
-/**
- * A concrete implementation of {@Monitor} with conditions for monitoring when communal mode should
- * be enabled.
- */
-@SysUISingleton
-public class CommunalConditionsMonitor extends Monitor {
-    @Inject
-    public CommunalConditionsMonitor(
-            @Named(COMMUNAL_CONDITIONS) Set<Condition> communalConditions) {
-        super(communalConditions);
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.java b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.java
index f27ae34..e1f1ac4 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.java
+++ b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.java
@@ -34,6 +34,8 @@
 import com.android.systemui.idle.LightSensorEventsDebounceAlgorithm;
 import com.android.systemui.idle.dagger.IdleViewComponent;
 import com.android.systemui.util.condition.Condition;
+import com.android.systemui.util.condition.Monitor;
+import com.android.systemui.util.condition.dagger.MonitorComponent;
 
 import java.util.Collections;
 import java.util.HashSet;
@@ -135,4 +137,14 @@
             return Optional.empty();
         }
     }
+
+    /** */
+    @Provides
+    @Named(COMMUNAL_CONDITIONS)
+    static Monitor provideCommunalSourceMonitor(
+            @Named(COMMUNAL_CONDITIONS) Set<Condition> communalConditions,
+            MonitorComponent.Factory factory) {
+        final MonitorComponent component = factory.create(communalConditions, new HashSet<>());
+        return component.getMonitor();
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java b/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java
index 471a327..b2fe3bb 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java
@@ -37,10 +37,14 @@
 import com.android.systemui.statusbar.phone.DozeParameters;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.tuner.TunerService;
+import com.android.systemui.unfold.FoldAodAnimationController;
+import com.android.systemui.unfold.FoldAodAnimationController.FoldAodAnimationStatus;
+import com.android.systemui.unfold.SysUIUnfoldComponent;
 import com.android.systemui.util.AlarmTimeout;
 import com.android.systemui.util.wakelock.WakeLock;
 
 import java.util.Calendar;
+import java.util.Optional;
 
 import javax.inject.Inject;
 
@@ -49,7 +53,8 @@
  */
 @DozeScope
 public class DozeUi implements DozeMachine.Part, TunerService.Tunable,
-        ConfigurationController.ConfigurationListener, StatusBarStateController.StateListener {
+        ConfigurationController.ConfigurationListener, FoldAodAnimationStatus,
+        StatusBarStateController.StateListener {
     // if enabled, calls dozeTimeTick() whenever the time changes:
     private static final boolean BURN_IN_TESTING_ENABLED = false;
     private static final long TIME_TICK_DEADLINE_MILLIS = 90 * 1000; // 1.5min
@@ -57,6 +62,7 @@
     private final DozeHost mHost;
     private final Handler mHandler;
     private final WakeLock mWakeLock;
+    private final FoldAodAnimationController mFoldAodAnimationController;
     private DozeMachine mMachine;
     private final AlarmTimeout mTimeTicker;
     private final boolean mCanAnimateTransition;
@@ -100,6 +106,7 @@
             DozeParameters params, KeyguardUpdateMonitor keyguardUpdateMonitor,
             DozeLog dozeLog, TunerService tunerService,
             StatusBarStateController statusBarStateController,
+            Optional<SysUIUnfoldComponent> sysUiUnfoldComponent,
             ConfigurationController configurationController) {
         mContext = context;
         mWakeLock = wakeLock;
@@ -118,12 +125,23 @@
 
         mConfigurationController = configurationController;
         mConfigurationController.addCallback(this);
+
+        mFoldAodAnimationController = sysUiUnfoldComponent
+                .map(SysUIUnfoldComponent::getFoldAodAnimationController).orElse(null);
+
+        if (mFoldAodAnimationController != null) {
+            mFoldAodAnimationController.addCallback(this);
+        }
     }
 
     @Override
     public void destroy() {
         mTunerService.removeTunable(this);
         mConfigurationController.removeCallback(this);
+
+        if (mFoldAodAnimationController != null) {
+            mFoldAodAnimationController.removeCallback(this);
+        }
     }
 
     @Override
@@ -142,7 +160,8 @@
                     && (mKeyguardShowing || mDozeParameters.shouldControlUnlockedScreenOff())
                     && !mHost.isPowerSaveActive();
             mDozeParameters.setControlScreenOffAnimation(controlScreenOff);
-            mHost.setAnimateScreenOff(controlScreenOff);
+            mHost.setAnimateScreenOff(controlScreenOff
+                    && mDozeParameters.shouldAnimateDozingChange());
         }
     }
 
@@ -299,4 +318,9 @@
     public void onStatePostChange() {
         updateAnimateScreenOff();
     }
+
+    @Override
+    public void onFoldToAodAnimationChanged() {
+        updateAnimateScreenOff();
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerView.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerView.java
new file mode 100644
index 0000000..bc1f772
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerView.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.dreams;
+
+import android.content.Context;
+import android.util.AttributeSet;
+
+import androidx.constraintlayout.widget.ConstraintLayout;
+
+/**
+ * {@link DreamOverlayContainerView} contains a dream overlay and its status bar.
+ */
+public class DreamOverlayContainerView extends ConstraintLayout {
+    public DreamOverlayContainerView(Context context) {
+        this(context, null);
+    }
+
+    public DreamOverlayContainerView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public DreamOverlayContainerView(Context context, AttributeSet attrs, int defStyleAttr) {
+        this(context, attrs, defStyleAttr, 0);
+    }
+
+    public DreamOverlayContainerView(
+            Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
index 8f0ea2f..393f039 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
@@ -29,11 +29,11 @@
 import android.view.WindowManager;
 
 import androidx.annotation.NonNull;
-import androidx.constraintlayout.widget.ConstraintLayout;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.policy.PhoneWindow;
 import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.dreams.dagger.DreamOverlayComponent;
 
 import java.util.concurrent.Executor;
 
@@ -54,57 +54,75 @@
     private final Executor mExecutor;
     // The state controller informs the service of updates to the overlays present.
     private final DreamOverlayStateController mStateController;
+    // The component used to resolve dream overlay dependencies.
+    private final DreamOverlayComponent mDreamOverlayComponent;
 
-    // The window is populated once the dream informs the service it has begun dreaming.
-    private Window mWindow;
-    private ConstraintLayout mLayout;
+    // The dream overlay's content view, which is located below the status bar (in z-order) and is
+    // the space into which widgets are placed.
+    private ViewGroup mDreamOverlayContentView;
 
     private final DreamOverlayStateController.Callback mOverlayStateCallback =
             new DreamOverlayStateController.Callback() {
-        @Override
-        public void onOverlayChanged() {
-            mExecutor.execute(() -> reloadOverlaysLocked());
-        }
-    };
+                @Override
+                public void onOverlayChanged() {
+                    mExecutor.execute(() -> reloadOverlaysLocked());
+                }
+            };
 
     // The service listens to view changes in order to declare that input occurring in areas outside
     // the overlay should be passed through to the dream underneath.
-    private View.OnAttachStateChangeListener mRootViewAttachListener =
+    private final View.OnAttachStateChangeListener mRootViewAttachListener =
             new View.OnAttachStateChangeListener() {
-        @Override
-        public void onViewAttachedToWindow(View v) {
-            v.getViewTreeObserver()
-                    .addOnComputeInternalInsetsListener(mOnComputeInternalInsetsListener);
-        }
+                @Override
+                public void onViewAttachedToWindow(View v) {
+                    v.getViewTreeObserver()
+                            .addOnComputeInternalInsetsListener(mOnComputeInternalInsetsListener);
+                }
 
-        @Override
-        public void onViewDetachedFromWindow(View v) {
-            v.getViewTreeObserver()
-                    .removeOnComputeInternalInsetsListener(mOnComputeInternalInsetsListener);
-        }
-    };
+                @Override
+                public void onViewDetachedFromWindow(View v) {
+                    v.getViewTreeObserver()
+                            .removeOnComputeInternalInsetsListener(
+                                    mOnComputeInternalInsetsListener);
+                }
+            };
 
     // A hook into the internal inset calculation where we declare the overlays as the only
     // touchable regions.
-    private ViewTreeObserver.OnComputeInternalInsetsListener mOnComputeInternalInsetsListener  =
+    private final ViewTreeObserver.OnComputeInternalInsetsListener
+            mOnComputeInternalInsetsListener =
             new ViewTreeObserver.OnComputeInternalInsetsListener() {
-        @Override
-        public void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo inoutInfo) {
-            if (mLayout != null) {
-                inoutInfo.setTouchableInsets(
-                        ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION);
-                final Region region = new Region();
-                for (int i = 0; i < mLayout.getChildCount(); i++) {
-                    View child = mLayout.getChildAt(i);
-                    final Rect rect = new Rect();
-                    child.getGlobalVisibleRect(rect);
-                    region.op(rect, Region.Op.UNION);
-                }
+                @Override
+                public void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo inoutInfo) {
+                    if (mDreamOverlayContentView != null) {
+                        inoutInfo.setTouchableInsets(
+                                ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION);
+                        final Region region = new Region();
+                        for (int i = 0; i < mDreamOverlayContentView.getChildCount(); i++) {
+                            View child = mDreamOverlayContentView.getChildAt(i);
+                            final Rect rect = new Rect();
+                            child.getGlobalVisibleRect(rect);
+                            region.op(rect, Region.Op.UNION);
+                        }
 
-                inoutInfo.touchableRegion.set(region);
-            }
-        }
-    };
+                        inoutInfo.touchableRegion.set(region);
+                    }
+                }
+            };
+
+    @Inject
+    public DreamOverlayService(
+            Context context,
+            @Main Executor executor,
+            DreamOverlayStateController overlayStateController,
+            DreamOverlayComponent.Factory dreamOverlayComponentFactory) {
+        mContext = context;
+        mExecutor = executor;
+        mStateController = overlayStateController;
+        mDreamOverlayComponent = dreamOverlayComponentFactory.create();
+
+        mStateController.addCallback(mOverlayStateCallback);
+    }
 
     @Override
     public void onStartDream(@NonNull WindowManager.LayoutParams layoutParams) {
@@ -112,10 +130,10 @@
     }
 
     private void reloadOverlaysLocked() {
-        if (mLayout == null) {
+        if (mDreamOverlayContentView == null) {
             return;
         }
-        mLayout.removeAllViews();
+        mDreamOverlayContentView.removeAllViews();
         for (OverlayProvider overlayProvider : mStateController.getOverlays()) {
             addOverlay(overlayProvider);
         }
@@ -129,31 +147,32 @@
      *                     into the dream window.
      */
     private void addOverlayWindowLocked(WindowManager.LayoutParams layoutParams) {
-        mWindow = new PhoneWindow(mContext);
-        mWindow.setAttributes(layoutParams);
-        mWindow.setWindowManager(null, layoutParams.token, "DreamOverlay", true);
+        final PhoneWindow window = new PhoneWindow(mContext);
+        window.setAttributes(layoutParams);
+        window.setWindowManager(null, layoutParams.token, "DreamOverlay", true);
 
-        mWindow.setBackgroundDrawable(new ColorDrawable(0));
+        window.setBackgroundDrawable(new ColorDrawable(0));
 
-        mWindow.clearFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
-        mWindow.addFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE);
-        mWindow.requestFeature(Window.FEATURE_NO_TITLE);
+        window.clearFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
+        window.addFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE);
+        window.requestFeature(Window.FEATURE_NO_TITLE);
         // Hide all insets when the dream is showing
-        mWindow.getDecorView().getWindowInsetsController().hide(WindowInsets.Type.systemBars());
-        mWindow.setDecorFitsSystemWindows(false);
+        window.getDecorView().getWindowInsetsController().hide(WindowInsets.Type.systemBars());
+        window.setDecorFitsSystemWindows(false);
 
         if (DEBUG) {
             Log.d(TAG, "adding overlay window to dream");
         }
 
-        mLayout = new ConstraintLayout(mContext);
-        mLayout.setLayoutParams(new ViewGroup.LayoutParams(
-                ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
-        mLayout.addOnAttachStateChangeListener(mRootViewAttachListener);
-        mWindow.setContentView(mLayout);
+        window.setContentView(mDreamOverlayComponent.getDreamOverlayContainerView());
+
+        mDreamOverlayContentView = mDreamOverlayComponent.getDreamOverlayContentView();
+        mDreamOverlayContentView.addOnAttachStateChangeListener(mRootViewAttachListener);
+
+        mDreamOverlayComponent.getDreamOverlayStatusBarViewController().init();
 
         final WindowManager windowManager = mContext.getSystemService(WindowManager.class);
-        windowManager.addView(mWindow.getDecorView(), mWindow.getAttributes());
+        windowManager.addView(window.getDecorView(), window.getAttributes());
         mExecutor.execute(this::reloadOverlaysLocked);
     }
 
@@ -163,11 +182,11 @@
                 (view, layoutParams) -> {
                     // Always move UI related work to the main thread.
                     mExecutor.execute(() -> {
-                        if (mLayout == null) {
+                        if (mDreamOverlayContentView == null) {
                             return;
                         }
 
-                        mLayout.addView(view, layoutParams);
+                        mDreamOverlayContentView.addView(view, layoutParams);
                     });
                 },
                 () -> {
@@ -178,15 +197,6 @@
                 });
     }
 
-    @Inject
-    public DreamOverlayService(Context context, @Main Executor executor,
-            DreamOverlayStateController overlayStateController) {
-        mContext = context;
-        mExecutor = executor;
-        mStateController = overlayStateController;
-        mStateController.addCallback(mOverlayStateCallback);
-    }
-
     @Override
     public void onDestroy() {
         mStateController.removeCallback(mOverlayStateCallback);
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarView.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarView.java
new file mode 100644
index 0000000..9847ef6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarView.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.dreams;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.ImageView;
+
+import androidx.constraintlayout.widget.ConstraintLayout;
+
+import com.android.internal.util.Preconditions;
+import com.android.systemui.R;
+import com.android.systemui.battery.BatteryMeterView;
+import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback;
+
+/**
+ * {@link DreamOverlayStatusBarView} is the view responsible for displaying the status bar in a
+ * dream. The status bar includes status icons such as battery and wifi.
+ */
+public class DreamOverlayStatusBarView extends ConstraintLayout implements
+        BatteryStateChangeCallback {
+
+    private BatteryMeterView mBatteryView;
+    private ImageView mWifiStatusView;
+
+    public DreamOverlayStatusBarView(Context context) {
+        this(context, null);
+    }
+
+    public DreamOverlayStatusBarView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public DreamOverlayStatusBarView(Context context, AttributeSet attrs, int defStyleAttr) {
+        this(context, attrs, defStyleAttr, 0);
+    }
+
+    public DreamOverlayStatusBarView(
+            Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+
+        mBatteryView = Preconditions.checkNotNull(findViewById(R.id.dream_overlay_battery),
+                "R.id.dream_overlay_battery must not be null");
+        mWifiStatusView = Preconditions.checkNotNull(findViewById(R.id.dream_overlay_wifi_status),
+                "R.id.dream_overlay_wifi_status must not be null");
+
+        mWifiStatusView.setImageDrawable(getContext().getDrawable(R.drawable.ic_signal_wifi_off));
+    }
+
+    /**
+     * Whether to show the battery percent text next to the battery status icons.
+     * @param show True if the battery percent text should be shown.
+     */
+    void showBatteryPercentText(boolean show) {
+        mBatteryView.setForceShowPercent(show);
+    }
+
+    /**
+     * Whether to show the wifi status icon.
+     * @param show True if the wifi status icon should be shown.
+     */
+    void showWifiStatus(boolean show) {
+        // Only show the wifi status icon when wifi isn't available.
+        mWifiStatusView.setVisibility(show ? View.VISIBLE : View.GONE);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java
new file mode 100644
index 0000000..5674b9f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.dreams;
+
+import android.annotation.IntDef;
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.ConnectivityManager.NetworkCallback;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.NetworkRequest;
+
+import com.android.systemui.battery.BatteryMeterViewController;
+import com.android.systemui.dreams.dagger.DreamOverlayComponent;
+import com.android.systemui.dreams.dagger.DreamOverlayModule;
+import com.android.systemui.statusbar.policy.BatteryController;
+import com.android.systemui.util.ViewController;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+
+/**
+ * View controller for {@link DreamOverlayStatusBarView}.
+ */
+@DreamOverlayComponent.DreamOverlayScope
+public class DreamOverlayStatusBarViewController extends ViewController<DreamOverlayStatusBarView> {
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = { "WIFI_STATUS_" }, value = {
+            WIFI_STATUS_UNKNOWN,
+            WIFI_STATUS_UNAVAILABLE,
+            WIFI_STATUS_AVAILABLE
+    })
+    private @interface WifiStatus {}
+    private static final int WIFI_STATUS_UNKNOWN = 0;
+    private static final int WIFI_STATUS_UNAVAILABLE = 1;
+    private static final int WIFI_STATUS_AVAILABLE = 2;
+
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = { "BATTERY_STATUS_" }, value = {
+            BATTERY_STATUS_UNKNOWN,
+            BATTERY_STATUS_NOT_CHARGING,
+            BATTERY_STATUS_CHARGING
+    })
+    private @interface BatteryStatus {}
+    private static final int BATTERY_STATUS_UNKNOWN = 0;
+    private static final int BATTERY_STATUS_NOT_CHARGING = 1;
+    private static final int BATTERY_STATUS_CHARGING = 2;
+
+    private final BatteryController mBatteryController;
+    private final BatteryMeterViewController mBatteryMeterViewController;
+    private final ConnectivityManager mConnectivityManager;
+    private final boolean mShowPercentAvailable;
+
+    private final NetworkRequest mNetworkRequest = new NetworkRequest.Builder()
+            .clearCapabilities()
+            .addTransportType(NetworkCapabilities.TRANSPORT_WIFI).build();
+
+    private final NetworkCallback mNetworkCallback = new NetworkCallback() {
+        @Override
+        public void onCapabilitiesChanged(
+                Network network, NetworkCapabilities networkCapabilities) {
+            onWifiAvailabilityChanged(
+                    networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI));
+        }
+
+        @Override
+        public void onAvailable(Network network) {
+            onWifiAvailabilityChanged(true);
+        }
+
+        @Override
+        public void onLost(Network network) {
+            onWifiAvailabilityChanged(false);
+        }
+    };
+
+    private final BatteryController.BatteryStateChangeCallback mBatteryStateChangeCallback =
+            new BatteryController.BatteryStateChangeCallback() {
+                @Override
+                public void onBatteryLevelChanged(int level, boolean pluggedIn, boolean charging) {
+                    DreamOverlayStatusBarViewController.this.onBatteryLevelChanged(charging);
+                }
+            };
+
+    private @WifiStatus int mWifiStatus = WIFI_STATUS_UNKNOWN;
+    private @BatteryStatus int mBatteryStatus = BATTERY_STATUS_UNKNOWN;
+
+    @Inject
+    public DreamOverlayStatusBarViewController(
+            Context context,
+            DreamOverlayStatusBarView view,
+            BatteryController batteryController,
+            @Named(DreamOverlayModule.DREAM_OVERLAY_BATTERY_CONTROLLER)
+                    BatteryMeterViewController batteryMeterViewController,
+            ConnectivityManager connectivityManager) {
+        super(view);
+        mBatteryController = batteryController;
+        mBatteryMeterViewController = batteryMeterViewController;
+        mConnectivityManager = connectivityManager;
+
+        mShowPercentAvailable = context.getResources().getBoolean(
+                com.android.internal.R.bool.config_battery_percentage_setting_available);
+    }
+
+    @Override
+    protected void onInit() {
+        super.onInit();
+        mBatteryMeterViewController.init();
+    }
+
+    @Override
+    protected void onViewAttached() {
+        mBatteryController.addCallback(mBatteryStateChangeCallback);
+        mConnectivityManager.registerNetworkCallback(mNetworkRequest, mNetworkCallback);
+
+        NetworkCapabilities capabilities =
+                mConnectivityManager.getNetworkCapabilities(
+                        mConnectivityManager.getActiveNetwork());
+        onWifiAvailabilityChanged(
+                capabilities != null
+                        && capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI));
+    }
+
+    @Override
+    protected void onViewDetached() {
+        mBatteryController.removeCallback(mBatteryStateChangeCallback);
+        mConnectivityManager.unregisterNetworkCallback(mNetworkCallback);
+    }
+
+    /**
+     * Wifi availability has changed. Update the wifi status icon as appropriate.
+     * @param available Whether wifi is available.
+     */
+    private void onWifiAvailabilityChanged(boolean available) {
+        final int newWifiStatus = available ? WIFI_STATUS_AVAILABLE : WIFI_STATUS_UNAVAILABLE;
+        if (mWifiStatus != newWifiStatus) {
+            mWifiStatus = newWifiStatus;
+            mView.showWifiStatus(mWifiStatus == WIFI_STATUS_UNAVAILABLE);
+        }
+    }
+
+    /**
+     * The battery level has changed. Update the battery status icon as appropriate.
+     * @param charging Whether the battery is currently charging.
+     */
+    private void onBatteryLevelChanged(boolean charging) {
+        final int newBatteryStatus =
+                charging ? BATTERY_STATUS_CHARGING : BATTERY_STATUS_NOT_CHARGING;
+        if (mBatteryStatus != newBatteryStatus) {
+            mBatteryStatus = newBatteryStatus;
+            mView.showBatteryPercentText(
+                    mBatteryStatus == BATTERY_STATUS_CHARGING && mShowPercentAvailable);
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java
index 7bf2361..ff5beb5 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java
@@ -21,8 +21,6 @@
 /**
  * Dagger Module providing Communal-related functionality.
  */
-@Module(subcomponents = {
-        AppWidgetOverlayComponent.class,
-})
+@Module(subcomponents = {AppWidgetOverlayComponent.class, DreamOverlayComponent.class})
 public interface DreamModule {
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayComponent.java b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayComponent.java
new file mode 100644
index 0000000..a3a446a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayComponent.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.dreams.dagger;
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import android.view.ViewGroup;
+
+import com.android.systemui.dreams.DreamOverlayContainerView;
+import com.android.systemui.dreams.DreamOverlayStatusBarViewController;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+
+import javax.inject.Scope;
+
+import dagger.Subcomponent;
+
+/**
+ * Dagger subcomponent for {@link DreamOverlayModule}.
+ */
+@Subcomponent(modules = {DreamOverlayModule.class})
+@DreamOverlayComponent.DreamOverlayScope
+public interface DreamOverlayComponent {
+    /** Simple factory for {@link DreamOverlayComponent}. */
+    @Subcomponent.Factory
+    interface Factory {
+        DreamOverlayComponent create();
+    }
+
+    /** Scope annotation for singleton items within the {@link DreamOverlayComponent}. */
+    @Documented
+    @Retention(RUNTIME)
+    @Scope
+    @interface DreamOverlayScope {}
+
+    /** Builds a {@link DreamOverlayContainerView} */
+    @DreamOverlayScope
+    DreamOverlayContainerView getDreamOverlayContainerView();
+
+    /** Builds a content view for dream overlays */
+    @DreamOverlayScope
+    ViewGroup getDreamOverlayContentView();
+
+    /** Builds a {@link DreamOverlayStatusBarViewController}. */
+    @DreamOverlayScope
+    DreamOverlayStatusBarViewController getDreamOverlayStatusBarViewController();
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java
new file mode 100644
index 0000000..d0a8fad
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.dreams.dagger;
+
+import android.content.ContentResolver;
+import android.os.Handler;
+import android.view.LayoutInflater;
+import android.view.ViewGroup;
+
+import com.android.internal.util.Preconditions;
+import com.android.systemui.R;
+import com.android.systemui.battery.BatteryMeterView;
+import com.android.systemui.battery.BatteryMeterViewController;
+import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.dreams.DreamOverlayContainerView;
+import com.android.systemui.dreams.DreamOverlayStatusBarView;
+import com.android.systemui.statusbar.policy.BatteryController;
+import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.systemui.tuner.TunerService;
+
+import javax.inject.Named;
+
+import dagger.Module;
+import dagger.Provides;
+
+/** Dagger module for {@link DreamOverlayComponent}. */
+@Module
+public abstract class DreamOverlayModule {
+    private static final String DREAM_OVERLAY_BATTERY_VIEW = "dream_overlay_battery_view";
+    public static final String DREAM_OVERLAY_BATTERY_CONTROLLER =
+            "dream_overlay_battery_controller";
+
+    /** */
+    @Provides
+    @DreamOverlayComponent.DreamOverlayScope
+    public static DreamOverlayContainerView providesDreamOverlayContainerView(
+            LayoutInflater layoutInflater) {
+        return Preconditions.checkNotNull((DreamOverlayContainerView)
+                layoutInflater.inflate(R.layout.dream_overlay_container, null),
+                "R.layout.dream_layout_container could not be properly inflated");
+    }
+
+    /** */
+    @Provides
+    @DreamOverlayComponent.DreamOverlayScope
+    public static ViewGroup providesDreamOverlayContentView(DreamOverlayContainerView view) {
+        return Preconditions.checkNotNull(view.findViewById(R.id.dream_overlay_content),
+                "R.id.dream_overlay_content must not be null");
+    }
+
+    /** */
+    @Provides
+    @DreamOverlayComponent.DreamOverlayScope
+    public static DreamOverlayStatusBarView providesDreamOverlayStatusBarView(
+            DreamOverlayContainerView view) {
+        return Preconditions.checkNotNull(view.findViewById(R.id.dream_overlay_status_bar),
+                "R.id.status_bar must not be null");
+    }
+
+    /** */
+    @Provides
+    @DreamOverlayComponent.DreamOverlayScope
+    @Named(DREAM_OVERLAY_BATTERY_VIEW)
+    static BatteryMeterView providesBatteryMeterView(DreamOverlayContainerView view) {
+        return Preconditions.checkNotNull(view.findViewById(R.id.dream_overlay_battery),
+                "R.id.battery must not be null");
+    }
+
+    /** */
+    @Provides
+    @DreamOverlayComponent.DreamOverlayScope
+    @Named(DREAM_OVERLAY_BATTERY_CONTROLLER)
+    static BatteryMeterViewController providesBatteryMeterViewController(
+            @Named(DREAM_OVERLAY_BATTERY_VIEW) BatteryMeterView batteryMeterView,
+            ConfigurationController configurationController,
+            TunerService tunerService,
+            BroadcastDispatcher broadcastDispatcher,
+            @Main Handler mainHandler,
+            ContentResolver contentResolver,
+            BatteryController batteryController) {
+        return new BatteryMeterViewController(
+                batteryMeterView,
+                configurationController,
+                tunerService,
+                broadcastDispatcher,
+                mainHandler,
+                contentResolver,
+                batteryController);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index c592431..0c9e315 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -122,6 +122,7 @@
 import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.statusbar.policy.UserSwitcherController;
+import com.android.systemui.unfold.FoldAodAnimationController;
 import com.android.systemui.unfold.SysUIUnfoldComponent;
 import com.android.systemui.unfold.UnfoldLightRevealOverlayAnimation;
 import com.android.systemui.util.DeviceConfigProxy;
@@ -131,7 +132,6 @@
 import java.util.ArrayList;
 import java.util.Optional;
 import java.util.concurrent.Executor;
-import java.util.concurrent.atomic.AtomicInteger;
 
 import dagger.Lazy;
 
@@ -439,7 +439,7 @@
     private boolean mInGestureNavigationMode;
 
     private boolean mWakeAndUnlocking;
-    private IKeyguardDrawnCallback mDrawnCallback;
+    private Runnable mWakeAndUnlockingDrawnCallback;
     private CharSequence mCustomMessage;
 
     /**
@@ -817,7 +817,8 @@
     private DozeParameters mDozeParameters;
 
     private final Optional<UnfoldLightRevealOverlayAnimation> mUnfoldLightRevealAnimation;
-    private final AtomicInteger mPendingDrawnTasks = new AtomicInteger();
+    private final Optional<FoldAodAnimationController> mFoldAodAnimationController;
+    private final PendingDrawnTasksContainer mPendingDrawnTasks = new PendingDrawnTasksContainer();
 
     private final KeyguardStateController mKeyguardStateController;
     private final Lazy<KeyguardUnlockAnimationController> mKeyguardUnlockAnimationControllerLazy;
@@ -877,8 +878,12 @@
                     mInGestureNavigationMode = QuickStepContract.isGesturalMode(mode);
                 }));
         mDozeParameters = dozeParameters;
-        mUnfoldLightRevealAnimation = unfoldComponent.map(
-                c -> c.getUnfoldLightRevealOverlayAnimation());
+
+        mUnfoldLightRevealAnimation = unfoldComponent
+                .map(SysUIUnfoldComponent::getUnfoldLightRevealOverlayAnimation);
+        mFoldAodAnimationController = unfoldComponent
+                .map(SysUIUnfoldComponent::getFoldAodAnimationController);
+
         mStatusBarStateController = statusBarStateController;
         statusBarStateController.addCallback(this);
 
@@ -1069,7 +1074,7 @@
             mDeviceInteractive = false;
             mGoingToSleep = false;
             mWakeAndUnlocking = false;
-            mAnimatingScreenOff = mDozeParameters.shouldControlUnlockedScreenOff();
+            mAnimatingScreenOff = mDozeParameters.shouldAnimateDozingChange();
 
             resetKeyguardDonePendingLocked();
             mHideAnimationRun = false;
@@ -2230,14 +2235,14 @@
             IRemoteAnimationRunner runner = mKeyguardExitAnimationRunner;
             mKeyguardExitAnimationRunner = null;
 
-            if (mWakeAndUnlocking && mDrawnCallback != null) {
+            if (mWakeAndUnlocking && mWakeAndUnlockingDrawnCallback != null) {
 
                 // Hack level over 9000: To speed up wake-and-unlock sequence, force it to report
                 // the next draw from here so we don't have to wait for window manager to signal
                 // this to our ViewRootImpl.
                 mKeyguardViewControllerLazy.get().getViewRootImpl().setReportNextDraw();
-                notifyDrawn(mDrawnCallback);
-                mDrawnCallback = null;
+                mWakeAndUnlockingDrawnCallback.run();
+                mWakeAndUnlockingDrawnCallback = null;
             }
 
             LatencyTracker.getInstance(mContext)
@@ -2573,31 +2578,27 @@
         synchronized (KeyguardViewMediator.this) {
             if (DEBUG) Log.d(TAG, "handleNotifyScreenTurningOn");
 
-            if (mUnfoldLightRevealAnimation.isPresent()) {
-                mPendingDrawnTasks.set(2); // unfold overlay and keyguard drawn
+            mPendingDrawnTasks.reset();
 
+            if (mUnfoldLightRevealAnimation.isPresent()) {
                 mUnfoldLightRevealAnimation.get()
-                        .onScreenTurningOn(() -> {
-                            if (mPendingDrawnTasks.decrementAndGet() == 0) {
-                                try {
-                                    callback.onDrawn();
-                                } catch (RemoteException e) {
-                                    Slog.w(TAG, "Exception calling onDrawn():", e);
-                                }
-                            }
-                        });
-            } else {
-                mPendingDrawnTasks.set(1); // only keyguard drawn
+                        .onScreenTurningOn(mPendingDrawnTasks.registerTask("unfold-reveal"));
+            }
+
+            if (mFoldAodAnimationController.isPresent()) {
+                mFoldAodAnimationController.get()
+                        .onScreenTurningOn(mPendingDrawnTasks.registerTask("fold-to-aod"));
             }
 
             mKeyguardViewControllerLazy.get().onScreenTurningOn();
             if (callback != null) {
                 if (mWakeAndUnlocking) {
-                    mDrawnCallback = callback;
-                } else {
-                    notifyDrawn(callback);
+                    mWakeAndUnlockingDrawnCallback =
+                            mPendingDrawnTasks.registerTask("wake-and-unlocking");
                 }
             }
+
+            mPendingDrawnTasks.onTasksComplete(() -> notifyDrawn(callback));
         }
         Trace.endSection();
     }
@@ -2606,6 +2607,8 @@
         Trace.beginSection("KeyguardViewMediator#handleNotifyScreenTurnedOn");
         synchronized (this) {
             if (DEBUG) Log.d(TAG, "handleNotifyScreenTurnedOn");
+
+            mPendingDrawnTasks.reset();
             mKeyguardViewControllerLazy.get().onScreenTurnedOn();
         }
         Trace.endSection();
@@ -2614,18 +2617,18 @@
     private void handleNotifyScreenTurnedOff() {
         synchronized (this) {
             if (DEBUG) Log.d(TAG, "handleNotifyScreenTurnedOff");
-            mDrawnCallback = null;
+            mWakeAndUnlockingDrawnCallback = null;
         }
     }
 
     private void notifyDrawn(final IKeyguardDrawnCallback callback) {
         Trace.beginSection("KeyguardViewMediator#notifyDrawn");
-        if (mPendingDrawnTasks.decrementAndGet() == 0) {
-            try {
+        try {
+            if (callback != null) {
                 callback.onDrawn();
-            } catch (RemoteException e) {
-                Slog.w(TAG, "Exception calling onDrawn():", e);
             }
+        } catch (RemoteException e) {
+            Slog.w(TAG, "Exception calling onDrawn():", e);
         }
         Trace.endSection();
     }
@@ -2784,9 +2787,9 @@
         pw.print("  mHideAnimationRun: "); pw.println(mHideAnimationRun);
         pw.print("  mPendingReset: "); pw.println(mPendingReset);
         pw.print("  mPendingLock: "); pw.println(mPendingLock);
-        pw.print("  mPendingDrawnTasks: "); pw.println(mPendingDrawnTasks.get());
+        pw.print("  mPendingDrawnTasks: "); pw.println(mPendingDrawnTasks.getPendingCount());
         pw.print("  mWakeAndUnlocking: "); pw.println(mWakeAndUnlocking);
-        pw.print("  mDrawnCallback: "); pw.println(mDrawnCallback);
+        pw.print("  mDrawnCallback: "); pw.println(mWakeAndUnlockingDrawnCallback);
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/PendingDrawnTasksContainer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/PendingDrawnTasksContainer.kt
new file mode 100644
index 0000000..bccd106
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/PendingDrawnTasksContainer.kt
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard
+
+import android.os.Trace
+import java.util.concurrent.atomic.AtomicInteger
+import java.util.concurrent.atomic.AtomicReference
+
+/**
+ * Allows to wait for multiple callbacks and notify when the last one is executed
+ */
+class PendingDrawnTasksContainer {
+
+    private lateinit var pendingDrawnTasksCount: AtomicInteger
+    private var completionCallback: AtomicReference<Runnable> = AtomicReference()
+
+    /**
+     * Registers a task that we should wait for
+     * @return a runnable that should be invoked when the task is finished
+     */
+    fun registerTask(name: String): Runnable {
+        pendingDrawnTasksCount.incrementAndGet()
+
+        if (ENABLE_TRACE) {
+            Trace.beginAsyncSection("PendingDrawnTasksContainer#$name", 0)
+        }
+
+        return Runnable {
+            if (pendingDrawnTasksCount.decrementAndGet() == 0) {
+                val onComplete = completionCallback.getAndSet(null)
+                onComplete?.run()
+
+                if (ENABLE_TRACE) {
+                    Trace.endAsyncSection("PendingDrawnTasksContainer#$name", 0)
+                }
+            }
+        }
+    }
+
+    /**
+     * Clears state and initializes the container
+     */
+    fun reset() {
+        // Create new objects in case if there are pending callbacks from the previous invocations
+        completionCallback = AtomicReference()
+        pendingDrawnTasksCount = AtomicInteger(0)
+    }
+
+    /**
+     * Starts waiting for all tasks to be completed
+     * When all registered tasks complete it will invoke the [onComplete] callback
+     */
+    fun onTasksComplete(onComplete: Runnable) {
+        completionCallback.set(onComplete)
+
+        if (pendingDrawnTasksCount.get() == 0) {
+            val currentOnComplete = completionCallback.getAndSet(null)
+            currentOnComplete?.run()
+        }
+    }
+
+    /**
+     * Returns current pending tasks count
+     */
+    fun getPendingCount(): Int = pendingDrawnTasksCount.get()
+}
+
+private const val ENABLE_TRACE = false
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
index 5bd02cc..10efec3 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
@@ -48,6 +48,7 @@
 import androidx.annotation.Nullable;
 import androidx.annotation.WorkerThread;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.systemui.Dependency;
@@ -562,7 +563,8 @@
             return this;
         }
 
-        CustomTile build() {
+        @VisibleForTesting
+        public CustomTile build() {
             if (mUserContext == null) {
                 throw new NullPointerException("UserContext cannot be null");
             }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
index ac95bf5..c2a9e3a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
@@ -169,7 +169,7 @@
         mFgsManagerTileProvider = fgsManagerTileProvider;
     }
 
-    public QSTile createTile(String tileSpec) {
+    public final QSTile createTile(String tileSpec) {
         QSTileImpl tile = createTileInternal(tileSpec);
         if (tile != null) {
             tile.initialize();
@@ -178,7 +178,7 @@
         return tile;
     }
 
-    private QSTileImpl createTileInternal(String tileSpec) {
+    protected QSTileImpl createTileInternal(String tileSpec) {
         // Stock tiles.
         switch (tileSpec) {
             case "wifi":
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/FgsManagerTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/FgsManagerTile.kt
index 75cf4d1..939a297 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/FgsManagerTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/FgsManagerTile.kt
@@ -54,7 +54,7 @@
     qsLogger: QSLogger?,
     private val fgsManagerDialogFactory: FgsManagerDialogFactory,
     private val runningFgsController: RunningFgsController
-) : QSTileImpl<QSTile.State?>(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
+) : QSTileImpl<QSTile.State>(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
         statusBarStateController, activityStarter, qsLogger), RunningFgsController.Callback {
 
     override fun handleInitialize() {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
index 4908d4f..dd742b8 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
@@ -90,6 +90,7 @@
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Set;
 import java.util.concurrent.Executor;
 import java.util.concurrent.atomic.AtomicReference;
@@ -485,6 +486,11 @@
 
     private Map<Integer, CharSequence> getUniqueSubscriptionDisplayNames(Context context) {
         class DisplayInfo {
+            DisplayInfo(SubscriptionInfo subscriptionInfo, CharSequence originalName) {
+                this.subscriptionInfo = subscriptionInfo;
+                this.originalName = originalName;
+            }
+
             public SubscriptionInfo subscriptionInfo;
             public CharSequence originalName;
             public CharSequence uniqueName;
@@ -498,12 +504,7 @@
                             // Filter out null values.
                             return (i != null && i.getDisplayName() != null);
                         })
-                        .map(i -> {
-                            DisplayInfo info = new DisplayInfo();
-                            info.subscriptionInfo = i;
-                            info.originalName = i.getDisplayName().toString().trim();
-                            return info;
-                        });
+                        .map(i -> new DisplayInfo(i, i.getDisplayName().toString().trim()));
 
         // A Unique set of display names
         Set<CharSequence> uniqueNames = new HashSet<>();
@@ -582,7 +583,7 @@
             return "";
         }
 
-        int resId = mapIconSets(config).get(iconKey).dataContentDescription;
+        int resId = Objects.requireNonNull(mapIconSets(config).get(iconKey)).dataContentDescription;
         if (isCarrierNetworkActive()) {
             SignalIcon.MobileIconGroup carrierMergedWifiIconGroup =
                     TelephonyIcons.CARRIER_MERGED_WIFI;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/BypassHeadsUpNotifier.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/BypassHeadsUpNotifier.kt
index f8d6c6d..b2e15f4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/BypassHeadsUpNotifier.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/BypassHeadsUpNotifier.kt
@@ -25,8 +25,8 @@
 import com.android.systemui.statusbar.NotificationLockscreenUserManager
 import com.android.systemui.statusbar.NotificationMediaManager
 import com.android.systemui.statusbar.StatusBarState
-import com.android.systemui.statusbar.notification.NotificationEntryManager
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection
 import com.android.systemui.statusbar.phone.HeadsUpManagerPhone
 import com.android.systemui.statusbar.phone.KeyguardBypassController
 import com.android.systemui.tuner.TunerService
@@ -44,7 +44,7 @@
     private val headsUpManager: HeadsUpManagerPhone,
     private val notificationLockscreenUserManager: NotificationLockscreenUserManager,
     private val mediaManager: NotificationMediaManager,
-    private val entryManager: NotificationEntryManager,
+    private val commonNotifCollection: CommonNotifCollection,
     tunerService: TunerService
 ) : StatusBarStateController.StateListener, NotificationMediaManager.MediaListener {
 
@@ -77,8 +77,7 @@
 
     override fun onPrimaryMetadataOrStateChanged(metadata: MediaMetadata?, state: Int) {
         val previous = currentMediaEntry
-        var newEntry = entryManager
-                .getActiveNotificationUnfiltered(mediaManager.mediaNotificationKey)
+        var newEntry = commonNotifCollection.getEntry(mediaManager.mediaNotificationKey)
         if (!NotificationMediaManager.isPlayingState(state)) {
             newEntry = null
         }
@@ -112,7 +111,7 @@
             // filter notifications invisible on Keyguard
             return false
         }
-        if (entryManager.getActiveNotificationUnfiltered(entry.key) != null) {
+        if (commonNotifCollection.getEntry(entry.key) != null) {
             // filter notifications not the active list currently
             return false
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index e1dbf4e..518788b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -669,6 +669,7 @@
 
     public void setIsRemoteInputActive(boolean isActive) {
         mIsRemoteInputActive = isActive;
+        updateFooter();
     }
 
     @VisibleForTesting
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
index e3a4bf0..2dc9276 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
@@ -51,6 +51,7 @@
     public static final int ANIMATION_DURATION_CLOSE_REMOTE_INPUT = 150;
     public static final int ANIMATION_DURATION_HEADS_UP_APPEAR = 400;
     public static final int ANIMATION_DURATION_HEADS_UP_DISAPPEAR = 400;
+    public static final int ANIMATION_DURATION_FOLD_TO_AOD = 600;
     public static final int ANIMATION_DURATION_PULSE_APPEAR =
             KeyguardSliceView.DEFAULT_ANIM_DURATION;
     public static final int ANIMATION_DURATION_BLOCKING_HELPER_FADE = 240;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
index 4b8b580..d87a024 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
@@ -243,6 +243,10 @@
         return mScreenOffAnimationController.shouldShowLightRevealScrim();
     }
 
+    public boolean shouldAnimateDozingChange() {
+        return mScreenOffAnimationController.shouldAnimateDozingChange();
+    }
+
     /**
      * Whether we're capable of controlling the screen off animation if we want to. This isn't
      * possible if AOD isn't even enabled or if the flag is disabled.
@@ -324,6 +328,7 @@
         for (Callback callback : mCallbacks) {
             callback.onAlwaysOnChange();
         }
+        mScreenOffAnimationController.onAlwaysOnChanged(getAlwaysOn());
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
index 097c0d1..3b7063e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
@@ -26,6 +26,7 @@
 import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE;
 import static com.android.keyguard.KeyguardClockSwitch.LARGE;
 import static com.android.keyguard.KeyguardClockSwitch.SMALL;
+import static com.android.systemui.animation.Interpolators.EMPHASIZED_DECELERATE;
 import static com.android.systemui.classifier.Classifier.QS_COLLAPSE;
 import static com.android.systemui.classifier.Classifier.QUICK_SETTINGS;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED;
@@ -34,6 +35,7 @@
 import static com.android.systemui.statusbar.StatusBarState.SHADE;
 import static com.android.systemui.statusbar.StatusBarState.SHADE_LOCKED;
 import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.ROWS_ALL;
+import static com.android.systemui.statusbar.notification.stack.StackStateAnimator.ANIMATION_DURATION_FOLD_TO_AOD;
 import static com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManagerKt.STATE_CLOSED;
 import static com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManagerKt.STATE_OPEN;
 import static com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManagerKt.STATE_OPENING;
@@ -1423,11 +1425,12 @@
                 .getVisibleNotificationCount() != 0 || mMediaDataManager.hasActiveMedia();
         boolean splitShadeWithActiveMedia =
                 mShouldUseSplitNotificationShade && mMediaDataManager.hasActiveMedia();
+        boolean shouldAnimateClockChange = mScreenOffAnimationController.shouldAnimateClockChange();
         if ((hasVisibleNotifications && !mShouldUseSplitNotificationShade)
                 || (splitShadeWithActiveMedia && !mDozing)) {
-            mKeyguardStatusViewController.displayClock(SMALL);
+            mKeyguardStatusViewController.displayClock(SMALL, shouldAnimateClockChange);
         } else {
-            mKeyguardStatusViewController.displayClock(LARGE);
+            mKeyguardStatusViewController.displayClock(LARGE, shouldAnimateClockChange);
         }
         updateKeyguardStatusViewAlignment(true /* animate */);
         int userIconHeight = mKeyguardQsUserSwitchController != null
@@ -1463,7 +1466,7 @@
                 mKeyguardStatusViewController.isClockTopAligned());
         mClockPositionAlgorithm.run(mClockPositionResult);
         boolean animate = mNotificationStackScrollLayoutController.isAddOrRemoveAnimationPending();
-        boolean animateClock = animate || mAnimateNextPositionUpdate;
+        boolean animateClock = (animate || mAnimateNextPositionUpdate) && shouldAnimateClockChange;
         mKeyguardStatusViewController.updatePosition(
                 mClockPositionResult.clockX, mClockPositionResult.clockY,
                 mClockPositionResult.clockScale, animateClock);
@@ -3821,6 +3824,45 @@
         }
     }
 
+    /**
+     * Updates the views to the initial state for the fold to AOD animation
+     */
+    public void prepareFoldToAodAnimation() {
+        // Force show AOD UI even if we are not locked
+        showAodUi();
+
+        // Move the content of the AOD all the way to the left
+        // so we can animate to the initial position
+        final int translationAmount = mView.getResources().getDimensionPixelSize(
+                R.dimen.below_clock_padding_start);
+        mView.setTranslationX(-translationAmount);
+        mView.setAlpha(0);
+    }
+
+    /**
+     * Starts fold to AOD animation
+     */
+    public void startFoldToAodAnimation(Runnable endAction) {
+        mView.animate()
+            .translationX(0)
+            .alpha(1f)
+            .setDuration(ANIMATION_DURATION_FOLD_TO_AOD)
+            .setInterpolator(EMPHASIZED_DECELERATE)
+            .setListener(new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationCancel(Animator animation) {
+                    endAction.run();
+                }
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    endAction.run();
+                }
+            })
+            .start();
+
+        mKeyguardStatusViewController.animateFoldToAod();
+    }
+
     /** */
     public void setImportantForAccessibility(int mode) {
         mView.setImportantForAccessibility(mode);
@@ -3935,6 +3977,10 @@
         mView.setAlpha(alpha);
     }
 
+    public void resetTranslation() {
+        mView.setTranslationX(0f);
+    }
+
     public ViewPropertyAnimator fadeOut(long startDelayMs, long durationMs, Runnable endAction) {
         return mView.animate().alpha(0).setStartDelay(startDelayMs).setDuration(
                 durationMs).setInterpolator(Interpolators.ALPHA_OUT).withLayer().withEndAction(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
index f67d181..1e71ceb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
@@ -38,7 +38,6 @@
 import com.android.systemui.R;
 import com.android.systemui.plugins.DarkIconDispatcher;
 import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
-import com.android.systemui.statusbar.window.StatusBarWindowView;
 import com.android.systemui.util.leak.RotationUtils;
 
 import java.util.Objects;
@@ -76,6 +75,7 @@
 
     @Override
     public void onFinishInflate() {
+        super.onFinishInflate();
         mBattery = findViewById(R.id.battery);
         mClock = findViewById(R.id.clock);
         mCutoutSpace = findViewById(R.id.cutout_space_view);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScreenOffAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScreenOffAnimationController.kt
index 497e7d7..e806ca0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScreenOffAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScreenOffAnimationController.kt
@@ -19,16 +19,23 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.WakefulnessLifecycle
 import com.android.systemui.statusbar.LightRevealScrim
+import com.android.systemui.unfold.FoldAodAnimationController
+import com.android.systemui.unfold.SysUIUnfoldComponent
+import java.util.Optional
 import javax.inject.Inject
 
 @SysUISingleton
 class ScreenOffAnimationController @Inject constructor(
+    sysUiUnfoldComponent: Optional<SysUIUnfoldComponent>,
     unlockedScreenOffAnimation: UnlockedScreenOffAnimationController,
     private val wakefulnessLifecycle: WakefulnessLifecycle,
 ) : WakefulnessLifecycle.Observer {
 
-    // TODO(b/202844967) add fold to aod animation here
-    private val animations: List<ScreenOffAnimation> = listOf(unlockedScreenOffAnimation)
+    private val foldToAodAnimation: FoldAodAnimationController? = sysUiUnfoldComponent
+        .orElse(null)?.getFoldAodAnimationController()
+
+    private val animations: List<ScreenOffAnimation> =
+        listOfNotNull(foldToAodAnimation, unlockedScreenOffAnimation)
 
     fun initialize(statusBar: StatusBar, lightRevealScrim: LightRevealScrim) {
         animations.forEach { it.initialize(statusBar, lightRevealScrim) }
@@ -43,6 +50,19 @@
     }
 
     /**
+     * Called when opaqueness of the light reveal scrim has change
+     * When [isOpaque] is true then scrim is visible and covers the screen
+     */
+    fun onScrimOpaqueChanged(isOpaque: Boolean) =
+        animations.forEach { it.onScrimOpaqueChanged(isOpaque) }
+
+    /**
+     * Called when always on display setting changed
+     */
+    fun onAlwaysOnChanged(alwaysOn: Boolean) =
+        animations.forEach { it.onAlwaysOnChanged(alwaysOn) }
+
+    /**
      * If returns true we are taking over the screen off animation from display manager to SysUI.
      * We can play our custom animation instead of default fade out animation.
      */
@@ -103,6 +123,12 @@
         animations.any { it.isAnimationPlaying() }
 
     /**
+     * Return true to ignore requests to hide keyguard
+     */
+    fun isKeyguardHideDelayed(): Boolean =
+        animations.any { it.isKeyguardHideDelayed() }
+
+    /**
      * Return true to make the StatusBar expanded so we can animate [LightRevealScrim]
      */
     fun shouldShowLightRevealScrim(): Boolean =
@@ -145,6 +171,19 @@
      */
     fun shouldAnimateAodIcons(): Boolean =
         animations.all { it.shouldAnimateAodIcons() }
+
+    /**
+     * Return true to animate doze state change, if returns false dozing will be applied without
+     * animation (sends only 0.0f or 1.0f dozing progress)
+     */
+    fun shouldAnimateDozingChange(): Boolean =
+        animations.all { it.shouldAnimateDozingChange() }
+
+    /**
+     * Return true to animate large <-> small clock transition
+     */
+    fun shouldAnimateClockChange(): Boolean =
+        animations.all { it.shouldAnimateClockChange() }
 }
 
 interface ScreenOffAnimation {
@@ -158,11 +197,17 @@
     fun shouldPlayAnimation(): Boolean = false
     fun isAnimationPlaying(): Boolean = false
 
+    fun onScrimOpaqueChanged(isOpaque: Boolean) {}
+    fun onAlwaysOnChanged(alwaysOn: Boolean) {}
+
     fun shouldAnimateInKeyguard(): Boolean = false
     fun animateInKeyguard(keyguardView: View, after: Runnable) = after.run()
 
+    fun isKeyguardHideDelayed(): Boolean = false
     fun shouldHideScrimOnWakeUp(): Boolean = false
     fun overrideNotificationsDozeAmount(): Boolean = false
     fun shouldShowAodIconsWhenShade(): Boolean = false
     fun shouldAnimateAodIcons(): Boolean = true
+    fun shouldAnimateDozingChange(): Boolean = true
+    fun shouldAnimateClockChange(): Boolean = true
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SplitShadeHeaderController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SplitShadeHeaderController.kt
index f6e19bf..8cf7288 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SplitShadeHeaderController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SplitShadeHeaderController.kt
@@ -49,8 +49,9 @@
     }
 
     private val combinedHeaders = featureFlags.isEnabled(Flags.COMBINED_QS_HEADERS)
-    // TODO(b/194178072) Handle RSSI hiding when multi carrier
     private val iconManager: StatusBarIconController.TintedIconManager
+    private val iconContainer: StatusIconContainer
+    private val carrierIconSlots: List<String>
     private val qsCarrierGroupController: QSCarrierGroupController
     private var visible = false
         set(value) {
@@ -117,10 +118,19 @@
         batteryMeterViewController.ignoreTunerUpdates()
         batteryIcon.setPercentShowMode(BatteryMeterView.MODE_ESTIMATE)
 
-        val iconContainer: StatusIconContainer = statusBar.findViewById(R.id.statusIcons)
+        iconContainer = statusBar.findViewById(R.id.statusIcons)
         iconManager = StatusBarIconController.TintedIconManager(iconContainer, featureFlags)
         iconManager.setTint(Utils.getColorAttrDefaultColor(statusBar.context,
                 android.R.attr.textColorPrimary))
+
+        carrierIconSlots = if (featureFlags.isEnabled(Flags.COMBINED_STATUS_BAR_SIGNAL_ICONS)) {
+            listOf(
+                statusBar.context.getString(com.android.internal.R.string.status_bar_no_calling),
+                statusBar.context.getString(com.android.internal.R.string.status_bar_call_strength)
+            )
+        } else {
+            listOf(statusBar.context.getString(com.android.internal.R.string.status_bar_mobile))
+        }
         qsCarrierGroupController = qsCarrierGroupControllerBuilder
                 .setQSCarrierGroup(statusBar.findViewById(R.id.carrier_group))
                 .build()
@@ -185,9 +195,20 @@
     private fun updateListeners() {
         qsCarrierGroupController.setListening(visible)
         if (visible) {
+            updateSingleCarrier(qsCarrierGroupController.isSingleCarrier)
+            qsCarrierGroupController.setOnSingleCarrierChangedListener { updateSingleCarrier(it) }
             statusBarIconController.addIconGroup(iconManager)
         } else {
+            qsCarrierGroupController.setOnSingleCarrierChangedListener(null)
             statusBarIconController.removeIconGroup(iconManager)
         }
     }
+
+    private fun updateSingleCarrier(singleCarrier: Boolean) {
+        if (singleCarrier) {
+            iconContainer.removeIgnoredSlots(carrierIconSlots)
+        } else {
+            iconContainer.addIgnoredSlots(carrierIconSlots)
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index 6c0b717..3312996 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -341,7 +341,7 @@
         mStatusBarWindowState =  state;
         mStatusBarWindowHidden = state == WINDOW_STATE_HIDDEN;
         mStatusBarHideIconsForBouncerManager.setStatusBarWindowHidden(mStatusBarWindowHidden);
-        if (getStatusBarView() != null) {
+        if (mStatusBarView != null) {
             // Should #updateHideIconsForBouncer always be called, regardless of whether we have a
             //   status bar view? If so, we can make #updateHideIconsForBouncer private.
             mStatusBarHideIconsForBouncerManager.updateHideIconsForBouncer(/* animate= */ false);
@@ -1124,23 +1124,18 @@
         // Set up CollapsedStatusBarFragment and PhoneStatusBarView
         StatusBarInitializer initializer = mStatusBarComponent.getStatusBarInitializer();
         initializer.setStatusBarViewUpdatedListener(
-                new StatusBarInitializer.OnStatusBarViewUpdatedListener() {
-                    @Override
-                    public void onStatusBarViewUpdated(
-                            @NonNull PhoneStatusBarView statusBarView,
-                            @NonNull PhoneStatusBarViewController statusBarViewController) {
-                        mStatusBarView = statusBarView;
-                        mPhoneStatusBarViewController = statusBarViewController;
-                        mNotificationShadeWindowViewController.setStatusBarView(mStatusBarView);
-                        // Ensure we re-propagate panel expansion values to the panel controller and
-                        // any listeners it may have, such as PanelBar. This will also ensure we
-                        // re-display the notification panel if necessary (for example, if
-                        // a heads-up notification was being displayed and should continue being
-                        // displayed).
-                        mNotificationPanelViewController.updatePanelExpansionAndVisibility();
-                        setBouncerShowingForStatusBarComponents(mBouncerShowing);
-                        checkBarModes();
-                    }
+                (statusBarView, statusBarViewController) -> {
+                    mStatusBarView = statusBarView;
+                    mPhoneStatusBarViewController = statusBarViewController;
+                    mNotificationShadeWindowViewController.setStatusBarView(mStatusBarView);
+                    // Ensure we re-propagate panel expansion values to the panel controller and
+                    // any listeners it may have, such as PanelBar. This will also ensure we
+                    // re-display the notification panel if necessary (for example, if
+                    // a heads-up notification was being displayed and should continue being
+                    // displayed).
+                    mNotificationPanelViewController.updatePanelExpansionAndVisibility();
+                    setBouncerShowingForStatusBarComponents(mBouncerShowing);
+                    checkBarModes();
                 });
         initializer.initializeStatusBar(mStatusBarComponent);
 
@@ -1199,6 +1194,8 @@
             Runnable updateOpaqueness = () -> {
                 mNotificationShadeWindowController.setLightRevealScrimOpaque(
                         mLightRevealScrim.isScrimOpaque());
+                mScreenOffAnimationController
+                        .onScrimOpaqueChanged(mLightRevealScrim.isScrimOpaque());
             };
             if (opaque) {
                 // Delay making the view opaque for a frame, because it needs some time to render
@@ -1578,10 +1575,6 @@
         Trace.endSection();
     }
 
-    protected PhoneStatusBarView getStatusBarView() {
-        return mStatusBarView;
-    }
-
     public NotificationShadeWindowView getNotificationShadeWindowView() {
         return mNotificationShadeWindowView;
     }
@@ -2915,7 +2908,17 @@
                 showKeyguardImpl();
             }
         } else {
-            return hideKeyguardImpl(force);
+            // During folding a foldable device this might be called as a result of
+            // 'onScreenTurnedOff' call for the inner display.
+            // In this case:
+            //  * When phone is locked on folding: it doesn't make sense to hide keyguard as it
+            //    will be immediately locked again
+            //  * When phone is unlocked: we still don't want to execute hiding of the keyguard
+            //    as the animation could prepare 'fake AOD' interface (without actually
+            //    transitioning to keyguard state) and this might reset the view states
+            if (!mScreenOffAnimationController.isKeyguardHideDelayed()) {
+                return hideKeyguardImpl(force);
+            }
         }
         return false;
     }
@@ -3078,6 +3081,7 @@
         mNotificationPanelViewController.onAffordanceLaunchEnded();
         mNotificationPanelViewController.cancelAnimation();
         mNotificationPanelViewController.setAlpha(1f);
+        mNotificationPanelViewController.resetTranslation();
         mNotificationPanelViewController.resetViewGroupFade();
         updateDozingState();
         updateScrimController();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index b84e6e6..a4aeae9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -111,8 +111,6 @@
     private final NavigationModeController mNavigationModeController;
     private final NotificationShadeWindowController mNotificationShadeWindowController;
     private final KeyguardBouncer.Factory mKeyguardBouncerFactory;
-    private final WakefulnessLifecycle mWakefulnessLifecycle;
-    private final UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController;
     private final KeyguardMessageAreaController.Factory mKeyguardMessageAreaFactory;
     private KeyguardMessageAreaController mKeyguardMessageAreaController;
     private final Lazy<ShadeController> mShadeController;
@@ -244,8 +242,6 @@
             KeyguardStateController keyguardStateController,
             NotificationMediaManager notificationMediaManager,
             KeyguardBouncer.Factory keyguardBouncerFactory,
-            WakefulnessLifecycle wakefulnessLifecycle,
-            UnlockedScreenOffAnimationController unlockedScreenOffAnimationController,
             KeyguardMessageAreaController.Factory keyguardMessageAreaFactory,
             Lazy<ShadeController> shadeController) {
         mContext = context;
@@ -260,8 +256,6 @@
         mStatusBarStateController = sysuiStatusBarStateController;
         mDockManager = dockManager;
         mKeyguardBouncerFactory = keyguardBouncerFactory;
-        mWakefulnessLifecycle = wakefulnessLifecycle;
-        mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController;
         mKeyguardMessageAreaFactory = keyguardMessageAreaFactory;
         mShadeController = shadeController;
     }
@@ -1155,7 +1149,7 @@
 
     @Override
     public ViewRootImpl getViewRootImpl() {
-        return mStatusBar.getStatusBarView().getViewRootImpl();
+        return mNotificationShadeWindowController.getNotificationShadeView().getViewRootImpl();
     }
 
     public void launchPendingWakeupAction() {
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt b/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt
new file mode 100644
index 0000000..fb9df01
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.unfold
+
+import android.os.PowerManager
+import android.provider.Settings
+import com.android.systemui.keyguard.KeyguardViewMediator
+import com.android.systemui.keyguard.ScreenLifecycle
+import com.android.systemui.keyguard.WakefulnessLifecycle
+import com.android.systemui.statusbar.LightRevealScrim
+import com.android.systemui.statusbar.phone.ScreenOffAnimation
+import com.android.systemui.statusbar.phone.StatusBar
+import com.android.systemui.statusbar.policy.CallbackController
+import com.android.systemui.unfold.FoldAodAnimationController.FoldAodAnimationStatus
+import com.android.systemui.util.settings.GlobalSettings
+import dagger.Lazy
+import javax.inject.Inject
+
+/**
+ * Controls folding to AOD animation: when AOD is enabled and foldable device is folded
+ * we play a special AOD animation on the outer screen
+ */
+@SysUIUnfoldScope
+class FoldAodAnimationController @Inject constructor(
+    private val screenLifecycle: ScreenLifecycle,
+    private val keyguardViewMediatorLazy: Lazy<KeyguardViewMediator>,
+    private val wakefulnessLifecycle: WakefulnessLifecycle,
+    private val globalSettings: GlobalSettings
+) : ScreenLifecycle.Observer,
+    CallbackController<FoldAodAnimationStatus>,
+    ScreenOffAnimation,
+    WakefulnessLifecycle.Observer {
+
+    private var alwaysOnEnabled: Boolean = false
+    private var isScrimOpaque: Boolean = false
+    private lateinit var statusBar: StatusBar
+    private var pendingScrimReadyCallback: Runnable? = null
+
+    private var shouldPlayAnimation = false
+    private val statusListeners = arrayListOf<FoldAodAnimationStatus>()
+
+    private var isAnimationPlaying = false
+
+    override fun initialize(statusBar: StatusBar, lightRevealScrim: LightRevealScrim) {
+        this.statusBar = statusBar
+
+        screenLifecycle.addObserver(this)
+        wakefulnessLifecycle.addObserver(this)
+    }
+
+    /**
+     * Returns true if we should run fold to AOD animation
+     */
+    override fun shouldPlayAnimation(): Boolean =
+        shouldPlayAnimation
+
+    override fun startAnimation(): Boolean =
+        if (alwaysOnEnabled &&
+            wakefulnessLifecycle.lastSleepReason == PowerManager.GO_TO_SLEEP_REASON_DEVICE_FOLD &&
+            globalSettings.getString(Settings.Global.ANIMATOR_DURATION_SCALE) != "0"
+        ) {
+            shouldPlayAnimation = true
+
+            isAnimationPlaying = true
+            statusBar.notificationPanelViewController.prepareFoldToAodAnimation()
+
+            statusListeners.forEach(FoldAodAnimationStatus::onFoldToAodAnimationChanged)
+
+            true
+        } else {
+            shouldPlayAnimation = false
+            false
+        }
+
+    override fun onStartedWakingUp() {
+        shouldPlayAnimation = false
+        isAnimationPlaying = false
+    }
+
+    /**
+     * Called when screen starts turning on, the contents of the screen might not be visible yet.
+     * This method reports back that the animation is ready in [onReady] callback.
+     *
+     * @param onReady callback when the animation is ready
+     * @see [com.android.systemui.keyguard.KeyguardViewMediator]
+     */
+    fun onScreenTurningOn(onReady: Runnable) {
+        if (shouldPlayAnimation) {
+            if (isScrimOpaque) {
+                onReady.run()
+            } else {
+                pendingScrimReadyCallback = onReady
+            }
+        } else {
+            // No animation, call ready callback immediately
+            onReady.run()
+        }
+    }
+
+    /**
+     * Called when keyguard scrim opaque changed
+     */
+    override fun onScrimOpaqueChanged(isOpaque: Boolean) {
+        isScrimOpaque = isOpaque
+
+        if (isOpaque) {
+            pendingScrimReadyCallback?.run()
+            pendingScrimReadyCallback = null
+        }
+    }
+
+    override fun onScreenTurnedOn() {
+        if (shouldPlayAnimation) {
+            statusBar.notificationPanelViewController.startFoldToAodAnimation {
+                // End action
+                isAnimationPlaying = false
+                keyguardViewMediatorLazy.get().maybeHandlePendingLock()
+            }
+            shouldPlayAnimation = false
+        }
+    }
+
+    override fun isAnimationPlaying(): Boolean =
+        isAnimationPlaying
+
+    override fun isKeyguardHideDelayed(): Boolean =
+        isAnimationPlaying()
+
+    override fun shouldShowAodIconsWhenShade(): Boolean =
+        shouldPlayAnimation()
+
+    override fun shouldAnimateAodIcons(): Boolean =
+        !shouldPlayAnimation()
+
+    override fun shouldAnimateDozingChange(): Boolean =
+        !shouldPlayAnimation()
+
+    override fun shouldAnimateClockChange(): Boolean =
+        !isAnimationPlaying()
+
+    /**
+     * Called when AOD status is changed
+     */
+    override fun onAlwaysOnChanged(alwaysOn: Boolean) {
+        alwaysOnEnabled = alwaysOn
+    }
+
+    override fun addCallback(listener: FoldAodAnimationStatus) {
+        statusListeners += listener
+    }
+
+    override fun removeCallback(listener: FoldAodAnimationStatus) {
+        statusListeners.remove(listener)
+    }
+
+    interface FoldAodAnimationStatus {
+        fun onFoldToAodAnimationChanged()
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/SysUIUnfoldModule.kt b/packages/SystemUI/src/com/android/systemui/unfold/SysUIUnfoldModule.kt
index b53ab21..ccde316 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/SysUIUnfoldModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/SysUIUnfoldModule.kt
@@ -78,6 +78,8 @@
 
     fun getStatusBarMoveFromCenterAnimationController(): StatusBarMoveFromCenterAnimationController
 
+    fun getFoldAodAnimationController(): FoldAodAnimationController
+
     fun getUnfoldTransitionWallpaperController(): UnfoldTransitionWallpaperController
 
     fun getUnfoldLightRevealOverlayAnimation(): UnfoldLightRevealOverlayAnimation
diff --git a/packages/SystemUI/src/com/android/systemui/util/condition/Monitor.java b/packages/SystemUI/src/com/android/systemui/util/condition/Monitor.java
index a7e9cdb..8b6e982 100644
--- a/packages/SystemUI/src/com/android/systemui/util/condition/Monitor.java
+++ b/packages/SystemUI/src/com/android/systemui/util/condition/Monitor.java
@@ -23,7 +23,6 @@
 
 import org.jetbrains.annotations.NotNull;
 
-import java.lang.ref.WeakReference;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.Iterator;
@@ -38,7 +37,7 @@
 public class Monitor implements CallbackController<Monitor.Callback> {
     private final String mTag = getClass().getSimpleName();
 
-    private final ArrayList<WeakReference<Callback>> mCallbacks = new ArrayList<>();
+    private final ArrayList<Callback> mCallbacks = new ArrayList<>();
 
     // Set of all conditions that need to be monitored.
     private final Set<Condition> mConditions;
@@ -66,9 +65,9 @@
         mAllConditionsMet = newAllConditionsMet;
 
         // Updates all callbacks.
-        final Iterator<WeakReference<Callback>> iterator = mCallbacks.iterator();
+        final Iterator<Callback> iterator = mCallbacks.iterator();
         while (iterator.hasNext()) {
-            final Callback callback = iterator.next().get();
+            final Callback callback = iterator.next();
             if (callback == null) {
                 iterator.remove();
             } else {
@@ -78,7 +77,7 @@
     };
 
     @Inject
-    public Monitor(Set<Condition> conditions) {
+    public Monitor(Set<Condition> conditions, Set<Callback> callbacks) {
         mConditions = conditions;
 
         // If there is no condition, give green pass.
@@ -89,12 +88,20 @@
 
         // Initializes the conditions map and registers a callback for each condition.
         mConditions.forEach((condition -> mConditionsMap.put(condition, false)));
+
+        if (callbacks == null) {
+            return;
+        }
+
+        for (Callback callback : callbacks) {
+            addCallback(callback);
+        }
     }
 
     @Override
     public void addCallback(@NotNull Callback callback) {
         if (shouldLog()) Log.d(mTag, "adding callback");
-        mCallbacks.add(new WeakReference<>(callback));
+        mCallbacks.add(callback);
 
         // Updates the callback immediately.
         callback.onConditionsChanged(mAllConditionsMet);
@@ -109,9 +116,9 @@
     @Override
     public void removeCallback(@NotNull Callback callback) {
         if (shouldLog()) Log.d(mTag, "removing callback");
-        final Iterator<WeakReference<Callback>> iterator = mCallbacks.iterator();
+        final Iterator<Callback> iterator = mCallbacks.iterator();
         while (iterator.hasNext()) {
-            final Callback cb = iterator.next().get();
+            final Callback cb = iterator.next();
             if (cb == null || cb == callback) {
                 iterator.remove();
             }
diff --git a/packages/SystemUI/src/com/android/systemui/util/condition/dagger/MonitorComponent.java b/packages/SystemUI/src/com/android/systemui/util/condition/dagger/MonitorComponent.java
new file mode 100644
index 0000000..fc67973
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/condition/dagger/MonitorComponent.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.util.condition.dagger;
+
+import com.android.systemui.util.condition.Condition;
+import com.android.systemui.util.condition.Monitor;
+
+import java.util.Set;
+
+import dagger.BindsInstance;
+import dagger.Subcomponent;
+
+/**
+ * Component for {@link Monitor}.
+ */
+@Subcomponent
+public interface MonitorComponent {
+    /**
+     * Factory for {@link MonitorComponent}.
+     */
+    @Subcomponent.Factory
+    interface Factory {
+        MonitorComponent create(@BindsInstance Set<Condition> conditions,
+                @BindsInstance Set<Monitor.Callback> callbacks);
+    }
+
+    /**
+     * Provides {@link Monitor}.
+     * @return
+     */
+    Monitor getMonitor();
+}
diff --git a/packages/SystemUI/src/com/android/systemui/util/dagger/UtilModule.java b/packages/SystemUI/src/com/android/systemui/util/dagger/UtilModule.java
index 981bf01..7892d6e 100644
--- a/packages/SystemUI/src/com/android/systemui/util/dagger/UtilModule.java
+++ b/packages/SystemUI/src/com/android/systemui/util/dagger/UtilModule.java
@@ -18,6 +18,7 @@
 
 import com.android.systemui.util.RingerModeTracker;
 import com.android.systemui.util.RingerModeTrackerImpl;
+import com.android.systemui.util.condition.dagger.MonitorComponent;
 import com.android.systemui.util.wrapper.UtilWrapperModule;
 
 import dagger.Binds;
@@ -26,6 +27,9 @@
 /** Dagger Module for code in the util package. */
 @Module(includes = {
                 UtilWrapperModule.class
+        },
+        subcomponents = {
+                MonitorComponent.class,
         })
 public interface UtilModule {
     /** */
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
index e967033..74e0f40 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
@@ -264,7 +264,7 @@
         reset(mView);
         observer.onChange(true);
         mExecutor.runAllReady();
-        verify(mView).switchToClock(KeyguardClockSwitch.SMALL);
+        verify(mView).switchToClock(KeyguardClockSwitch.SMALL, /* animate */ true);
     }
 
     private void verifyAttachment(VerificationMode times) {
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java
index e4336fe..8717a0e 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java
@@ -253,8 +253,8 @@
     }
 
     @Test
-    public void switchingToBigClock_makesSmallClockDisappear() {
-        mKeyguardClockSwitch.switchToClock(LARGE);
+    public void switchingToBigClockWithAnimation_makesSmallClockDisappear() {
+        mKeyguardClockSwitch.switchToClock(LARGE, /* animate */ true);
 
         mKeyguardClockSwitch.mClockInAnim.end();
         mKeyguardClockSwitch.mClockOutAnim.end();
@@ -265,8 +265,17 @@
     }
 
     @Test
-    public void switchingToSmallClock_makesBigClockDisappear() {
-        mKeyguardClockSwitch.switchToClock(SMALL);
+    public void switchingToBigClockNoAnimation_makesSmallClockDisappear() {
+        mKeyguardClockSwitch.switchToClock(LARGE, /* animate */ false);
+
+        assertThat(mLargeClockFrame.getAlpha()).isEqualTo(1);
+        assertThat(mLargeClockFrame.getVisibility()).isEqualTo(VISIBLE);
+        assertThat(mClockFrame.getAlpha()).isEqualTo(0);
+    }
+
+    @Test
+    public void switchingToSmallClockWithAnimation_makesBigClockDisappear() {
+        mKeyguardClockSwitch.switchToClock(SMALL, /* animate */ true);
 
         mKeyguardClockSwitch.mClockInAnim.end();
         mKeyguardClockSwitch.mClockOutAnim.end();
@@ -279,8 +288,19 @@
     }
 
     @Test
+    public void switchingToSmallClockNoAnimation_makesBigClockDisappear() {
+        mKeyguardClockSwitch.switchToClock(SMALL, false);
+
+        assertThat(mClockFrame.getAlpha()).isEqualTo(1);
+        assertThat(mClockFrame.getVisibility()).isEqualTo(VISIBLE);
+        // only big clock is removed at switch
+        assertThat(mLargeClockFrame.getParent()).isNull();
+        assertThat(mLargeClockFrame.getAlpha()).isEqualTo(0);
+    }
+
+    @Test
     public void switchingToBigClock_returnsTrueOnlyWhenItWasNotVisibleBefore() {
-        assertThat(mKeyguardClockSwitch.switchToClock(LARGE)).isTrue();
-        assertThat(mKeyguardClockSwitch.switchToClock(LARGE)).isFalse();
+        assertThat(mKeyguardClockSwitch.switchToClock(LARGE, /* animate */ true)).isTrue();
+        assertThat(mKeyguardClockSwitch.switchToClock(LARGE, /* animate */ true)).isFalse();
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
index 7776285..24b01e0 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
@@ -244,7 +244,7 @@
         ViewGroup userSwitcher = mKeyguardSecurityContainer.findViewById(
                 R.id.keyguard_bouncer_user_switcher);
         assertThat(((FrameLayout.LayoutParams) userSwitcher.getLayoutParams()).gravity)
-                .isEqualTo(Gravity.LEFT | Gravity.TOP);
+                .isEqualTo(Gravity.LEFT | Gravity.CENTER_VERTICAL);
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/CommunalManagerUpdaterTest.java b/packages/SystemUI/tests/src/com/android/systemui/communal/CommunalManagerUpdaterTest.java
index 9a9b7c4..4a29ada 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/communal/CommunalManagerUpdaterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/communal/CommunalManagerUpdaterTest.java
@@ -28,8 +28,8 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.communal.conditions.CommunalConditionsMonitor;
 import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.condition.Monitor;
 import com.android.systemui.util.time.FakeSystemClock;
 
 import org.junit.Before;
@@ -45,7 +45,7 @@
     @Mock
     private CommunalManager mCommunalManager;
     @Mock
-    private CommunalConditionsMonitor mCommunalConditionsMonitor;
+    private Monitor mCommunalConditionsMonitor;
 
     private FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock());
 
@@ -55,7 +55,7 @@
         mContext.addMockSystemService(CommunalManager.class, mCommunalManager);
 
         doAnswer(invocation -> {
-            final CommunalConditionsMonitor.Callback callback = invocation.getArgument(0);
+            final Monitor.Callback callback = invocation.getArgument(0);
             callback.onConditionsChanged(true);
             return null;
         }).when(mCommunalConditionsMonitor).addCallback(any());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/CommunalSourceMonitorTest.java b/packages/SystemUI/tests/src/com/android/systemui/communal/CommunalSourceMonitorTest.java
index 409dd94..df1cc76 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/communal/CommunalSourceMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/communal/CommunalSourceMonitorTest.java
@@ -31,8 +31,8 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.communal.conditions.CommunalConditionsMonitor;
 import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.condition.Monitor;
 import com.android.systemui.util.time.FakeSystemClock;
 
 import org.junit.Before;
@@ -49,9 +49,9 @@
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
 public class CommunalSourceMonitorTest extends SysuiTestCase {
-    @Mock private CommunalConditionsMonitor mCommunalConditionsMonitor;
+    @Mock private Monitor mCommunalConditionsMonitor;
 
-    @Captor private ArgumentCaptor<CommunalConditionsMonitor.Callback> mConditionsCallbackCaptor;
+    @Captor private ArgumentCaptor<Monitor.Callback> mConditionsCallbackCaptor;
 
     private CommunalSourceMonitor mCommunalSourceMonitor;
     private FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock());
@@ -156,7 +156,7 @@
     private void setConditionsMet(boolean value) {
         mExecutor.runAllReady();
         verify(mCommunalConditionsMonitor).addCallback(mConditionsCallbackCaptor.capture());
-        final CommunalConditionsMonitor.Callback conditionsCallback =
+        final Monitor.Callback conditionsCallback =
                 mConditionsCallbackCaptor.getValue();
         conditionsCallback.onConditionsChanged(value);
         mExecutor.runAllReady();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeUiTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeUiTest.java
index 873f7a4..55af51d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeUiTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeUiTest.java
@@ -45,6 +45,8 @@
 import com.android.systemui.statusbar.phone.DozeParameters;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.tuner.TunerService;
+import com.android.systemui.unfold.FoldAodAnimationController;
+import com.android.systemui.unfold.SysUIUnfoldComponent;
 import com.android.systemui.util.wakelock.WakeLockFake;
 
 import org.junit.After;
@@ -54,6 +56,8 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.util.Optional;
+
 @RunWith(AndroidJUnit4.class)
 @SmallTest
 public class DozeUiTest extends SysuiTestCase {
@@ -79,6 +83,10 @@
     @Mock
     private StatusBarStateController mStatusBarStateController;
     @Mock
+    private FoldAodAnimationController mFoldAodAnimationController;
+    @Mock
+    private SysUIUnfoldComponent mSysUIUnfoldComponent;
+    @Mock
     private ConfigurationController mConfigurationController;
 
     @Before
@@ -90,9 +98,13 @@
         mWakeLock = new WakeLockFake();
         mHandler = mHandlerThread.getThreadHandler();
 
+        when(mSysUIUnfoldComponent.getFoldAodAnimationController())
+                .thenReturn(mFoldAodAnimationController);
+
         mDozeUi = new DozeUi(mContext, mAlarmManager, mWakeLock, mHost, mHandler,
                 mDozeParameters, mKeyguardUpdateMonitor, mDozeLog, mTunerService,
-                mStatusBarStateController, mConfigurationController);
+                mStatusBarStateController, Optional.of(mSysUIUnfoldComponent),
+                mConfigurationController);
         mDozeUi.setDozeMachine(mMachine);
     }
 
@@ -121,6 +133,7 @@
         reset(mHost);
         when(mDozeParameters.getAlwaysOn()).thenReturn(false);
         when(mDozeParameters.getDisplayNeedsBlanking()).thenReturn(false);
+        when(mDozeParameters.shouldAnimateDozingChange()).thenReturn(true);
 
         mDozeUi.getKeyguardCallback().onKeyguardVisibilityChanged(false);
         verify(mHost).setAnimateScreenOff(eq(false));
@@ -131,6 +144,7 @@
         reset(mHost);
         when(mDozeParameters.getAlwaysOn()).thenReturn(true);
         when(mDozeParameters.getDisplayNeedsBlanking()).thenReturn(false);
+        when(mDozeParameters.shouldAnimateDozingChange()).thenReturn(true);
 
         // Take over when the keyguard is visible.
         mDozeUi.getKeyguardCallback().onKeyguardVisibilityChanged(true);
@@ -142,6 +156,18 @@
     }
 
     @Test
+    public void propagatesAnimateScreenOff_alwaysOn_shouldAnimateDozingChangeIsFalse() {
+        reset(mHost);
+        when(mDozeParameters.getAlwaysOn()).thenReturn(true);
+        when(mDozeParameters.getDisplayNeedsBlanking()).thenReturn(false);
+        when(mDozeParameters.shouldAnimateDozingChange()).thenReturn(false);
+
+        // Take over when the keyguard is visible.
+        mDozeUi.getKeyguardCallback().onKeyguardVisibilityChanged(true);
+        verify(mHost).setAnimateScreenOff(eq(false));
+    }
+
+    @Test
     public void neverAnimateScreenOff_whenNotSupported() {
         // Re-initialize DozeParameters saying that the display requires blanking.
         reset(mDozeParameters);
@@ -149,7 +175,8 @@
         when(mDozeParameters.getDisplayNeedsBlanking()).thenReturn(true);
         mDozeUi = new DozeUi(mContext, mAlarmManager, mWakeLock, mHost, mHandler,
                 mDozeParameters, mKeyguardUpdateMonitor, mDozeLog, mTunerService,
-                mStatusBarStateController, mConfigurationController);
+                mStatusBarStateController, Optional.of(mSysUIUnfoldComponent),
+                mConfigurationController);
         mDozeUi.setDozeMachine(mMachine);
 
         // Never animate if display doesn't support it.
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 53bfeee..904ee91 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
@@ -37,6 +37,7 @@
 
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.SysuiTestableContext;
+import com.android.systemui.dreams.dagger.DreamOverlayComponent;
 import com.android.systemui.util.concurrency.FakeExecutor;
 import com.android.systemui.util.time.FakeSystemClock;
 import com.android.systemui.utils.leaks.LeakCheckedTest;
@@ -78,16 +79,41 @@
     @Mock
     DreamOverlayStateController mDreamOverlayStateController;
 
+    @Mock
+    DreamOverlayComponent.Factory mDreamOverlayStatusBarViewComponentFactory;
+
+    @Mock
+    DreamOverlayComponent mDreamOverlayComponent;
+
+    @Mock
+    DreamOverlayStatusBarViewController mDreamOverlayStatusBarViewController;
+
+    @Mock
+    DreamOverlayContainerView mDreamOverlayContainerView;
+
+    @Mock
+    ViewGroup mDreamOverlayContentView;
+
     @Before
     public void setup() {
         MockitoAnnotations.initMocks(this);
         mContext.addMockSystemService(WindowManager.class, mWindowManager);
+
+        when(mDreamOverlayComponent.getDreamOverlayContentView())
+                .thenReturn(mDreamOverlayContentView);
+        when(mDreamOverlayComponent.getDreamOverlayContainerView())
+                .thenReturn(mDreamOverlayContainerView);
+        when(mDreamOverlayComponent.getDreamOverlayStatusBarViewController())
+                .thenReturn(mDreamOverlayStatusBarViewController);
+        when(mDreamOverlayStatusBarViewComponentFactory.create())
+                .thenReturn(mDreamOverlayComponent);
+
     }
 
     @Test
     public void testInteraction() throws Exception {
         final DreamOverlayService service = new DreamOverlayService(mContext, mMainExecutor,
-                mDreamOverlayStateController);
+                mDreamOverlayStateController, mDreamOverlayStatusBarViewComponentFactory);
         final IBinder proxy = service.onBind(new Intent());
         final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(proxy);
         clearInvocations(mWindowManager);
@@ -128,7 +154,7 @@
     @Test
     public void testListening() throws Exception {
         final DreamOverlayService service = new DreamOverlayService(mContext, mMainExecutor,
-                mDreamOverlayStateController);
+                mDreamOverlayStateController, mDreamOverlayStatusBarViewComponentFactory);
 
         final IBinder proxy = service.onBind(new Intent());
         final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(proxy);
@@ -150,4 +176,35 @@
         // Verify provider is asked to create overlay.
         verify(mProvider).onCreateOverlay(any(), any(), any());
     }
+
+    @Test
+    public void testDreamOverlayStatusBarViewControllerInitialized() throws Exception {
+        final DreamOverlayService service = new DreamOverlayService(mContext, mMainExecutor,
+                mDreamOverlayStateController, mDreamOverlayStatusBarViewComponentFactory);
+
+        final IBinder proxy = service.onBind(new Intent());
+        final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(proxy);
+
+        // Inform the overlay service of dream starting.
+        overlay.startDream(mWindowParams, mDreamOverlayCallback);
+        mMainExecutor.runAllReady();
+
+        verify(mDreamOverlayStatusBarViewController).init();
+    }
+
+    @Test
+    public void testRootViewAttachListenerIsAddedToDreamOverlayContentView() throws Exception {
+        final DreamOverlayService service = new DreamOverlayService(mContext, mMainExecutor,
+                mDreamOverlayStateController, mDreamOverlayStatusBarViewComponentFactory);
+
+        final IBinder proxy = service.onBind(new Intent());
+        final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(proxy);
+
+        // Inform the overlay service of dream starting.
+        overlay.startDream(mWindowParams, mDreamOverlayCallback);
+        mMainExecutor.runAllReady();
+
+        verify(mDreamOverlayContentView).addOnAttachStateChangeListener(
+                any(View.OnAttachStateChangeListener.class));
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java
new file mode 100644
index 0000000..7f72dda
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java
@@ -0,0 +1,195 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.dreams;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.net.ConnectivityManager;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.NetworkRequest;
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.battery.BatteryMeterViewController;
+import com.android.systemui.statusbar.policy.BatteryController;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class DreamOverlayStatusBarViewControllerTest extends SysuiTestCase {
+
+    @Mock
+    DreamOverlayStatusBarView mView;
+    @Mock
+    BatteryController mBatteryController;
+    @Mock
+    BatteryMeterViewController mBatteryMeterViewController;
+    @Mock
+    ConnectivityManager mConnectivityManager;
+    @Mock
+    NetworkCapabilities mNetworkCapabilities;
+    @Mock
+    Network mNetwork;
+
+    DreamOverlayStatusBarViewController mController;
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+        mController = new DreamOverlayStatusBarViewController(
+                mContext, mView, mBatteryController, mBatteryMeterViewController,
+                mConnectivityManager);
+    }
+
+    @Test
+    public void testOnInitInitializesControllers() {
+        mController.onInit();
+        verify(mBatteryMeterViewController).init();
+    }
+
+    @Test
+    public void testOnViewAttachedAddsBatteryControllerCallback() {
+        mController.onViewAttached();
+        verify(mBatteryController)
+                .addCallback(any(BatteryController.BatteryStateChangeCallback.class));
+    }
+
+    @Test
+    public void testOnViewAttachedRegistersNetworkCallback() {
+        mController.onViewAttached();
+        verify(mConnectivityManager)
+                .registerNetworkCallback(any(NetworkRequest.class), any(
+                        ConnectivityManager.NetworkCallback.class));
+    }
+
+    @Test
+    public void testOnViewAttachedShowsWifiStatusWhenWifiUnavailable() {
+        when(mNetworkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI))
+                .thenReturn(false);
+        when(mConnectivityManager.getNetworkCapabilities(any())).thenReturn(mNetworkCapabilities);
+        mController.onViewAttached();
+        verify(mView).showWifiStatus(true);
+    }
+
+    @Test
+    public void testOnViewAttachedHidesWifiStatusWhenWifiAvailable() {
+        when(mNetworkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI))
+                .thenReturn(true);
+        when(mConnectivityManager.getNetworkCapabilities(any())).thenReturn(mNetworkCapabilities);
+        mController.onViewAttached();
+        verify(mView).showWifiStatus(false);
+    }
+
+    @Test
+    public void testOnViewAttachedShowsWifiStatusWhenNetworkCapabilitiesUnavailable() {
+        when(mConnectivityManager.getNetworkCapabilities(any())).thenReturn(null);
+        mController.onViewAttached();
+        verify(mView).showWifiStatus(true);
+    }
+
+    @Test
+    public void testOnViewDetachedRemovesBatteryControllerCallback() {
+        mController.onViewDetached();
+        verify(mBatteryController)
+                .removeCallback(any(BatteryController.BatteryStateChangeCallback.class));
+    }
+
+    @Test
+    public void testOnViewDetachedUnregistersNetworkCallback() {
+        mController.onViewDetached();
+        verify(mConnectivityManager)
+                .unregisterNetworkCallback(any(ConnectivityManager.NetworkCallback.class));
+    }
+
+    @Test
+    public void testBatteryPercentTextShownWhenBatteryLevelChangesWhileCharging() {
+        final ArgumentCaptor<BatteryController.BatteryStateChangeCallback> callbackCapture =
+                ArgumentCaptor.forClass(BatteryController.BatteryStateChangeCallback.class);
+        mController.onViewAttached();
+        verify(mBatteryController).addCallback(callbackCapture.capture());
+        callbackCapture.getValue().onBatteryLevelChanged(1, true, true);
+        verify(mView).showBatteryPercentText(true);
+    }
+
+    @Test
+    public void testBatteryPercentTextHiddenWhenBatteryLevelChangesWhileNotCharging() {
+        final ArgumentCaptor<BatteryController.BatteryStateChangeCallback> callbackCapture =
+                ArgumentCaptor.forClass(BatteryController.BatteryStateChangeCallback.class);
+        mController.onViewAttached();
+        verify(mBatteryController).addCallback(callbackCapture.capture());
+        callbackCapture.getValue().onBatteryLevelChanged(1, true, false);
+        verify(mView).showBatteryPercentText(false);
+    }
+
+    @Test
+    public void testWifiStatusHiddenWhenWifiBecomesAvailable() {
+        // Make sure wifi starts out unavailable when onViewAttached is called.
+        when(mNetworkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI))
+                .thenReturn(false);
+        when(mConnectivityManager.getNetworkCapabilities(any())).thenReturn(mNetworkCapabilities);
+        mController.onViewAttached();
+
+        final ArgumentCaptor<ConnectivityManager.NetworkCallback> callbackCapture =
+                ArgumentCaptor.forClass(ConnectivityManager.NetworkCallback.class);
+        verify(mConnectivityManager).registerNetworkCallback(any(), callbackCapture.capture());
+        callbackCapture.getValue().onAvailable(mNetwork);
+        verify(mView).showWifiStatus(false);
+    }
+
+    @Test
+    public void testWifiStatusShownWhenWifiBecomesUnavailable() {
+        // Make sure wifi starts out available when onViewAttached is called.
+        when(mNetworkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI))
+                .thenReturn(true);
+        when(mConnectivityManager.getNetworkCapabilities(any())).thenReturn(mNetworkCapabilities);
+        mController.onViewAttached();
+
+        final ArgumentCaptor<ConnectivityManager.NetworkCallback> callbackCapture =
+                ArgumentCaptor.forClass(ConnectivityManager.NetworkCallback.class);
+        verify(mConnectivityManager).registerNetworkCallback(any(), callbackCapture.capture());
+        callbackCapture.getValue().onLost(mNetwork);
+        verify(mView).showWifiStatus(true);
+    }
+
+    @Test
+    public void testWifiStatusHiddenWhenCapabilitiesChange() {
+        // Make sure wifi starts out unavailable when onViewAttached is called.
+        when(mNetworkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI))
+                .thenReturn(false);
+        when(mConnectivityManager.getNetworkCapabilities(any())).thenReturn(mNetworkCapabilities);
+        mController.onViewAttached();
+
+        final ArgumentCaptor<ConnectivityManager.NetworkCallback> callbackCapture =
+                ArgumentCaptor.forClass(ConnectivityManager.NetworkCallback.class);
+        verify(mConnectivityManager).registerNetworkCallback(any(), callbackCapture.capture());
+        when(mNetworkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI))
+                .thenReturn(true);
+        callbackCapture.getValue().onCapabilitiesChanged(mNetwork, mNetworkCapabilities);
+        verify(mView).showWifiStatus(false);
+    }
+}
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 8d329c5..27fcb11 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -58,6 +58,7 @@
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.statusbar.policy.UserSwitcherController;
+import com.android.systemui.unfold.FoldAodAnimationController;
 import com.android.systemui.unfold.SysUIUnfoldComponent;
 import com.android.systemui.unfold.UnfoldLightRevealOverlayAnimation;
 import com.android.systemui.util.DeviceConfigProxy;
@@ -69,7 +70,6 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
-import org.mockito.ArgumentMatchers;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
@@ -95,62 +95,40 @@
     private @Mock NavigationModeController mNavigationModeController;
     private @Mock KeyguardDisplayManager mKeyguardDisplayManager;
     private @Mock DozeParameters mDozeParameters;
-    private @Mock Optional<SysUIUnfoldComponent> mSysUIUnfoldComponent;
-    private @Mock Optional<UnfoldLightRevealOverlayAnimation> mUnfoldAnimationOptional;
+    private @Mock SysUIUnfoldComponent mSysUIUnfoldComponent;
     private @Mock UnfoldLightRevealOverlayAnimation mUnfoldAnimation;
     private @Mock SysuiStatusBarStateController mStatusBarStateController;
     private @Mock KeyguardStateController mKeyguardStateController;
     private @Mock NotificationShadeDepthController mNotificationShadeDepthController;
     private @Mock KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
     private @Mock ScreenOffAnimationController mScreenOffAnimationController;
+    private @Mock FoldAodAnimationController mFoldAodAnimationController;
     private @Mock IKeyguardDrawnCallback mKeyguardDrawnCallback;
     private @Mock InteractionJankMonitor mInteractionJankMonitor;
     private DeviceConfigProxy mDeviceConfig = new DeviceConfigProxyFake();
     private FakeExecutor mUiBgExecutor = new FakeExecutor(new FakeSystemClock());
 
+    private Optional<SysUIUnfoldComponent> mSysUiUnfoldComponentOptional;
+
     private FalsingCollectorFake mFalsingCollector;
 
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
         mFalsingCollector = new FalsingCollectorFake();
+        mSysUiUnfoldComponentOptional = Optional.of(mSysUIUnfoldComponent);
 
         when(mLockPatternUtils.getDevicePolicyManager()).thenReturn(mDevicePolicyManager);
         when(mPowerManager.newWakeLock(anyInt(), any())).thenReturn(mock(WakeLock.class));
-        when(mSysUIUnfoldComponent.map(
-                ArgumentMatchers.<Function<SysUIUnfoldComponent, UnfoldLightRevealOverlayAnimation>>
-                        any()))
-            .thenReturn(mUnfoldAnimationOptional);
-        when(mUnfoldAnimationOptional.isPresent()).thenReturn(true);
-        when(mUnfoldAnimationOptional.get()).thenReturn(mUnfoldAnimation);
+        when(mSysUIUnfoldComponent.getUnfoldLightRevealOverlayAnimation())
+                .thenReturn(mUnfoldAnimation);
+        when(mSysUIUnfoldComponent.getFoldAodAnimationController())
+                .thenReturn(mFoldAodAnimationController);
+
         when(mInteractionJankMonitor.begin(any(), anyInt())).thenReturn(true);
         when(mInteractionJankMonitor.end(anyInt())).thenReturn(true);
 
-        mViewMediator = new KeyguardViewMediator(
-                mContext,
-                mFalsingCollector,
-                mLockPatternUtils,
-                mBroadcastDispatcher,
-                () -> mStatusBarKeyguardViewManager,
-                mDismissCallbackRegistry,
-                mUpdateMonitor,
-                mDumpManager,
-                mUiBgExecutor,
-                mPowerManager,
-                mTrustManager,
-                mUserSwitcherController,
-                mDeviceConfig,
-                mNavigationModeController,
-                mKeyguardDisplayManager,
-                mDozeParameters,
-                mSysUIUnfoldComponent,
-                mStatusBarStateController,
-                mKeyguardStateController,
-                () -> mKeyguardUnlockAnimationController,
-                mScreenOffAnimationController,
-                () -> mNotificationShadeDepthController,
-                mInteractionJankMonitor);
-        mViewMediator.start();
+        createAndStartViewMediator();
     }
 
     @Test
@@ -179,6 +157,7 @@
         mViewMediator.onScreenTurningOn(mKeyguardDrawnCallback);
         TestableLooper.get(this).processAllMessages();
         onUnfoldOverlayReady();
+        onFoldAodReady();
 
         // Should be called when both unfold overlay and keyguard drawn ready
         verify(mKeyguardDrawnCallback).onDrawn();
@@ -188,7 +167,8 @@
     @TestableLooper.RunWithLooper(setAsMainLooper = true)
     public void testUnfoldTransitionDisabledDrawnTasksReady_onScreenTurningOn_callsDrawnCallback()
             throws RemoteException {
-        when(mUnfoldAnimationOptional.isPresent()).thenReturn(false);
+        mSysUiUnfoldComponentOptional = Optional.empty();
+        createAndStartViewMediator();
 
         mViewMediator.onScreenTurningOn(mKeyguardDrawnCallback);
         TestableLooper.get(this).processAllMessages();
@@ -200,6 +180,7 @@
     @Test
     public void testIsAnimatingScreenOff() {
         when(mDozeParameters.shouldControlUnlockedScreenOff()).thenReturn(true);
+        when(mDozeParameters.shouldAnimateDozingChange()).thenReturn(true);
 
         mViewMediator.onFinishedGoingToSleep(OFF_BECAUSE_OF_USER, false);
         mViewMediator.setDozing(true);
@@ -244,4 +225,39 @@
         overlayReadyCaptor.getValue().run();
         TestableLooper.get(this).processAllMessages();
     }
+
+    private void onFoldAodReady() {
+        ArgumentCaptor<Runnable> ready = ArgumentCaptor.forClass(Runnable.class);
+        verify(mFoldAodAnimationController).onScreenTurningOn(ready.capture());
+        ready.getValue().run();
+        TestableLooper.get(this).processAllMessages();
+    }
+
+    private void createAndStartViewMediator() {
+        mViewMediator = new KeyguardViewMediator(
+                mContext,
+                mFalsingCollector,
+                mLockPatternUtils,
+                mBroadcastDispatcher,
+                () -> mStatusBarKeyguardViewManager,
+                mDismissCallbackRegistry,
+                mUpdateMonitor,
+                mDumpManager,
+                mUiBgExecutor,
+                mPowerManager,
+                mTrustManager,
+                mUserSwitcherController,
+                mDeviceConfig,
+                mNavigationModeController,
+                mKeyguardDisplayManager,
+                mDozeParameters,
+                mSysUiUnfoldComponentOptional,
+                mStatusBarStateController,
+                mKeyguardStateController,
+                () -> mKeyguardUnlockAnimationController,
+                mScreenOffAnimationController,
+                () -> mNotificationShadeDepthController,
+                mInteractionJankMonitor);
+        mViewMediator.start();
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSFactoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSFactoryImplTest.kt
new file mode 100644
index 0000000..af33daf
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSFactoryImplTest.kt
@@ -0,0 +1,216 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tileimpl
+
+import android.content.ComponentName
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.qs.QSHost
+import com.android.systemui.qs.external.CustomTile
+import com.android.systemui.qs.tiles.AirplaneModeTile
+import com.android.systemui.qs.tiles.AlarmTile
+import com.android.systemui.qs.tiles.BatterySaverTile
+import com.android.systemui.qs.tiles.BluetoothTile
+import com.android.systemui.qs.tiles.CameraToggleTile
+import com.android.systemui.qs.tiles.CastTile
+import com.android.systemui.qs.tiles.CellularTile
+import com.android.systemui.qs.tiles.ColorInversionTile
+import com.android.systemui.qs.tiles.DataSaverTile
+import com.android.systemui.qs.tiles.DeviceControlsTile
+import com.android.systemui.qs.tiles.DndTile
+import com.android.systemui.qs.tiles.FgsManagerTile
+import com.android.systemui.qs.tiles.FlashlightTile
+import com.android.systemui.qs.tiles.HotspotTile
+import com.android.systemui.qs.tiles.InternetTile
+import com.android.systemui.qs.tiles.LocationTile
+import com.android.systemui.qs.tiles.MicrophoneToggleTile
+import com.android.systemui.qs.tiles.NfcTile
+import com.android.systemui.qs.tiles.NightDisplayTile
+import com.android.systemui.qs.tiles.OneHandedModeTile
+import com.android.systemui.qs.tiles.QRCodeScannerTile
+import com.android.systemui.qs.tiles.QuickAccessWalletTile
+import com.android.systemui.qs.tiles.ReduceBrightColorsTile
+import com.android.systemui.qs.tiles.RotationLockTile
+import com.android.systemui.qs.tiles.ScreenRecordTile
+import com.android.systemui.qs.tiles.UiModeNightTile
+import com.android.systemui.qs.tiles.UserTile
+import com.android.systemui.qs.tiles.WifiTile
+import com.android.systemui.qs.tiles.WorkModeTile
+import com.android.systemui.util.leak.GarbageMonitor
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Answers
+import org.mockito.Mock
+import org.mockito.Mockito.inOrder
+import org.mockito.MockitoAnnotations
+import org.mockito.Mockito.`when` as whenever
+
+private val specMap = mapOf(
+        "wifi" to WifiTile::class.java,
+        "internet" to InternetTile::class.java,
+        "bt" to BluetoothTile::class.java,
+        "cell" to CellularTile::class.java,
+        "dnd" to DndTile::class.java,
+        "inversion" to ColorInversionTile::class.java,
+        "airplane" to AirplaneModeTile::class.java,
+        "work" to WorkModeTile::class.java,
+        "rotation" to RotationLockTile::class.java,
+        "flashlight" to FlashlightTile::class.java,
+        "location" to LocationTile::class.java,
+        "cast" to CastTile::class.java,
+        "hotspot" to HotspotTile::class.java,
+        "user" to UserTile::class.java,
+        "battery" to BatterySaverTile::class.java,
+        "saver" to DataSaverTile::class.java,
+        "night" to NightDisplayTile::class.java,
+        "nfc" to NfcTile::class.java,
+        "dark" to UiModeNightTile::class.java,
+        "screenrecord" to ScreenRecordTile::class.java,
+        "reduce_brightness" to ReduceBrightColorsTile::class.java,
+        "cameratoggle" to CameraToggleTile::class.java,
+        "mictoggle" to MicrophoneToggleTile::class.java,
+        "controls" to DeviceControlsTile::class.java,
+        "alarm" to AlarmTile::class.java,
+        "wallet" to QuickAccessWalletTile::class.java,
+        "qr_code_scanner" to QRCodeScannerTile::class.java,
+        "onehanded" to OneHandedModeTile::class.java,
+        "fgsmanager" to FgsManagerTile::class.java
+)
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class QSFactoryImplTest : SysuiTestCase() {
+
+    @Mock private lateinit var qsHost: QSHost
+    @Mock(answer = Answers.RETURNS_SELF) private lateinit var customTileBuilder: CustomTile.Builder
+    @Mock private lateinit var customTile: CustomTile
+
+    @Mock private lateinit var wifiTile: WifiTile
+    @Mock private lateinit var internetTile: InternetTile
+    @Mock private lateinit var bluetoothTile: BluetoothTile
+    @Mock private lateinit var cellularTile: CellularTile
+    @Mock private lateinit var dndTile: DndTile
+    @Mock private lateinit var colorInversionTile: ColorInversionTile
+    @Mock private lateinit var airplaneTile: AirplaneModeTile
+    @Mock private lateinit var workTile: WorkModeTile
+    @Mock private lateinit var rotationTile: RotationLockTile
+    @Mock private lateinit var flashlightTile: FlashlightTile
+    @Mock private lateinit var locationTile: LocationTile
+    @Mock private lateinit var castTile: CastTile
+    @Mock private lateinit var hotspotTile: HotspotTile
+    @Mock private lateinit var userTile: UserTile
+    @Mock private lateinit var batterySaverTile: BatterySaverTile
+    @Mock private lateinit var dataSaverTile: DataSaverTile
+    @Mock private lateinit var nightDisplayTile: NightDisplayTile
+    @Mock private lateinit var nfcTile: NfcTile
+    @Mock private lateinit var memoryTile: GarbageMonitor.MemoryTile
+    @Mock private lateinit var darkModeTile: UiModeNightTile
+    @Mock private lateinit var screenRecordTile: ScreenRecordTile
+    @Mock private lateinit var reduceBrightColorsTile: ReduceBrightColorsTile
+    @Mock private lateinit var cameraToggleTile: CameraToggleTile
+    @Mock private lateinit var microphoneToggleTile: MicrophoneToggleTile
+    @Mock private lateinit var deviceControlsTile: DeviceControlsTile
+    @Mock private lateinit var alarmTile: AlarmTile
+    @Mock private lateinit var quickAccessWalletTile: QuickAccessWalletTile
+    @Mock private lateinit var qrCodeScannerTile: QRCodeScannerTile
+    @Mock private lateinit var oneHandedModeTile: OneHandedModeTile
+    @Mock private lateinit var fgsManagerTile: FgsManagerTile
+
+    private lateinit var factory: QSFactoryImpl
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+
+        whenever(qsHost.context).thenReturn(mContext)
+        whenever(qsHost.userContext).thenReturn(mContext)
+        whenever(customTileBuilder.build()).thenReturn(customTile)
+
+        factory = QSFactoryImpl(
+                { qsHost },
+                { customTileBuilder },
+                { wifiTile },
+                { internetTile },
+                { bluetoothTile },
+                { cellularTile },
+                { dndTile },
+                { colorInversionTile },
+                { airplaneTile },
+                { workTile },
+                { rotationTile },
+                { flashlightTile },
+                { locationTile },
+                { castTile },
+                { hotspotTile },
+                { userTile },
+                { batterySaverTile },
+                { dataSaverTile },
+                { nightDisplayTile },
+                { nfcTile },
+                { memoryTile },
+                { darkModeTile },
+                { screenRecordTile },
+                { reduceBrightColorsTile },
+                { cameraToggleTile },
+                { microphoneToggleTile },
+                { deviceControlsTile },
+                { alarmTile },
+                { quickAccessWalletTile },
+                { qrCodeScannerTile },
+                { oneHandedModeTile },
+                { fgsManagerTile }
+        )
+        // When adding/removing tiles, fix also [specMap]
+    }
+
+    @Test
+    fun testCorrectTileClassStock() {
+        specMap.forEach { spec, klazz ->
+            assertThat(factory.createTile(spec)).isInstanceOf(klazz)
+        }
+    }
+
+    @Test
+    fun testCustomTileClass() {
+        val customSpec = CustomTile.toSpec(ComponentName("test", "test"))
+        assertThat(factory.createTile(customSpec)).isInstanceOf(CustomTile::class.java)
+    }
+
+    @Test
+    fun testBadTileNull() {
+        assertThat(factory.createTile("-432~")).isNull()
+    }
+
+    @Test
+    fun testTileInitializedAndStale() {
+        specMap.forEach { spec, _ ->
+            val tile = factory.createTile(spec) as QSTileImpl<*>
+            val inOrder = inOrder(tile)
+            inOrder.verify(tile).initialize()
+            inOrder.verify(tile).postStale()
+        }
+
+        val customSpec = CustomTile.toSpec(ComponentName("test", "test"))
+        val tile = factory.createTile(customSpec) as QSTileImpl<*>
+        val inOrder = inOrder(tile)
+        inOrder.verify(tile).initialize()
+        inOrder.verify(tile).postStale()
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java
index 9bcdcc9..1cd9b9e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java
@@ -425,6 +425,7 @@
                 .thenReturn(mKeyguardUserSwitcherComponent);
         when(mKeyguardUserSwitcherComponent.getKeyguardUserSwitcherController())
                 .thenReturn(mKeyguardUserSwitcherController);
+        when(mScreenOffAnimationController.shouldAnimateClockChange()).thenReturn(true);
 
         doAnswer((Answer<Void>) invocation -> {
             mTouchHandler = invocation.getArgument(0);
@@ -880,11 +881,11 @@
 
         when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(0);
         triggerPositionClockAndNotifications();
-        verify(mKeyguardStatusViewController).displayClock(LARGE);
+        verify(mKeyguardStatusViewController).displayClock(LARGE, /* animate */ true);
 
         when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(1);
         mNotificationPanelViewController.closeQs();
-        verify(mKeyguardStatusViewController).displayClock(SMALL);
+        verify(mKeyguardStatusViewController).displayClock(SMALL, /* animate */ true);
     }
 
     @Test
@@ -894,12 +895,14 @@
 
         when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(0);
         triggerPositionClockAndNotifications();
-        verify(mKeyguardStatusViewController).displayClock(LARGE);
+        verify(mKeyguardStatusViewController).displayClock(LARGE, /* animate */ true);
 
         when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(1);
         triggerPositionClockAndNotifications();
-        verify(mKeyguardStatusViewController, times(2)).displayClock(LARGE);
-        verify(mKeyguardStatusViewController, never()).displayClock(SMALL);
+        verify(mKeyguardStatusViewController, times(2))
+                .displayClock(LARGE, /* animate */ true);
+        verify(mKeyguardStatusViewController, never())
+                .displayClock(SMALL, /* animate */ true);
     }
 
     @Test
@@ -911,7 +914,20 @@
 
         mNotificationPanelViewController.setDozing(true, false, null);
 
-        verify(mKeyguardStatusViewController).displayClock(LARGE);
+        verify(mKeyguardStatusViewController).displayClock(LARGE, /* animate */ true);
+    }
+
+    @Test
+    public void testSwitchesToBigClockInSplitShadeOnAodAnimateDisabled() {
+        when(mScreenOffAnimationController.shouldAnimateClockChange()).thenReturn(false);
+        mStatusBarStateController.setState(KEYGUARD);
+        enableSplitShade(/* enabled= */ true);
+        when(mMediaDataManager.hasActiveMedia()).thenReturn(true);
+        when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(2);
+
+        mNotificationPanelViewController.setDozing(true, false, null);
+
+        verify(mKeyguardStatusViewController).displayClock(LARGE, /* animate */ false);
     }
 
     @Test
@@ -923,13 +939,13 @@
         // one notification + media player visible
         when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(1);
         triggerPositionClockAndNotifications();
-        verify(mKeyguardStatusViewController).displayClock(SMALL);
+        verify(mKeyguardStatusViewController).displayClock(SMALL, /* animate */ true);
 
         // only media player visible
         when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(0);
         triggerPositionClockAndNotifications();
-        verify(mKeyguardStatusViewController, times(2)).displayClock(SMALL);
-        verify(mKeyguardStatusViewController, never()).displayClock(LARGE);
+        verify(mKeyguardStatusViewController, times(2)).displayClock(SMALL, true);
+        verify(mKeyguardStatusViewController, never()).displayClock(LARGE, /* animate */ true);
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SplitShadeHeaderControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SplitShadeHeaderControllerTest.kt
index 2d548e9..a8544a9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SplitShadeHeaderControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SplitShadeHeaderControllerTest.kt
@@ -1,8 +1,8 @@
 package com.android.systemui.statusbar.phone
 
-import android.test.suitebuilder.annotation.SmallTest
 import android.testing.AndroidTestingRunner
 import android.view.View
+import androidx.test.filters.SmallTest
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.animation.ShadeInterpolation
@@ -42,6 +42,7 @@
     var viewVisibility = View.GONE
 
     private lateinit var splitShadeHeaderController: SplitShadeHeaderController
+    private lateinit var carrierIconSlots: List<String>
 
     @Before
     fun setup() {
@@ -67,12 +68,13 @@
                 featureFlags,
                 batteryMeterViewController
         )
+        carrierIconSlots = listOf(
+                context.getString(com.android.internal.R.string.status_bar_mobile))
     }
 
     @Test
     fun setVisible_onlyInSplitShade() {
-        splitShadeHeaderController.splitShadeMode = true
-        splitShadeHeaderController.shadeExpanded = true
+        makeShadeVisible()
         assertThat(viewVisibility).isEqualTo(View.VISIBLE)
 
         splitShadeHeaderController.splitShadeMode = false
@@ -81,17 +83,38 @@
 
     @Test
     fun updateListeners_registersWhenVisible() {
-        splitShadeHeaderController.splitShadeMode = true
-        splitShadeHeaderController.shadeExpanded = true
+        makeShadeVisible()
         verify(qsCarrierGroupController).setListening(true)
         verify(statusBarIconController).addIconGroup(any())
     }
 
     @Test
     fun shadeExpandedFraction_updatesAlpha() {
-        splitShadeHeaderController.splitShadeMode = true
-        splitShadeHeaderController.shadeExpanded = true
+        makeShadeVisible()
         splitShadeHeaderController.shadeExpandedFraction = 0.5f
         verify(view).setAlpha(ShadeInterpolation.getContentAlpha(0.5f))
     }
-}
\ No newline at end of file
+
+    @Test
+    fun singleCarrier_enablesCarrierIconsInStatusIcons() {
+        whenever(qsCarrierGroupController.isSingleCarrier).thenReturn(true)
+
+        makeShadeVisible()
+
+        verify(statusIcons).removeIgnoredSlots(carrierIconSlots)
+    }
+
+    @Test
+    fun dualCarrier_disablesCarrierIconsInStatusIcons() {
+        whenever(qsCarrierGroupController.isSingleCarrier).thenReturn(false)
+
+        makeShadeVisible()
+
+        verify(statusIcons).addIgnoredSlots(carrierIconSlots)
+    }
+
+    private fun makeShadeVisible() {
+        splitShadeHeaderController.splitShadeMode = true
+        splitShadeHeaderController.shadeExpanded = true
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
index c5bdfed..cbaa460 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
@@ -43,9 +43,6 @@
 import com.android.keyguard.ViewMediatorCallback;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.dock.DockManager;
-import com.android.systemui.dump.DumpManager;
-import com.android.systemui.keyguard.DismissCallbackRegistry;
-import com.android.systemui.keyguard.WakefulnessLifecycle;
 import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
 import com.android.systemui.statusbar.NotificationMediaManager;
@@ -79,9 +76,7 @@
     @Mock
     private NotificationPanelViewController mNotificationPanelView;
     @Mock
-    private BiometricUnlockController mBiometrucUnlockController;
-    @Mock
-    private DismissCallbackRegistry mDismissCallbackRegistry;
+    private BiometricUnlockController mBiometricUnlockController;
     @Mock
     private SysuiStatusBarStateController mStatusBarStateController;
     @Mock
@@ -97,15 +92,12 @@
     @Mock
     private KeyguardBouncer mBouncer;
     @Mock
-    private UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController;
-    @Mock
     private StatusBarKeyguardViewManager.AlternateAuthInterceptor mAlternateAuthInterceptor;
     @Mock
     private KeyguardMessageArea mKeyguardMessageArea;
     @Mock
     private ShadeController mShadeController;
 
-    private WakefulnessLifecycle mWakefulnessLifecycle;
     private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
 
     @Before
@@ -117,10 +109,6 @@
                 .thenReturn(mBouncer);
         when(mStatusBar.getBouncerContainer()).thenReturn(mContainer);
         when(mContainer.findViewById(anyInt())).thenReturn(mKeyguardMessageArea);
-        mWakefulnessLifecycle = new WakefulnessLifecycle(
-                getContext(),
-                null,
-                mock(DumpManager.class));
         mStatusBarKeyguardViewManager = new StatusBarKeyguardViewManager(
                 getContext(),
                 mViewMediatorCallback,
@@ -134,15 +122,13 @@
                 mKeyguardStateController,
                 mock(NotificationMediaManager.class),
                 mKeyguardBouncerFactory,
-                mWakefulnessLifecycle,
-                mUnlockedScreenOffAnimationController,
                 mKeyguardMessageAreaFactory,
                 () -> mShadeController);
         mStatusBarKeyguardViewManager.registerStatusBar(
                 mStatusBar,
                 mNotificationPanelView,
                 new PanelExpansionStateManager(),
-                mBiometrucUnlockController,
+                mBiometricUnlockController,
                 mNotificationContainer,
                 mBypassController);
         mStatusBarKeyguardViewManager.show(null);
@@ -261,7 +247,7 @@
 
     @Test
     public void onPanelExpansionChanged_neverTranslatesBouncerWhenWakeAndUnlock() {
-        when(mBiometrucUnlockController.getMode())
+        when(mBiometricUnlockController.getMode())
                 .thenReturn(BiometricUnlockController.MODE_WAKE_AND_UNLOCK);
         mStatusBarKeyguardViewManager.onPanelExpansionChanged(
                 /* fraction= */ KeyguardBouncer.EXPANSION_VISIBLE,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/condition/ConditionMonitorTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/condition/ConditionMonitorTest.java
index 878bdea..d645449 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/condition/ConditionMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/condition/ConditionMonitorTest.java
@@ -58,7 +58,7 @@
         mCondition3 = spy(new FakeCondition());
         mConditions = new HashSet<>(Arrays.asList(mCondition1, mCondition2, mCondition3));
 
-        mConditionMonitor = new Monitor(mConditions);
+        mConditionMonitor = new Monitor(mConditions, null /*callbacks*/);
     }
 
     @Test
@@ -98,7 +98,7 @@
 
     @Test
     public void addCallback_noConditions_reportAllConditionsMet() {
-        final Monitor monitor = new Monitor(new HashSet<>());
+        final Monitor monitor = new Monitor(new HashSet<>(), null /*callbacks*/);
         final Monitor.Callback callback = mock(Monitor.Callback.class);
 
         monitor.addCallback(callback);
diff --git a/packages/overlays/NavigationBarModeGesturalOverlay/res/values-land/dimens.xml b/packages/overlays/NavigationBarModeGesturalOverlay/res/values-land/dimens.xml
index 1f5834d..a03dcbd 100644
--- a/packages/overlays/NavigationBarModeGesturalOverlay/res/values-land/dimens.xml
+++ b/packages/overlays/NavigationBarModeGesturalOverlay/res/values-land/dimens.xml
@@ -18,5 +18,5 @@
 -->
 <resources>
     <!-- The height of the bottom navigation gesture area. -->
-    <dimen name="navigation_bar_gesture_height">32dp</dimen>
+    <dimen name="navigation_bar_gesture_height">24dp</dimen>
 </resources>
diff --git a/packages/overlays/NavigationBarModeGesturalOverlay/res/values/dimens.xml b/packages/overlays/NavigationBarModeGesturalOverlay/res/values/dimens.xml
index ac1f022..c5d0c9e 100644
--- a/packages/overlays/NavigationBarModeGesturalOverlay/res/values/dimens.xml
+++ b/packages/overlays/NavigationBarModeGesturalOverlay/res/values/dimens.xml
@@ -18,13 +18,13 @@
 -->
 <resources>
     <!-- Height of the bottom navigation / system bar. -->
-    <dimen name="navigation_bar_height">16dp</dimen>
+    <dimen name="navigation_bar_height">24dp</dimen>
     <!-- Height of the bottom navigation bar in portrait; often the same as @dimen/navigation_bar_height -->
-    <dimen name="navigation_bar_height_landscape">16dp</dimen>
+    <dimen name="navigation_bar_height_landscape">24dp</dimen>
     <!-- Width of the navigation bar when it is placed vertically on the screen -->
-    <dimen name="navigation_bar_width">16dp</dimen>
+    <dimen name="navigation_bar_width">24dp</dimen>
     <!-- Height of the bottom navigation / system bar. -->
     <dimen name="navigation_bar_frame_height">48dp</dimen>
     <!-- The height of the bottom navigation gesture area. -->
-    <dimen name="navigation_bar_gesture_height">32dp</dimen>
+    <dimen name="navigation_bar_gesture_height">24dp</dimen>
 </resources>
\ No newline at end of file
diff --git a/packages/overlays/NavigationBarModeGesturalOverlayExtraWideBack/res/values/dimens.xml b/packages/overlays/NavigationBarModeGesturalOverlayExtraWideBack/res/values/dimens.xml
index ac1f022..c5d0c9e 100644
--- a/packages/overlays/NavigationBarModeGesturalOverlayExtraWideBack/res/values/dimens.xml
+++ b/packages/overlays/NavigationBarModeGesturalOverlayExtraWideBack/res/values/dimens.xml
@@ -18,13 +18,13 @@
 -->
 <resources>
     <!-- Height of the bottom navigation / system bar. -->
-    <dimen name="navigation_bar_height">16dp</dimen>
+    <dimen name="navigation_bar_height">24dp</dimen>
     <!-- Height of the bottom navigation bar in portrait; often the same as @dimen/navigation_bar_height -->
-    <dimen name="navigation_bar_height_landscape">16dp</dimen>
+    <dimen name="navigation_bar_height_landscape">24dp</dimen>
     <!-- Width of the navigation bar when it is placed vertically on the screen -->
-    <dimen name="navigation_bar_width">16dp</dimen>
+    <dimen name="navigation_bar_width">24dp</dimen>
     <!-- Height of the bottom navigation / system bar. -->
     <dimen name="navigation_bar_frame_height">48dp</dimen>
     <!-- The height of the bottom navigation gesture area. -->
-    <dimen name="navigation_bar_gesture_height">32dp</dimen>
+    <dimen name="navigation_bar_gesture_height">24dp</dimen>
 </resources>
\ No newline at end of file
diff --git a/packages/overlays/NavigationBarModeGesturalOverlayNarrowBack/res/values/dimens.xml b/packages/overlays/NavigationBarModeGesturalOverlayNarrowBack/res/values/dimens.xml
index ac1f022..c5d0c9e 100644
--- a/packages/overlays/NavigationBarModeGesturalOverlayNarrowBack/res/values/dimens.xml
+++ b/packages/overlays/NavigationBarModeGesturalOverlayNarrowBack/res/values/dimens.xml
@@ -18,13 +18,13 @@
 -->
 <resources>
     <!-- Height of the bottom navigation / system bar. -->
-    <dimen name="navigation_bar_height">16dp</dimen>
+    <dimen name="navigation_bar_height">24dp</dimen>
     <!-- Height of the bottom navigation bar in portrait; often the same as @dimen/navigation_bar_height -->
-    <dimen name="navigation_bar_height_landscape">16dp</dimen>
+    <dimen name="navigation_bar_height_landscape">24dp</dimen>
     <!-- Width of the navigation bar when it is placed vertically on the screen -->
-    <dimen name="navigation_bar_width">16dp</dimen>
+    <dimen name="navigation_bar_width">24dp</dimen>
     <!-- Height of the bottom navigation / system bar. -->
     <dimen name="navigation_bar_frame_height">48dp</dimen>
     <!-- The height of the bottom navigation gesture area. -->
-    <dimen name="navigation_bar_gesture_height">32dp</dimen>
+    <dimen name="navigation_bar_gesture_height">24dp</dimen>
 </resources>
\ No newline at end of file
diff --git a/packages/overlays/NavigationBarModeGesturalOverlayWideBack/res/values/dimens.xml b/packages/overlays/NavigationBarModeGesturalOverlayWideBack/res/values/dimens.xml
index ac1f022..c5d0c9e 100644
--- a/packages/overlays/NavigationBarModeGesturalOverlayWideBack/res/values/dimens.xml
+++ b/packages/overlays/NavigationBarModeGesturalOverlayWideBack/res/values/dimens.xml
@@ -18,13 +18,13 @@
 -->
 <resources>
     <!-- Height of the bottom navigation / system bar. -->
-    <dimen name="navigation_bar_height">16dp</dimen>
+    <dimen name="navigation_bar_height">24dp</dimen>
     <!-- Height of the bottom navigation bar in portrait; often the same as @dimen/navigation_bar_height -->
-    <dimen name="navigation_bar_height_landscape">16dp</dimen>
+    <dimen name="navigation_bar_height_landscape">24dp</dimen>
     <!-- Width of the navigation bar when it is placed vertically on the screen -->
-    <dimen name="navigation_bar_width">16dp</dimen>
+    <dimen name="navigation_bar_width">24dp</dimen>
     <!-- Height of the bottom navigation / system bar. -->
     <dimen name="navigation_bar_frame_height">48dp</dimen>
     <!-- The height of the bottom navigation gesture area. -->
-    <dimen name="navigation_bar_gesture_height">32dp</dimen>
+    <dimen name="navigation_bar_gesture_height">24dp</dimen>
 </resources>
\ No newline at end of file
diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
index 881c910..f050b66 100644
--- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
@@ -1027,8 +1027,8 @@
                     mSystemSupport.getMagnificationProcessor();
             final long identity = Binder.clearCallingIdentity();
             try {
-                magnificationProcessor.getMagnificationRegion(displayId, region,
-                        mSecurityPolicy.canControlMagnification(this));
+                magnificationProcessor.getFullscreenMagnificationRegion(displayId,
+                        region, mSecurityPolicy.canControlMagnification(this));
                 return region;
             } finally {
                 Binder.restoreCallingIdentity(identity);
@@ -1095,7 +1095,7 @@
         try {
             MagnificationProcessor magnificationProcessor =
                     mSystemSupport.getMagnificationProcessor();
-            return (magnificationProcessor.reset(displayId, animate)
+            return (magnificationProcessor.resetFullscreenMagnification(displayId, animate)
                     || !magnificationProcessor.isMagnifying(displayId));
         } finally {
             Binder.restoreCallingIdentity(identity);
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationProcessor.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationProcessor.java
index d0b9895..7a525ee 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationProcessor.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationProcessor.java
@@ -119,6 +119,18 @@
         return false;
     }
 
+    private boolean setScaleAndCenterForFullScreenMagnification(int displayId, float scale,
+            float centerX, float centerY,
+            boolean animate, int id) {
+        if (!isRegistered(displayId)) {
+            register(displayId);
+        }
+        return mController.getFullScreenMagnificationController().setScaleAndCenter(
+                displayId,
+                scale,
+                centerX, centerY, animate, id);
+    }
+
     /**
      * Returns {@code true} if transition magnification mode needed. And it is no need to transition
      * mode when the controlling mode is unchanged or the controlling magnifier is not activated.
@@ -135,24 +147,18 @@
     }
 
     /**
-     * Returns the magnification scale. If an animation is in progress,
-     * this reflects the end state of the animation.
+     * Returns the magnification scale of full-screen magnification on the display.
+     * If an animation is in progress, this reflects the end state of the animation.
      *
      * @param displayId The logical display id.
      * @return the scale
      */
     public float getScale(int displayId) {
-        int mode = getControllingMode(displayId);
-        if (mode == MAGNIFICATION_MODE_FULLSCREEN) {
-            return mController.getFullScreenMagnificationController().getScale(displayId);
-        } else if (mode == MAGNIFICATION_MODE_WINDOW) {
-            return mController.getWindowMagnificationMgr().getScale(displayId);
-        }
-        return 0;
+        return mController.getFullScreenMagnificationController().getScale(displayId);
     }
 
     /**
-     * Returns the magnification center in X coordinate of the controlling magnification mode.
+     * Returns the magnification center in X coordinate of full-screen magnification.
      * If the service can control magnification but fullscreen magnifier is not registered, it will
      * register the magnifier for this call then unregister the magnifier finally to make the
      * magnification center correct.
@@ -162,25 +168,19 @@
      * @return the X coordinate
      */
     public float getCenterX(int displayId, boolean canControlMagnification) {
-        int mode = getControllingMode(displayId);
-        if (mode == MAGNIFICATION_MODE_FULLSCREEN) {
-            boolean registeredJustForThisCall = registerDisplayMagnificationIfNeeded(displayId,
-                    canControlMagnification);
-            try {
-                return mController.getFullScreenMagnificationController().getCenterX(displayId);
-            } finally {
-                if (registeredJustForThisCall) {
-                    unregister(displayId);
-                }
+        boolean registeredJustForThisCall = registerDisplayMagnificationIfNeeded(displayId,
+                canControlMagnification);
+        try {
+            return mController.getFullScreenMagnificationController().getCenterX(displayId);
+        } finally {
+            if (registeredJustForThisCall) {
+                unregister(displayId);
             }
-        } else if (mode == MAGNIFICATION_MODE_WINDOW) {
-            return mController.getWindowMagnificationMgr().getCenterX(displayId);
         }
-        return 0;
     }
 
     /**
-     * Returns the magnification center in Y coordinate of the controlling magnification mode.
+     * Returns the magnification center in Y coordinate of full-screen magnification.
      * If the service can control magnification but fullscreen magnifier is not registered, it will
      * register the magnifier for this call then unregister the magnifier finally to make the
      * magnification center correct.
@@ -190,49 +190,25 @@
      * @return the Y coordinate
      */
     public float getCenterY(int displayId, boolean canControlMagnification) {
-        int mode = getControllingMode(displayId);
-        if (mode == MAGNIFICATION_MODE_FULLSCREEN) {
-            boolean registeredJustForThisCall = registerDisplayMagnificationIfNeeded(displayId,
-                    canControlMagnification);
-            try {
-                return mController.getFullScreenMagnificationController().getCenterY(displayId);
-            } finally {
-                if (registeredJustForThisCall) {
-                    unregister(displayId);
-                }
+        boolean registeredJustForThisCall = registerDisplayMagnificationIfNeeded(displayId,
+                canControlMagnification);
+        try {
+            return mController.getFullScreenMagnificationController().getCenterY(displayId);
+        } finally {
+            if (registeredJustForThisCall) {
+                unregister(displayId);
             }
-        } else if (mode == MAGNIFICATION_MODE_WINDOW) {
-            return mController.getWindowMagnificationMgr().getCenterY(displayId);
         }
-        return 0;
     }
 
     /**
-     * Return the magnification bounds of the current controlling magnification on the given
-     * display. If the magnifier is not enabled, it returns an empty region.
-     * If the service can control magnification but fullscreen magnifier is not registered, it will
-     * register the magnifier for this call then unregister the magnifier finally to make
-     * the magnification region correct.
+     * Returns the magnification bounds of full-screen magnification on the given display.
      *
      * @param displayId The logical display id
      * @param outRegion the region to populate
      * @param canControlMagnification Whether the service can control magnification
-     * @return outRegion the magnification bounds of full-screen magnifier or the magnification
-     * source bounds of window magnifier
      */
-    public Region getMagnificationRegion(int displayId, @NonNull Region outRegion,
-            boolean canControlMagnification) {
-        int mode = getControllingMode(displayId);
-        if (mode == MAGNIFICATION_MODE_FULLSCREEN) {
-            getFullscreenMagnificationRegion(displayId, outRegion, canControlMagnification);
-        } else if (mode == MAGNIFICATION_MODE_WINDOW) {
-            mController.getWindowMagnificationMgr().getMagnificationSourceBounds(displayId,
-                    outRegion);
-        }
-        return outRegion;
-    }
-
-    private void getFullscreenMagnificationRegion(int displayId, @NonNull Region outRegion,
+    public void getFullscreenMagnificationRegion(int displayId, @NonNull Region outRegion,
             boolean canControlMagnification) {
         boolean registeredJustForThisCall = registerDisplayMagnificationIfNeeded(displayId,
                 canControlMagnification);
@@ -246,21 +222,9 @@
         }
     }
 
-    private boolean setScaleAndCenterForFullScreenMagnification(int displayId, float scale,
-            float centerX, float centerY,
-            boolean animate, int id) {
-        if (!isRegistered(displayId)) {
-            register(displayId);
-        }
-        return mController.getFullScreenMagnificationController().setScaleAndCenter(
-                displayId,
-                scale,
-                centerX, centerY, animate, id);
-    }
-
     /**
-     * Resets the magnification on the given display. The reset mode could be full-screen or
-     * window if it is activated.
+     * Resets the current magnification on the given display. The reset mode could be
+     * full-screen or window if it is activated.
      *
      * @param displayId The logical display id.
      * @param animate   {@code true} to animate the transition, {@code false}
@@ -268,7 +232,7 @@
      * @return {@code true} if the magnification spec changed, {@code false} if
      * the spec did not change
      */
-    public boolean reset(int displayId, boolean animate) {
+    public boolean resetCurrentMagnification(int displayId, boolean animate) {
         int mode = getControllingMode(displayId);
         if (mode == MAGNIFICATION_MODE_FULLSCREEN) {
             return mController.getFullScreenMagnificationController().reset(displayId, animate);
@@ -279,6 +243,19 @@
     }
 
     /**
+     * Resets the full-screen magnification on the given display.
+     *
+     * @param displayId The logical display id.
+     * @param animate   {@code true} to animate the transition, {@code false}
+     *                  to transition immediately
+     * @return {@code true} if the magnification spec changed, {@code false} if
+     * the spec did not change
+     */
+    public boolean resetFullscreenMagnification(int displayId, boolean animate) {
+        return mController.getFullScreenMagnificationController().reset(displayId, animate);
+    }
+
+    /**
      * {@link FullScreenMagnificationController#resetIfNeeded(int, boolean)}
      */
     // TODO: support window magnification
diff --git a/services/backup/java/com/android/server/backup/KeyValueAdbBackupEngine.java b/services/backup/java/com/android/server/backup/KeyValueAdbBackupEngine.java
index 0855b9d..0fe90b1 100644
--- a/services/backup/java/com/android/server/backup/KeyValueAdbBackupEngine.java
+++ b/services/backup/java/com/android/server/backup/KeyValueAdbBackupEngine.java
@@ -6,7 +6,6 @@
 import static android.os.ParcelFileDescriptor.MODE_TRUNCATE;
 
 import static com.android.server.backup.UserBackupManagerService.BACKUP_MANIFEST_FILENAME;
-import static com.android.server.backup.UserBackupManagerService.OP_TYPE_BACKUP_WAIT;
 
 import android.app.ApplicationThreadConstants;
 import android.app.IBackupAgent;
@@ -22,6 +21,7 @@
 import android.os.SELinux;
 import android.util.Slog;
 
+import com.android.server.backup.OperationStorage.OpType;
 import com.android.server.backup.fullbackup.AppMetadataBackupWriter;
 import com.android.server.backup.remote.ServiceBackupCallback;
 import com.android.server.backup.utils.FullBackupUtils;
@@ -162,7 +162,7 @@
         long kvBackupAgentTimeoutMillis = mAgentTimeoutParameters.getKvBackupAgentTimeoutMillis();
         try {
             mBackupManagerService.prepareOperationTimeout(token, kvBackupAgentTimeoutMillis, null,
-                    OP_TYPE_BACKUP_WAIT);
+                    OpType.BACKUP_WAIT);
 
             IBackupCallback callback =
                     new ServiceBackupCallback(
@@ -262,7 +262,7 @@
             pipes = ParcelFileDescriptor.createPipe();
 
             mBackupManagerService.prepareOperationTimeout(token, kvBackupAgentTimeoutMillis, null,
-                    OP_TYPE_BACKUP_WAIT);
+                    OpType.BACKUP_WAIT);
 
             // We will have to create a runnable that will read the manifest and backup data we
             // created, such that we can pipe the data into mOutput. The reason we do this is that
diff --git a/services/backup/java/com/android/server/backup/UserBackupManagerService.java b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
index 98ea03e..81d6381 100644
--- a/services/backup/java/com/android/server/backup/UserBackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
@@ -107,12 +107,14 @@
 import com.android.server.AppWidgetBackupBridge;
 import com.android.server.EventLogTags;
 import com.android.server.LocalServices;
+import com.android.server.backup.OperationStorage.OpState;
+import com.android.server.backup.OperationStorage.OpType;
 import com.android.server.backup.fullbackup.FullBackupEntry;
 import com.android.server.backup.fullbackup.PerformFullTransportBackupTask;
 import com.android.server.backup.internal.BackupHandler;
 import com.android.server.backup.internal.ClearDataObserver;
+import com.android.server.backup.internal.LifecycleOperationStorage;
 import com.android.server.backup.internal.OnTaskFinishedListener;
-import com.android.server.backup.internal.Operation;
 import com.android.server.backup.internal.PerformInitializeTask;
 import com.android.server.backup.internal.RunInitializeReceiver;
 import com.android.server.backup.internal.SetupObserver;
@@ -287,21 +289,6 @@
     private static final String BACKUP_FINISHED_ACTION = "android.intent.action.BACKUP_FINISHED";
     private static final String BACKUP_FINISHED_PACKAGE_EXTRA = "packageName";
 
-    // Bookkeeping of in-flight operations. The operation token is the index of the entry in the
-    // pending operations list.
-    public static final int OP_PENDING = 0;
-    private static final int OP_ACKNOWLEDGED = 1;
-    private static final int OP_TIMEOUT = -1;
-
-    // Waiting for backup agent to respond during backup operation.
-    public static final int OP_TYPE_BACKUP_WAIT = 0;
-
-    // Waiting for backup agent to respond during restore operation.
-    public static final int OP_TYPE_RESTORE_WAIT = 1;
-
-    // An entire backup operation spanning multiple packages.
-    public static final int OP_TYPE_BACKUP = 2;
-
     // Time delay for initialization operations that can be delayed so as not to consume too much
     // CPU on bring-up and increase time-to-UI.
     private static final long INITIALIZATION_DELAY_MILLIS = 3000;
@@ -400,30 +387,8 @@
 
     private ActiveRestoreSession mActiveRestoreSession;
 
-    /**
-     * mCurrentOperations contains the list of currently active operations.
-     *
-     * If type of operation is OP_TYPE_WAIT, it are waiting for an ack or timeout.
-     * An operation wraps a BackupRestoreTask within it.
-     * It's the responsibility of this task to remove the operation from this array.
-     *
-     * A BackupRestore task gets notified of ack/timeout for the operation via
-     * BackupRestoreTask#handleCancel, BackupRestoreTask#operationComplete and notifyAll called
-     * on the mCurrentOpLock.
-     * {@link UserBackupManagerService#waitUntilOperationComplete(int)} is
-     * used in various places to 'wait' for notifyAll and detect change of pending state of an
-     * operation. So typically, an operation will be removed from this array by:
-     * - BackupRestoreTask#handleCancel and
-     * - BackupRestoreTask#operationComplete OR waitUntilOperationComplete. Do not remove at both
-     * these places because waitUntilOperationComplete relies on the operation being present to
-     * determine its completion status.
-     *
-     * If type of operation is OP_BACKUP, it is a task running backups. It provides a handle to
-     * cancel backup tasks.
-     */
-    @GuardedBy("mCurrentOpLock")
-    private final SparseArray<Operation> mCurrentOperations = new SparseArray<>();
-    private final Object mCurrentOpLock = new Object();
+    private final LifecycleOperationStorage mOperationStorage;
+
     private final Random mTokenGenerator = new Random();
     private final AtomicInteger mNextToken = new AtomicInteger();
 
@@ -542,12 +507,14 @@
     }
 
     @VisibleForTesting
-    UserBackupManagerService(Context context, PackageManager packageManager) {
+    UserBackupManagerService(Context context, PackageManager packageManager,
+            LifecycleOperationStorage operationStorage) {
         mContext = context;
 
         mUserId = 0;
         mRegisterTransportsRequestedTime = 0;
         mPackageManager = packageManager;
+        mOperationStorage = operationStorage;
 
         mBaseStateDir = null;
         mDataDir = null;
@@ -600,8 +567,10 @@
                 BackupAgentTimeoutParameters(Handler.getMain(), mContext.getContentResolver());
         mAgentTimeoutParameters.start();
 
+        mOperationStorage = new LifecycleOperationStorage(mUserId);
+
         Objects.requireNonNull(userBackupThread, "userBackupThread cannot be null");
-        mBackupHandler = new BackupHandler(this, userBackupThread);
+        mBackupHandler = new BackupHandler(this, mOperationStorage, userBackupThread);
 
         // Set up our bookkeeping
         final ContentResolver resolver = context.getContentResolver();
@@ -756,6 +725,10 @@
         return mTransportManager;
     }
 
+    public OperationStorage getOperationStorage() {
+        return mOperationStorage;
+    }
+
     public boolean isEnabled() {
         return mEnabled;
     }
@@ -838,14 +811,6 @@
         return mActiveRestoreSession;
     }
 
-    public SparseArray<Operation> getCurrentOperations() {
-        return mCurrentOperations;
-    }
-
-    public Object getCurrentOpLock() {
-        return mCurrentOpLock;
-    }
-
     public SparseArray<AdbParams> getAdbBackupRestoreConfirmations() {
         return mAdbBackupRestoreConfirmations;
     }
@@ -1987,18 +1952,12 @@
         }
         final long oldToken = Binder.clearCallingIdentity();
         try {
-            List<Integer> operationsToCancel = new ArrayList<>();
-            synchronized (mCurrentOpLock) {
-                for (int i = 0; i < mCurrentOperations.size(); i++) {
-                    Operation op = mCurrentOperations.valueAt(i);
-                    int token = mCurrentOperations.keyAt(i);
-                    if (op.type == OP_TYPE_BACKUP) {
-                        operationsToCancel.add(token);
-                    }
-                }
-            }
+            Set<Integer> operationsToCancel =
+                    mOperationStorage.operationTokensForOpType(OpType.BACKUP);
+
             for (Integer token : operationsToCancel) {
-                handleCancel(token, true /* cancelAll */);
+                mOperationStorage.cancelOperation(token, /* cancelAll */ true,
+                        operationType -> { /* no callback needed here */ });
             }
             // We don't want the backup jobs to kick in any time soon.
             // Reschedules them to run in the distant future.
@@ -2012,7 +1971,7 @@
     /** Schedule a timeout message for the operation identified by {@code token}. */
     public void prepareOperationTimeout(int token, long interval, BackupRestoreTask callback,
             int operationType) {
-        if (operationType != OP_TYPE_BACKUP_WAIT && operationType != OP_TYPE_RESTORE_WAIT) {
+        if (operationType != OpType.BACKUP_WAIT && operationType != OpType.RESTORE_WAIT) {
             Slog.wtf(
                     TAG,
                     addUserIdToLogMessage(
@@ -2036,19 +1995,17 @@
                                     + callback));
         }
 
-        synchronized (mCurrentOpLock) {
-            mCurrentOperations.put(token, new Operation(OP_PENDING, callback, operationType));
-            Message msg = mBackupHandler.obtainMessage(getMessageIdForOperationType(operationType),
-                    token, 0, callback);
-            mBackupHandler.sendMessageDelayed(msg, interval);
-        }
+        mOperationStorage.registerOperation(token, OpState.PENDING, callback, operationType);
+        Message msg = mBackupHandler.obtainMessage(getMessageIdForOperationType(operationType),
+                token, 0, callback);
+        mBackupHandler.sendMessageDelayed(msg, interval);
     }
 
     private int getMessageIdForOperationType(int operationType) {
         switch (operationType) {
-            case OP_TYPE_BACKUP_WAIT:
+            case OpType.BACKUP_WAIT:
                 return MSG_BACKUP_OPERATION_TIMEOUT;
-            case OP_TYPE_RESTORE_WAIT:
+            case OpType.RESTORE_WAIT:
                 return MSG_RESTORE_OPERATION_TIMEOUT;
             default:
                 Slog.wtf(
@@ -2061,162 +2018,28 @@
         }
     }
 
-    /**
-     * Add an operation to the list of currently running operations. Used for cancellation,
-     * completion and timeout callbacks that act on the operation via the {@code token}.
-     */
-    public void putOperation(int token, Operation operation) {
-        if (MORE_DEBUG) {
-            Slog.d(
-                    TAG,
-                    addUserIdToLogMessage(
-                            mUserId,
-                            "Adding operation token="
-                                    + Integer.toHexString(token)
-                                    + ", operation type="
-                                    + operation.type));
-        }
-        synchronized (mCurrentOpLock) {
-            mCurrentOperations.put(token, operation);
-        }
-    }
-
-    /**
-     * Remove an operation from the list of currently running operations. An operation is removed
-     * when it is completed, cancelled, or timed out, and thus no longer running.
-     */
-    public void removeOperation(int token) {
-        if (MORE_DEBUG) {
-            Slog.d(
-                    TAG,
-                    addUserIdToLogMessage(
-                            mUserId, "Removing operation token=" + Integer.toHexString(token)));
-        }
-        synchronized (mCurrentOpLock) {
-            if (mCurrentOperations.get(token) == null) {
-                Slog.w(TAG, addUserIdToLogMessage(mUserId, "Duplicate remove for operation. token="
-                        + Integer.toHexString(token)));
-            }
-            mCurrentOperations.remove(token);
-        }
-    }
-
     /** Block until we received an operation complete message (from the agent or cancellation). */
     public boolean waitUntilOperationComplete(int token) {
-        if (MORE_DEBUG) {
-            Slog.i(TAG, addUserIdToLogMessage(mUserId, "Blocking until operation complete for "
-                    + Integer.toHexString(token)));
-        }
-        int finalState = OP_PENDING;
-        Operation op = null;
-        synchronized (mCurrentOpLock) {
-            while (true) {
-                op = mCurrentOperations.get(token);
-                if (op == null) {
-                    // mysterious disappearance: treat as success with no callback
-                    break;
-                } else {
-                    if (op.state == OP_PENDING) {
-                        try {
-                            mCurrentOpLock.wait();
-                        } catch (InterruptedException e) {
-                        }
-                        // When the wait is notified we loop around and recheck the current state
-                    } else {
-                        if (MORE_DEBUG) {
-                            Slog.d(
-                                    TAG,
-                                    addUserIdToLogMessage(
-                                            mUserId,
-                                            "Unblocked waiting for operation token="
-                                                    + Integer.toHexString(token)));
-                        }
-                        // No longer pending; we're done
-                        finalState = op.state;
-                        break;
-                    }
-                }
-            }
-        }
-
-        removeOperation(token);
-        if (op != null) {
-            mBackupHandler.removeMessages(getMessageIdForOperationType(op.type));
-        }
-        if (MORE_DEBUG) {
-            Slog.v(TAG, addUserIdToLogMessage(mUserId, "operation " + Integer.toHexString(token)
-                    + " complete: finalState=" + finalState));
-        }
-        return finalState == OP_ACKNOWLEDGED;
+        return mOperationStorage.waitUntilOperationComplete(token, operationType -> {
+            mBackupHandler.removeMessages(getMessageIdForOperationType(operationType));
+        });
     }
 
     /** Cancel the operation associated with {@code token}. */
     public void handleCancel(int token, boolean cancelAll) {
-        // Notify any synchronous waiters
-        Operation op = null;
-        synchronized (mCurrentOpLock) {
-            op = mCurrentOperations.get(token);
-            if (MORE_DEBUG) {
-                if (op == null) {
-                    Slog.w(
-                            TAG,
-                            addUserIdToLogMessage(
-                                    mUserId,
-                                    "Cancel of token "
-                                            + Integer.toHexString(token)
-                                            + " but no op found"));
-                }
+        // Remove all pending timeout messages of types OpType.BACKUP_WAIT and
+        // OpType.RESTORE_WAIT. On the other hand, OP_TYPE_BACKUP cannot time out and
+        // doesn't require cancellation.
+        mOperationStorage.cancelOperation(token, cancelAll, operationType -> {
+            if (operationType == OpType.BACKUP_WAIT || operationType == OpType.RESTORE_WAIT) {
+                mBackupHandler.removeMessages(getMessageIdForOperationType(operationType));
             }
-            int state = (op != null) ? op.state : OP_TIMEOUT;
-            if (state == OP_ACKNOWLEDGED) {
-                // The operation finished cleanly, so we have nothing more to do.
-                if (DEBUG) {
-                    Slog.w(TAG, addUserIdToLogMessage(mUserId, "Operation already got an ack."
-                            + "Should have been removed from mCurrentOperations."));
-                }
-                op = null;
-                mCurrentOperations.delete(token);
-            } else if (state == OP_PENDING) {
-                if (DEBUG) {
-                    Slog.v(
-                            TAG,
-                            addUserIdToLogMessage(
-                                    mUserId, "Cancel: token=" + Integer.toHexString(token)));
-                }
-                op.state = OP_TIMEOUT;
-                // Can't delete op from mCurrentOperations here. waitUntilOperationComplete may be
-                // called after we receive cancel here. We need this op's state there.
-
-                // Remove all pending timeout messages of types OP_TYPE_BACKUP_WAIT and
-                // OP_TYPE_RESTORE_WAIT. On the other hand, OP_TYPE_BACKUP cannot time out and
-                // doesn't require cancellation.
-                if (op.type == OP_TYPE_BACKUP_WAIT || op.type == OP_TYPE_RESTORE_WAIT) {
-                    mBackupHandler.removeMessages(getMessageIdForOperationType(op.type));
-                }
-            }
-            mCurrentOpLock.notifyAll();
-        }
-
-        // If there's a TimeoutHandler for this event, call it
-        if (op != null && op.callback != null) {
-            if (MORE_DEBUG) {
-                Slog.v(TAG, addUserIdToLogMessage(mUserId, "   Invoking cancel on " + op.callback));
-            }
-            op.callback.handleCancel(cancelAll);
-        }
+        });
     }
 
     /** Returns {@code true} if a backup is currently running, else returns {@code false}. */
     public boolean isBackupOperationInProgress() {
-        synchronized (mCurrentOpLock) {
-            for (int i = 0; i < mCurrentOperations.size(); i++) {
-                Operation op = mCurrentOperations.valueAt(i);
-                if (op.type == OP_TYPE_BACKUP && op.state == OP_PENDING) {
-                    return true;
-                }
-            }
-        }
-        return false;
+        return mOperationStorage.isBackupOperationInProgress();
     }
 
     /** Unbind the backup agent and kill the app if it's a non-system app. */
@@ -2578,6 +2401,7 @@
             String[] pkg = new String[]{entry.packageName};
             mRunningFullBackupTask = PerformFullTransportBackupTask.newWithCurrentTransport(
                     this,
+                    mOperationStorage,
                     /* observer */ null,
                     pkg,
                     /* updateSchedule */ true,
@@ -3107,6 +2931,7 @@
                 CountDownLatch latch = new CountDownLatch(1);
                 Runnable task = PerformFullTransportBackupTask.newWithCurrentTransport(
                         this,
+                        mOperationStorage,
                         /* observer */ null,
                         pkgNames,
                         /* updateSchedule */ false,
@@ -4126,48 +3951,11 @@
      * outstanding asynchronous backup/restore operation.
      */
     public void opComplete(int token, long result) {
-        if (MORE_DEBUG) {
-            Slog.v(
-                    TAG,
-                    addUserIdToLogMessage(
-                            mUserId,
-                            "opComplete: " + Integer.toHexString(token) + " result=" + result));
-        }
-        Operation op = null;
-        synchronized (mCurrentOpLock) {
-            op = mCurrentOperations.get(token);
-            if (op != null) {
-                if (op.state == OP_TIMEOUT) {
-                    // The operation already timed out, and this is a late response.  Tidy up
-                    // and ignore it; we've already dealt with the timeout.
-                    op = null;
-                    mCurrentOperations.delete(token);
-                } else if (op.state == OP_ACKNOWLEDGED) {
-                    if (DEBUG) {
-                        Slog.w(
-                                TAG,
-                                addUserIdToLogMessage(
-                                        mUserId,
-                                        "Received duplicate ack for token="
-                                                + Integer.toHexString(token)));
-                    }
-                    op = null;
-                    mCurrentOperations.remove(token);
-                } else if (op.state == OP_PENDING) {
-                    // Can't delete op from mCurrentOperations. waitUntilOperationComplete can be
-                    // called after we we receive this call.
-                    op.state = OP_ACKNOWLEDGED;
-                }
-            }
-            mCurrentOpLock.notifyAll();
-        }
-
-        // The completion callback, if any, is invoked on the handler
-        if (op != null && op.callback != null) {
-            Pair<BackupRestoreTask, Long> callbackAndResult = Pair.create(op.callback, result);
+        mOperationStorage.onOperationComplete(token, result, callback -> {
+            Pair<BackupRestoreTask, Long> callbackAndResult = Pair.create(callback, result);
             Message msg = mBackupHandler.obtainMessage(MSG_OP_COMPLETE, callbackAndResult);
             mBackupHandler.sendMessage(msg);
-        }
+        });
     }
 
     /** Checks if the package is eligible for backup. */
diff --git a/services/backup/java/com/android/server/backup/fullbackup/FullBackupEngine.java b/services/backup/java/com/android/server/backup/fullbackup/FullBackupEngine.java
index fe5497f..1e1ca95 100644
--- a/services/backup/java/com/android/server/backup/fullbackup/FullBackupEngine.java
+++ b/services/backup/java/com/android/server/backup/fullbackup/FullBackupEngine.java
@@ -21,7 +21,6 @@
 import static com.android.server.backup.BackupManagerService.TAG;
 import static com.android.server.backup.UserBackupManagerService.BACKUP_MANIFEST_FILENAME;
 import static com.android.server.backup.UserBackupManagerService.BACKUP_METADATA_FILENAME;
-import static com.android.server.backup.UserBackupManagerService.OP_TYPE_BACKUP_WAIT;
 import static com.android.server.backup.UserBackupManagerService.SHARED_BACKUP_AGENT_PACKAGE;
 
 import android.annotation.UserIdInt;
@@ -39,6 +38,7 @@
 import com.android.server.AppWidgetBackupBridge;
 import com.android.server.backup.BackupAgentTimeoutParameters;
 import com.android.server.backup.BackupRestoreTask;
+import com.android.server.backup.OperationStorage.OpType;
 import com.android.server.backup.UserBackupManagerService;
 import com.android.server.backup.remote.RemoteCall;
 import com.android.server.backup.utils.BackupEligibilityRules;
@@ -147,7 +147,7 @@
                         mToken,
                         timeout,
                         mTimeoutMonitor /* in parent class */,
-                        OP_TYPE_BACKUP_WAIT);
+                        OpType.BACKUP_WAIT);
                 mAgent.doFullBackup(
                         mPipe,
                         mQuota,
diff --git a/services/backup/java/com/android/server/backup/fullbackup/FullBackupObbConnection.java b/services/backup/java/com/android/server/backup/fullbackup/FullBackupObbConnection.java
index aaf1f0a..be6ac26 100644
--- a/services/backup/java/com/android/server/backup/fullbackup/FullBackupObbConnection.java
+++ b/services/backup/java/com/android/server/backup/fullbackup/FullBackupObbConnection.java
@@ -18,7 +18,6 @@
 
 import static com.android.server.backup.BackupManagerService.MORE_DEBUG;
 import static com.android.server.backup.BackupManagerService.TAG;
-import static com.android.server.backup.UserBackupManagerService.OP_TYPE_BACKUP_WAIT;
 
 import android.app.backup.IBackupManager;
 import android.content.ComponentName;
@@ -33,6 +32,7 @@
 
 import com.android.internal.backup.IObbBackupService;
 import com.android.server.backup.BackupAgentTimeoutParameters;
+import com.android.server.backup.OperationStorage.OpType;
 import com.android.server.backup.UserBackupManagerService;
 import com.android.server.backup.utils.FullBackupUtils;
 
@@ -83,7 +83,7 @@
             long fullBackupAgentTimeoutMillis =
                     mAgentTimeoutParameters.getFullBackupAgentTimeoutMillis();
             backupManagerService.prepareOperationTimeout(
-                    token, fullBackupAgentTimeoutMillis, null, OP_TYPE_BACKUP_WAIT);
+                    token, fullBackupAgentTimeoutMillis, null, OpType.BACKUP_WAIT);
             mService.backupObbs(pkg.packageName, pipes[1], token,
                     backupManagerService.getBackupManagerBinder());
             FullBackupUtils.routeSocketDataToOutput(pipes[0], out);
diff --git a/services/backup/java/com/android/server/backup/fullbackup/PerformAdbBackupTask.java b/services/backup/java/com/android/server/backup/fullbackup/PerformAdbBackupTask.java
index 448e086..7ee307e 100644
--- a/services/backup/java/com/android/server/backup/fullbackup/PerformAdbBackupTask.java
+++ b/services/backup/java/com/android/server/backup/fullbackup/PerformAdbBackupTask.java
@@ -37,6 +37,7 @@
 import com.android.server.AppWidgetBackupBridge;
 import com.android.server.backup.BackupRestoreTask;
 import com.android.server.backup.KeyValueAdbBackupEngine;
+import com.android.server.backup.OperationStorage;
 import com.android.server.backup.UserBackupManagerService;
 import com.android.server.backup.utils.BackupEligibilityRules;
 import com.android.server.backup.utils.PasswordUtils;
@@ -67,6 +68,7 @@
 public class PerformAdbBackupTask extends FullBackupTask implements BackupRestoreTask {
 
     private final UserBackupManagerService mUserBackupManagerService;
+    private final OperationStorage mOperationStorage;
     private final AtomicBoolean mLatch;
 
     private final ParcelFileDescriptor mOutputFile;
@@ -85,7 +87,8 @@
     private final int mCurrentOpToken;
     private final BackupEligibilityRules mBackupEligibilityRules;
 
-    public PerformAdbBackupTask(UserBackupManagerService backupManagerService,
+    public PerformAdbBackupTask(
+            UserBackupManagerService backupManagerService, OperationStorage operationStorage,
             ParcelFileDescriptor fd, IFullBackupRestoreObserver observer,
             boolean includeApks, boolean includeObbs, boolean includeShared, boolean doWidgets,
             String curPassword, String encryptPassword, boolean doAllApps, boolean doSystem,
@@ -93,6 +96,7 @@
             BackupEligibilityRules backupEligibilityRules) {
         super(observer);
         mUserBackupManagerService = backupManagerService;
+        mOperationStorage = operationStorage;
         mCurrentOpToken = backupManagerService.generateRandomIntegerToken();
         mLatch = latch;
 
@@ -505,6 +509,6 @@
         if (target != null) {
             mUserBackupManagerService.tearDownAgentAndKill(mCurrentTarget.applicationInfo);
         }
-        mUserBackupManagerService.removeOperation(mCurrentOpToken);
+        mOperationStorage.removeOperation(mCurrentOpToken);
     }
 }
diff --git a/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java b/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java
index 9ce4eab..0ca77d1 100644
--- a/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java
+++ b/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java
@@ -19,9 +19,6 @@
 import static com.android.server.backup.BackupManagerService.DEBUG;
 import static com.android.server.backup.BackupManagerService.DEBUG_SCHEDULING;
 import static com.android.server.backup.BackupManagerService.MORE_DEBUG;
-import static com.android.server.backup.UserBackupManagerService.OP_PENDING;
-import static com.android.server.backup.UserBackupManagerService.OP_TYPE_BACKUP;
-import static com.android.server.backup.UserBackupManagerService.OP_TYPE_BACKUP_WAIT;
 
 import android.annotation.Nullable;
 import android.app.IBackupAgent;
@@ -45,10 +42,12 @@
 import com.android.server.backup.BackupAgentTimeoutParameters;
 import com.android.server.backup.BackupRestoreTask;
 import com.android.server.backup.FullBackupJob;
+import com.android.server.backup.OperationStorage;
+import com.android.server.backup.OperationStorage.OpState;
+import com.android.server.backup.OperationStorage.OpType;
 import com.android.server.backup.TransportManager;
 import com.android.server.backup.UserBackupManagerService;
 import com.android.server.backup.internal.OnTaskFinishedListener;
-import com.android.server.backup.internal.Operation;
 import com.android.server.backup.remote.RemoteCall;
 import com.android.server.backup.transport.BackupTransportClient;
 import com.android.server.backup.transport.TransportConnection;
@@ -99,6 +98,7 @@
 public class PerformFullTransportBackupTask extends FullBackupTask implements BackupRestoreTask {
     public static PerformFullTransportBackupTask newWithCurrentTransport(
             UserBackupManagerService backupManagerService,
+            OperationStorage operationStorage,
             IFullBackupRestoreObserver observer,
             String[] whichPackages,
             boolean updateSchedule,
@@ -118,6 +118,7 @@
                                 listenerCaller);
         return new PerformFullTransportBackupTask(
                 backupManagerService,
+                operationStorage,
                 transportConnection,
                 observer,
                 whichPackages,
@@ -136,6 +137,7 @@
     private UserBackupManagerService mUserBackupManagerService;
     private final Object mCancelLock = new Object();
 
+    OperationStorage mOperationStorage;
     List<PackageInfo> mPackages;
     PackageInfo mCurrentPackage;
     boolean mUpdateSchedule;
@@ -158,6 +160,7 @@
     private final BackupEligibilityRules mBackupEligibilityRules;
 
     public PerformFullTransportBackupTask(UserBackupManagerService backupManagerService,
+            OperationStorage operationStorage,
             TransportConnection transportConnection,
             IFullBackupRestoreObserver observer,
             String[] whichPackages, boolean updateSchedule,
@@ -165,7 +168,8 @@
             @Nullable IBackupManagerMonitor monitor, @Nullable OnTaskFinishedListener listener,
             boolean userInitiated, BackupEligibilityRules backupEligibilityRules) {
         super(observer);
-        this.mUserBackupManagerService = backupManagerService;
+        mUserBackupManagerService = backupManagerService;
+        mOperationStorage = operationStorage;
         mTransportConnection = transportConnection;
         mUpdateSchedule = updateSchedule;
         mLatch = latch;
@@ -261,16 +265,13 @@
     }
 
     private void registerTask() {
-        synchronized (mUserBackupManagerService.getCurrentOpLock()) {
-            Slog.d(TAG, "backupmanager pftbt token=" + Integer.toHexString(mCurrentOpToken));
-            mUserBackupManagerService.getCurrentOperations().put(
-                    mCurrentOpToken,
-                    new Operation(OP_PENDING, this, OP_TYPE_BACKUP));
-        }
+        Slog.d(TAG, "backupmanager pftbt token=" + Integer.toHexString(mCurrentOpToken));
+        mOperationStorage.registerOperation(mCurrentOpToken, OpState.PENDING, this, OpType.BACKUP);
     }
 
+    // public, because called from KeyValueBackupTask.finishTask.
     public void unregisterTask() {
-        mUserBackupManagerService.removeOperation(mCurrentOpToken);
+        mOperationStorage.removeOperation(mCurrentOpToken);
     }
 
     @Override
@@ -722,7 +723,7 @@
                     mAgentTimeoutParameters.getFullBackupAgentTimeoutMillis();
             try {
                 mUserBackupManagerService.prepareOperationTimeout(
-                        mCurrentOpToken, fullBackupAgentTimeoutMillis, this, OP_TYPE_BACKUP_WAIT);
+                        mCurrentOpToken, fullBackupAgentTimeoutMillis, this, OpType.BACKUP_WAIT);
                 if (MORE_DEBUG) {
                     Slog.d(TAG, "Preflighting full payload of " + pkg.packageName);
                 }
@@ -777,7 +778,7 @@
             }
             mResult.set(result);
             mLatch.countDown();
-            mUserBackupManagerService.removeOperation(mCurrentOpToken);
+            mOperationStorage.removeOperation(mCurrentOpToken);
         }
 
         @Override
@@ -787,7 +788,7 @@
             }
             mResult.set(BackupTransport.AGENT_ERROR);
             mLatch.countDown();
-            mUserBackupManagerService.removeOperation(mCurrentOpToken);
+            mOperationStorage.removeOperation(mCurrentOpToken);
         }
 
         @Override
@@ -837,16 +838,12 @@
         }
 
         void registerTask() {
-            synchronized (mUserBackupManagerService.getCurrentOpLock()) {
-                mUserBackupManagerService.getCurrentOperations().put(
-                        mCurrentOpToken, new Operation(OP_PENDING, this, OP_TYPE_BACKUP_WAIT));
-            }
+            mOperationStorage.registerOperation(mCurrentOpToken,
+                    OpState.PENDING, this, OpType.BACKUP_WAIT);
         }
 
         void unregisterTask() {
-            synchronized (mUserBackupManagerService.getCurrentOpLock()) {
-                mUserBackupManagerService.getCurrentOperations().remove(mCurrentOpToken);
-            }
+            mOperationStorage.removeOperation(mCurrentOpToken);
         }
 
         @Override
@@ -956,7 +953,7 @@
             mPreflightLatch.countDown();
             mBackupLatch.countDown();
             // We are done with this operation.
-            mUserBackupManagerService.removeOperation(mCurrentOpToken);
+            mOperationStorage.removeOperation(mCurrentOpToken);
         }
     }
 }
diff --git a/services/backup/java/com/android/server/backup/internal/BackupHandler.java b/services/backup/java/com/android/server/backup/internal/BackupHandler.java
index 5c24859..03796ea 100644
--- a/services/backup/java/com/android/server/backup/internal/BackupHandler.java
+++ b/services/backup/java/com/android/server/backup/internal/BackupHandler.java
@@ -35,6 +35,7 @@
 import com.android.server.backup.BackupAgentTimeoutParameters;
 import com.android.server.backup.BackupRestoreTask;
 import com.android.server.backup.DataChangedJournal;
+import com.android.server.backup.OperationStorage;
 import com.android.server.backup.TransportManager;
 import com.android.server.backup.UserBackupManagerService;
 import com.android.server.backup.fullbackup.PerformAdbBackupTask;
@@ -84,6 +85,7 @@
     public static final int MSG_STOP = 22;
 
     private final UserBackupManagerService backupManagerService;
+    private final OperationStorage mOperationStorage;
     private final BackupAgentTimeoutParameters mAgentTimeoutParameters;
 
     private final HandlerThread mBackupThread;
@@ -92,10 +94,12 @@
     volatile boolean mIsStopping = false;
 
     public BackupHandler(
-            UserBackupManagerService backupManagerService, HandlerThread backupThread) {
+            UserBackupManagerService backupManagerService, OperationStorage operationStorage,
+            HandlerThread backupThread) {
         super(backupThread.getLooper());
         mBackupThread = backupThread;
         this.backupManagerService = backupManagerService;
+        mOperationStorage = operationStorage;
         mAgentTimeoutParameters = Objects.requireNonNull(
                 backupManagerService.getAgentTimeoutParameters(),
                 "Timeout parameters cannot be null");
@@ -215,6 +219,7 @@
                                                         caller);
                         KeyValueBackupTask.start(
                                 backupManagerService,
+                                mOperationStorage,
                                 transportConnection,
                                 transport.transportDirName(),
                                 queue,
@@ -278,8 +283,8 @@
                 // TODO: refactor full backup to be a looper-based state machine
                 // similar to normal backup/restore.
                 AdbBackupParams params = (AdbBackupParams) msg.obj;
-                PerformAdbBackupTask task = new PerformAdbBackupTask(backupManagerService,
-                        params.fd,
+                PerformAdbBackupTask task = new PerformAdbBackupTask(
+                        backupManagerService, mOperationStorage, params.fd,
                         params.observer, params.includeApks, params.includeObbs,
                         params.includeShared, params.doWidgets, params.curPassword,
                         params.encryptPassword, params.allApps, params.includeSystem,
@@ -296,6 +301,7 @@
                 PerformUnifiedRestoreTask task =
                         new PerformUnifiedRestoreTask(
                                 backupManagerService,
+                                mOperationStorage,
                                 params.mTransportConnection,
                                 params.observer,
                                 params.monitor,
@@ -332,7 +338,7 @@
                 // similar to normal backup/restore.
                 AdbRestoreParams params = (AdbRestoreParams) msg.obj;
                 PerformAdbRestoreTask task = new PerformAdbRestoreTask(backupManagerService,
-                        params.fd,
+                        mOperationStorage, params.fd,
                         params.curPassword, params.encryptPassword,
                         params.observer, params.latch);
                 (new Thread(task, "adb-restore")).start();
@@ -459,6 +465,7 @@
 
                 KeyValueBackupTask.start(
                         backupManagerService,
+                        mOperationStorage,
                         params.mTransportConnection,
                         params.dirName,
                         params.kvPackages,
diff --git a/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java b/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java
index 30da8c1..16aa4eb 100644
--- a/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java
+++ b/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java
@@ -23,8 +23,6 @@
 import static android.os.ParcelFileDescriptor.MODE_TRUNCATE;
 
 import static com.android.server.backup.UserBackupManagerService.KEY_WIDGET_STATE;
-import static com.android.server.backup.UserBackupManagerService.OP_PENDING;
-import static com.android.server.backup.UserBackupManagerService.OP_TYPE_BACKUP;
 
 import android.annotation.IntDef;
 import android.annotation.Nullable;
@@ -57,10 +55,12 @@
 import com.android.server.backup.BackupRestoreTask;
 import com.android.server.backup.DataChangedJournal;
 import com.android.server.backup.KeyValueBackupJob;
+import com.android.server.backup.OperationStorage;
+import com.android.server.backup.OperationStorage.OpState;
+import com.android.server.backup.OperationStorage.OpType;
 import com.android.server.backup.UserBackupManagerService;
 import com.android.server.backup.fullbackup.PerformFullTransportBackupTask;
 import com.android.server.backup.internal.OnTaskFinishedListener;
-import com.android.server.backup.internal.Operation;
 import com.android.server.backup.remote.RemoteCall;
 import com.android.server.backup.remote.RemoteCallable;
 import com.android.server.backup.remote.RemoteResult;
@@ -211,6 +211,7 @@
      */
     public static KeyValueBackupTask start(
             UserBackupManagerService backupManagerService,
+            OperationStorage operationStorage,
             TransportConnection transportConnection,
             String transportDirName,
             List<String> queue,
@@ -227,6 +228,7 @@
         KeyValueBackupTask task =
                 new KeyValueBackupTask(
                         backupManagerService,
+                        operationStorage,
                         transportConnection,
                         transportDirName,
                         queue,
@@ -244,6 +246,7 @@
     }
 
     private final UserBackupManagerService mBackupManagerService;
+    private final OperationStorage mOperationStorage;
     private final PackageManager mPackageManager;
     private final TransportConnection mTransportConnection;
     private final BackupAgentTimeoutParameters mAgentTimeoutParameters;
@@ -302,6 +305,7 @@
     @VisibleForTesting
     public KeyValueBackupTask(
             UserBackupManagerService backupManagerService,
+            OperationStorage operationStorage,
             TransportConnection transportConnection,
             String transportDirName,
             List<String> queue,
@@ -313,6 +317,7 @@
             boolean nonIncremental,
             BackupEligibilityRules backupEligibilityRules) {
         mBackupManagerService = backupManagerService;
+        mOperationStorage = operationStorage;
         mPackageManager = backupManagerService.getPackageManager();
         mTransportConnection = transportConnection;
         mOriginalQueue = queue;
@@ -338,12 +343,11 @@
     }
 
     private void registerTask() {
-        mBackupManagerService.putOperation(
-                mCurrentOpToken, new Operation(OP_PENDING, this, OP_TYPE_BACKUP));
+        mOperationStorage.registerOperation(mCurrentOpToken, OpState.PENDING, this, OpType.BACKUP);
     }
 
     private void unregisterTask() {
-        mBackupManagerService.removeOperation(mCurrentOpToken);
+        mOperationStorage.removeOperation(mCurrentOpToken);
     }
 
     @Override
@@ -639,6 +643,7 @@
     private PerformFullTransportBackupTask createFullBackupTask(List<String> packages) {
         return new PerformFullTransportBackupTask(
                 mBackupManagerService,
+                mOperationStorage,
                 mTransportConnection,
                 /* fullBackupRestoreObserver */ null,
                 packages.toArray(new String[packages.size()]),
diff --git a/services/backup/java/com/android/server/backup/restore/AdbRestoreFinishedLatch.java b/services/backup/java/com/android/server/backup/restore/AdbRestoreFinishedLatch.java
index 376b618..cfc0f20 100644
--- a/services/backup/java/com/android/server/backup/restore/AdbRestoreFinishedLatch.java
+++ b/services/backup/java/com/android/server/backup/restore/AdbRestoreFinishedLatch.java
@@ -23,6 +23,7 @@
 
 import com.android.server.backup.BackupAgentTimeoutParameters;
 import com.android.server.backup.BackupRestoreTask;
+import com.android.server.backup.OperationStorage;
 import com.android.server.backup.UserBackupManagerService;
 
 import java.util.Objects;
@@ -36,13 +37,16 @@
 
     private static final String TAG = "AdbRestoreFinishedLatch";
     private UserBackupManagerService backupManagerService;
+    private final OperationStorage mOperationStorage;
     final CountDownLatch mLatch;
     private final int mCurrentOpToken;
     private final BackupAgentTimeoutParameters mAgentTimeoutParameters;
 
     public AdbRestoreFinishedLatch(UserBackupManagerService backupManagerService,
+            OperationStorage operationStorage,
             int currentOpToken) {
         this.backupManagerService = backupManagerService;
+        mOperationStorage = operationStorage;
         mLatch = new CountDownLatch(1);
         mCurrentOpToken = currentOpToken;
         mAgentTimeoutParameters = Objects.requireNonNull(
@@ -72,7 +76,7 @@
             Slog.w(TAG, "adb onRestoreFinished() complete");
         }
         mLatch.countDown();
-        backupManagerService.removeOperation(mCurrentOpToken);
+        mOperationStorage.removeOperation(mCurrentOpToken);
     }
 
     @Override
@@ -81,6 +85,6 @@
             Slog.w(TAG, "adb onRestoreFinished() timed out");
         }
         mLatch.countDown();
-        backupManagerService.removeOperation(mCurrentOpToken);
+        mOperationStorage.removeOperation(mCurrentOpToken);
     }
 }
diff --git a/services/backup/java/com/android/server/backup/restore/FullRestoreEngine.java b/services/backup/java/com/android/server/backup/restore/FullRestoreEngine.java
index 5718bdf..76df8b9 100644
--- a/services/backup/java/com/android/server/backup/restore/FullRestoreEngine.java
+++ b/services/backup/java/com/android/server/backup/restore/FullRestoreEngine.java
@@ -21,7 +21,6 @@
 import static com.android.server.backup.BackupManagerService.TAG;
 import static com.android.server.backup.UserBackupManagerService.BACKUP_MANIFEST_FILENAME;
 import static com.android.server.backup.UserBackupManagerService.BACKUP_METADATA_FILENAME;
-import static com.android.server.backup.UserBackupManagerService.OP_TYPE_RESTORE_WAIT;
 import static com.android.server.backup.UserBackupManagerService.SHARED_BACKUP_AGENT_PACKAGE;
 import static com.android.server.backup.internal.BackupHandler.MSG_RESTORE_OPERATION_TIMEOUT;
 
@@ -48,6 +47,8 @@
 import com.android.server.backup.BackupRestoreTask;
 import com.android.server.backup.FileMetadata;
 import com.android.server.backup.KeyValueAdbRestoreEngine;
+import com.android.server.backup.OperationStorage;
+import com.android.server.backup.OperationStorage.OpType;
 import com.android.server.backup.UserBackupManagerService;
 import com.android.server.backup.fullbackup.FullBackupObbConnection;
 import com.android.server.backup.utils.BackupEligibilityRules;
@@ -71,6 +72,7 @@
 public class FullRestoreEngine extends RestoreEngine {
 
     private final UserBackupManagerService mBackupManagerService;
+    private final OperationStorage mOperationStorage;
     private final int mUserId;
 
     // Task in charge of monitoring timeouts
@@ -133,12 +135,14 @@
     private boolean mPipesClosed;
     private final BackupEligibilityRules mBackupEligibilityRules;
 
-    public FullRestoreEngine(UserBackupManagerService backupManagerService,
+    public FullRestoreEngine(
+            UserBackupManagerService backupManagerService, OperationStorage operationStorage,
             BackupRestoreTask monitorTask, IFullBackupRestoreObserver observer,
             IBackupManagerMonitor monitor, PackageInfo onlyPackage, boolean allowApks,
             int ephemeralOpToken, boolean isAdbRestore,
             BackupEligibilityRules backupEligibilityRules) {
         mBackupManagerService = backupManagerService;
+        mOperationStorage = operationStorage;
         mEphemeralOpToken = ephemeralOpToken;
         mMonitorTask = monitorTask;
         mObserver = observer;
@@ -409,7 +413,7 @@
                             mBackupManagerService.prepareOperationTimeout(token,
                                     timeout,
                                     mMonitorTask,
-                                    OP_TYPE_RESTORE_WAIT);
+                                    OpType.RESTORE_WAIT);
 
                             if (FullBackup.OBB_TREE_TOKEN.equals(info.domain)) {
                                 if (DEBUG) {
@@ -603,9 +607,9 @@
                     long fullBackupAgentTimeoutMillis =
                             mAgentTimeoutParameters.getFullBackupAgentTimeoutMillis();
                     final AdbRestoreFinishedLatch latch = new AdbRestoreFinishedLatch(
-                            mBackupManagerService, token);
+                            mBackupManagerService, mOperationStorage, token);
                     mBackupManagerService.prepareOperationTimeout(
-                            token, fullBackupAgentTimeoutMillis, latch, OP_TYPE_RESTORE_WAIT);
+                            token, fullBackupAgentTimeoutMillis, latch, OpType.RESTORE_WAIT);
                     if (mTargetApp.processName.equals("system")) {
                         if (MORE_DEBUG) {
                             Slog.d(TAG, "system agent - restoreFinished on thread");
diff --git a/services/backup/java/com/android/server/backup/restore/PerformAdbRestoreTask.java b/services/backup/java/com/android/server/backup/restore/PerformAdbRestoreTask.java
index e03150e..22af19e 100644
--- a/services/backup/java/com/android/server/backup/restore/PerformAdbRestoreTask.java
+++ b/services/backup/java/com/android/server/backup/restore/PerformAdbRestoreTask.java
@@ -32,6 +32,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.LocalServices;
+import com.android.server.backup.OperationStorage;
 import com.android.server.backup.UserBackupManagerService;
 import com.android.server.backup.fullbackup.FullBackupObbConnection;
 import com.android.server.backup.utils.BackupEligibilityRules;
@@ -60,6 +61,7 @@
 public class PerformAdbRestoreTask implements Runnable {
 
     private final UserBackupManagerService mBackupManagerService;
+    private final OperationStorage mOperationStorage;
     private final ParcelFileDescriptor mInputFile;
     private final String mCurrentPassword;
     private final String mDecryptPassword;
@@ -68,10 +70,12 @@
 
     private IFullBackupRestoreObserver mObserver;
 
-    public PerformAdbRestoreTask(UserBackupManagerService backupManagerService,
+    public PerformAdbRestoreTask(
+            UserBackupManagerService backupManagerService, OperationStorage operationStorage,
             ParcelFileDescriptor fd, String curPassword, String decryptPassword,
             IFullBackupRestoreObserver observer, AtomicBoolean latch) {
         this.mBackupManagerService = backupManagerService;
+        mOperationStorage = operationStorage;
         mInputFile = fd;
         mCurrentPassword = curPassword;
         mDecryptPassword = decryptPassword;
@@ -109,9 +113,9 @@
                     mBackupManagerService.getPackageManager(),
                     LocalServices.getService(PackageManagerInternal.class),
                     mBackupManagerService.getUserId(), BackupManager.OperationType.ADB_BACKUP);
-            FullRestoreEngine mEngine = new FullRestoreEngine(mBackupManagerService, null,
-                    mObserver, null, null, true, 0 /*unused*/, true,
-                    eligibilityRules);
+            FullRestoreEngine mEngine = new FullRestoreEngine(mBackupManagerService,
+                    mOperationStorage, null, mObserver, null, null,
+                    true, 0 /*unused*/, true, eligibilityRules);
             FullRestoreEngineThread mEngineThread = new FullRestoreEngineThread(mEngine,
                     tarInputStream);
             mEngineThread.run();
diff --git a/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java b/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
index ac831af..b48367d 100644
--- a/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
+++ b/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
@@ -20,7 +20,6 @@
 import static com.android.server.backup.BackupManagerService.MORE_DEBUG;
 import static com.android.server.backup.BackupManagerService.TAG;
 import static com.android.server.backup.UserBackupManagerService.KEY_WIDGET_STATE;
-import static com.android.server.backup.UserBackupManagerService.OP_TYPE_RESTORE_WAIT;
 import static com.android.server.backup.UserBackupManagerService.PACKAGE_MANAGER_SENTINEL;
 import static com.android.server.backup.UserBackupManagerService.SETTINGS_PACKAGE;
 import static com.android.server.backup.internal.BackupHandler.MSG_BACKUP_RESTORE_STEP;
@@ -60,6 +59,8 @@
 import com.android.server.backup.BackupAgentTimeoutParameters;
 import com.android.server.backup.BackupRestoreTask;
 import com.android.server.backup.BackupUtils;
+import com.android.server.backup.OperationStorage;
+import com.android.server.backup.OperationStorage.OpType;
 import com.android.server.backup.PackageManagerBackupAgent;
 import com.android.server.backup.PackageManagerBackupAgent.Metadata;
 import com.android.server.backup.TransportManager;
@@ -84,6 +85,7 @@
 public class PerformUnifiedRestoreTask implements BackupRestoreTask {
 
     private UserBackupManagerService backupManagerService;
+    private final OperationStorage mOperationStorage;
     private final int mUserId;
     private final TransportManager mTransportManager;
     // Transport client we're working with to do the restore
@@ -169,6 +171,7 @@
     PerformUnifiedRestoreTask(UserBackupManagerService backupManagerService) {
         mListener = null;
         mAgentTimeoutParameters = null;
+        mOperationStorage = null;
         mTransportConnection = null;
         mTransportManager = null;
         mEphemeralOpToken = 0;
@@ -181,6 +184,7 @@
     // about releasing it.
     public PerformUnifiedRestoreTask(
             UserBackupManagerService backupManagerService,
+            OperationStorage operationStorage,
             TransportConnection transportConnection,
             IRestoreObserver observer,
             IBackupManagerMonitor monitor,
@@ -192,6 +196,7 @@
             OnTaskFinishedListener listener,
             BackupEligibilityRules backupEligibilityRules) {
         this.backupManagerService = backupManagerService;
+        mOperationStorage = operationStorage;
         mUserId = backupManagerService.getUserId();
         mTransportManager = backupManagerService.getTransportManager();
         mEphemeralOpToken = backupManagerService.generateRandomIntegerToken();
@@ -767,7 +772,7 @@
             long restoreAgentTimeoutMillis = mAgentTimeoutParameters.getRestoreAgentTimeoutMillis(
                     app.applicationInfo.uid);
             backupManagerService.prepareOperationTimeout(
-                    mEphemeralOpToken, restoreAgentTimeoutMillis, this, OP_TYPE_RESTORE_WAIT);
+                    mEphemeralOpToken, restoreAgentTimeoutMillis, this, OpType.RESTORE_WAIT);
             startedAgentRestore = true;
             mAgent.doRestoreWithExcludedKeys(mBackupData, appVersionCode, mNewState,
                     mEphemeralOpToken, backupManagerService.getBackupManagerBinder(),
@@ -877,7 +882,7 @@
             backupManagerService
                     .prepareOperationTimeout(mEphemeralOpToken,
                             restoreAgentFinishedTimeoutMillis, this,
-                            OP_TYPE_RESTORE_WAIT);
+                            OpType.RESTORE_WAIT);
             mAgent.doRestoreFinished(mEphemeralOpToken,
                     backupManagerService.getBackupManagerBinder());
             // If we get this far, the callback or timeout will schedule the
@@ -921,7 +926,7 @@
             EventLog.writeEvent(EventLogTags.FULL_RESTORE_PACKAGE,
                     mCurrentPackage.packageName);
 
-            mEngine = new FullRestoreEngine(backupManagerService, this, null,
+            mEngine = new FullRestoreEngine(backupManagerService, mOperationStorage, this, null,
                     mMonitor, mCurrentPackage, false, mEphemeralOpToken, false,
                     mBackupEligibilityRules);
             mEngineThread = new FullRestoreEngineThread(mEngine, mEnginePipes[0]);
@@ -1071,7 +1076,7 @@
         // The app has timed out handling a restoring file
         @Override
         public void handleCancel(boolean cancelAll) {
-            backupManagerService.removeOperation(mEphemeralOpToken);
+            mOperationStorage.removeOperation(mEphemeralOpToken);
             if (DEBUG) {
                 Slog.w(TAG, "Full-data restore target timed out; shutting down");
             }
@@ -1268,7 +1273,7 @@
 
     @Override
     public void operationComplete(long unusedResult) {
-        backupManagerService.removeOperation(mEphemeralOpToken);
+        mOperationStorage.removeOperation(mEphemeralOpToken);
         if (MORE_DEBUG) {
             Slog.i(TAG, "operationComplete() during restore: target="
                     + mCurrentPackage.packageName
@@ -1331,7 +1336,7 @@
     // A call to agent.doRestore() or agent.doRestoreFinished() has timed out
     @Override
     public void handleCancel(boolean cancelAll) {
-        backupManagerService.removeOperation(mEphemeralOpToken);
+        mOperationStorage.removeOperation(mEphemeralOpToken);
         Slog.e(TAG, "Timeout restoring application " + mCurrentPackage.packageName);
         mMonitor = BackupManagerMonitorUtils.monitorEvent(mMonitor,
                 BackupManagerMonitor.LOG_EVENT_ID_KEY_VALUE_RESTORE_TIMEOUT,
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index a745e5a..4727b16 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -93,6 +93,7 @@
 import static android.provider.Settings.Global.NETWORK_ACCESS_TIMEOUT_MS;
 import static android.provider.Settings.Global.WAIT_FOR_DEBUGGER;
 import static android.text.format.DateUtils.DAY_IN_MILLIS;
+import static android.util.FeatureFlagUtils.SETTINGS_ENABLE_MONITOR_PHANTOM_PROCS;
 
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_CONFIGURATION;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_ALL;
@@ -307,6 +308,7 @@
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.EventLog;
+import android.util.FeatureFlagUtils;
 import android.util.IntArray;
 import android.util.Log;
 import android.util.Pair;
@@ -14598,6 +14600,8 @@
     private void checkExcessivePowerUsage() {
         updateCpuStatsNow();
 
+        final boolean monitorPhantomProcs = mSystemReady && FeatureFlagUtils.isEnabled(mContext,
+                SETTINGS_ENABLE_MONITOR_PHANTOM_PROCS);
         synchronized (mProcLock) {
             final boolean doCpuKills = mLastPowerCheckUptime != 0;
             final long curUptime = SystemClock.uptimeMillis();
@@ -14623,9 +14627,11 @@
 
                     updateAppProcessCpuTimeLPr(uptimeSince, doCpuKills, checkDur, cpuLimit, app);
 
-                    // Also check the phantom processes if there is any
-                    updatePhantomProcessCpuTimeLPr(
-                            uptimeSince, doCpuKills, checkDur, cpuLimit, app);
+                    if (monitorPhantomProcs) {
+                        // Also check the phantom processes if there is any
+                        updatePhantomProcessCpuTimeLPr(
+                                uptimeSince, doCpuKills, checkDur, cpuLimit, app);
+                    }
                 }
             });
         }
diff --git a/services/core/java/com/android/server/am/AppProfiler.java b/services/core/java/com/android/server/am/AppProfiler.java
index 221de8d..d6a4cf6 100644
--- a/services/core/java/com/android/server/am/AppProfiler.java
+++ b/services/core/java/com/android/server/am/AppProfiler.java
@@ -19,6 +19,7 @@
 import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT;
 import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_CRITICAL;
 import static android.os.Process.FIRST_APPLICATION_UID;
+import static android.util.FeatureFlagUtils.SETTINGS_ENABLE_MONITOR_PHANTOM_PROCS;
 
 import static com.android.internal.app.procstats.ProcessStats.ADJ_MEM_FACTOR_CRITICAL;
 import static com.android.internal.app.procstats.ProcessStats.ADJ_MEM_FACTOR_LOW;
@@ -77,6 +78,7 @@
 import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.DebugUtils;
+import android.util.FeatureFlagUtils;
 import android.util.Pair;
 import android.util.Slog;
 import android.util.SparseArray;
@@ -1805,6 +1807,8 @@
     }
 
     void updateCpuStatsNow() {
+        final boolean monitorPhantomProcs = mService.mSystemReady && FeatureFlagUtils.isEnabled(
+                mService.mContext, SETTINGS_ENABLE_MONITOR_PHANTOM_PROCS);
         synchronized (mProcessCpuTracker) {
             mProcessCpuMutexFree.set(false);
             final long now = SystemClock.uptimeMillis();
@@ -1843,7 +1847,7 @@
                 }
             }
 
-            if (haveNewCpuStats) {
+            if (monitorPhantomProcs && haveNewCpuStats) {
                 mService.mPhantomProcessList.updateProcessCpuStatesLocked(mProcessCpuTracker);
             }
 
diff --git a/services/core/java/com/android/server/am/PhantomProcessList.java b/services/core/java/com/android/server/am/PhantomProcessList.java
index b07684c..2ec1aed 100644
--- a/services/core/java/com/android/server/am/PhantomProcessList.java
+++ b/services/core/java/com/android/server/am/PhantomProcessList.java
@@ -18,6 +18,7 @@
 
 import static android.os.MessageQueue.OnFileDescriptorEventListener.EVENT_ERROR;
 import static android.os.MessageQueue.OnFileDescriptorEventListener.EVENT_INPUT;
+import static android.util.FeatureFlagUtils.SETTINGS_ENABLE_MONITOR_PHANTOM_PROCS;
 
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PROCESSES;
 import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
@@ -28,6 +29,7 @@
 import android.os.Handler;
 import android.os.Process;
 import android.os.StrictMode;
+import android.util.FeatureFlagUtils;
 import android.util.Slog;
 import android.util.SparseArray;
 
@@ -419,6 +421,10 @@
      * order of the oom adjs of their parent process.
      */
     void trimPhantomProcessesIfNecessary() {
+        if (!mService.mSystemReady || !FeatureFlagUtils.isEnabled(mService.mContext,
+                SETTINGS_ENABLE_MONITOR_PHANTOM_PROCS)) {
+            return;
+        }
         synchronized (mService.mProcLock) {
             synchronized (mLock) {
                 mTrimPhantomProcessScheduled = false;
diff --git a/services/core/java/com/android/server/app/GameManagerService.java b/services/core/java/com/android/server/app/GameManagerService.java
index 20606f0..8dce8e9 100644
--- a/services/core/java/com/android/server/app/GameManagerService.java
+++ b/services/core/java/com/android/server/app/GameManagerService.java
@@ -385,9 +385,8 @@
             }
 
             public boolean isValid() {
-                return (mGameMode == GameManager.GAME_MODE_PERFORMANCE
-                        || mGameMode == GameManager.GAME_MODE_BATTERY)
-                        && (!mAllowDownscale || getCompatChangeId() != 0);
+                return mGameMode == GameManager.GAME_MODE_PERFORMANCE
+                        || mGameMode == GameManager.GAME_MODE_BATTERY;
             }
 
             /**
@@ -839,7 +838,7 @@
             }
             long scaleId = modeConfig.getCompatChangeId();
             if (scaleId == 0) {
-                Slog.w(TAG, "Invalid downscaling change id " + scaleId + " for "
+                Slog.i(TAG, "Invalid downscaling change id " + scaleId + " for "
                         + packageName);
                 return;
             }
diff --git a/services/core/java/com/android/server/audio/AudioManagerShellCommand.java b/services/core/java/com/android/server/audio/AudioManagerShellCommand.java
new file mode 100644
index 0000000..241abaf
--- /dev/null
+++ b/services/core/java/com/android/server/audio/AudioManagerShellCommand.java
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2021 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.audio;
+
+import android.content.Context;
+import android.media.AudioManager;
+import android.os.ShellCommand;
+
+import java.io.PrintWriter;
+
+
+class AudioManagerShellCommand extends ShellCommand {
+    private static final String TAG = "AudioManagerShellCommand";
+
+    private final AudioService mService;
+
+    AudioManagerShellCommand(AudioService service) {
+        mService = service;
+    }
+
+    @Override
+    public int onCommand(String cmd) {
+        if (cmd == null) {
+            return handleDefaultCommands(cmd);
+        }
+        final PrintWriter pw = getOutPrintWriter();
+        switch(cmd) {
+            case "set-surround-format-enabled":
+                return setSurroundFormatEnabled();
+            case "get-is-surround-format-enabled":
+                return getIsSurroundFormatEnabled();
+            case "set-encoded-surround-mode":
+                return setEncodedSurroundMode();
+            case "get-encoded-surround-mode":
+                return getEncodedSurroundMode();
+        }
+        return 0;
+    }
+
+    @Override
+    public void onHelp() {
+        final PrintWriter pw = getOutPrintWriter();
+        pw.println("Audio manager commands:");
+        pw.println("  help");
+        pw.println("    Print this help text.");
+        pw.println();
+        pw.println("  set-surround-format-enabled SURROUND_FORMAT IS_ENABLED");
+        pw.println("    Enables/disabled the SURROUND_FORMAT based on IS_ENABLED");
+        pw.println("  get-is-surround-format-enabled SURROUND_FORMAT");
+        pw.println("    Returns if the SURROUND_FORMAT is enabled");
+        pw.println("  set-encoded-surround-mode SURROUND_SOUND_MODE");
+        pw.println("    Sets the encoded surround sound mode to SURROUND_SOUND_MODE");
+        pw.println("  get-encoded-surround-mode");
+        pw.println("    Returns the encoded surround sound mode");
+    }
+
+    private int setSurroundFormatEnabled() {
+        String surroundFormatText = getNextArg();
+        String isSurroundFormatEnabledText = getNextArg();
+
+        if (surroundFormatText == null) {
+            getErrPrintWriter().println("Error: no surroundFormat specified");
+            return 1;
+        }
+
+        if (isSurroundFormatEnabledText == null) {
+            getErrPrintWriter().println("Error: no enabled value for surroundFormat specified");
+            return 1;
+        }
+
+        int surroundFormat = -1;
+        boolean isSurroundFormatEnabled = false;
+        try {
+            surroundFormat = Integer.parseInt(surroundFormatText);
+            isSurroundFormatEnabled = Boolean.parseBoolean(isSurroundFormatEnabledText);
+        } catch (NumberFormatException e) {
+            getErrPrintWriter().println("Error: wrong format specified for surroundFormat");
+            return 1;
+        }
+        if (surroundFormat < 0) {
+            getErrPrintWriter().println("Error: invalid value of surroundFormat");
+            return 1;
+        }
+
+        final Context context = mService.mContext;
+        final AudioManager am = context.getSystemService(AudioManager.class);
+        am.setSurroundFormatEnabled(surroundFormat, isSurroundFormatEnabled);
+        return 0;
+    }
+
+    private int getIsSurroundFormatEnabled() {
+        String surroundFormatText = getNextArg();
+
+        if (surroundFormatText == null) {
+            getErrPrintWriter().println("Error: no surroundFormat specified");
+            return 1;
+        }
+
+        int surroundFormat = -1;
+        try {
+            surroundFormat = Integer.parseInt(surroundFormatText);
+        } catch (NumberFormatException e) {
+            getErrPrintWriter().println("Error: wrong format specified for surroundFormat");
+            return 1;
+        }
+
+        if (surroundFormat < 0) {
+            getErrPrintWriter().println("Error: invalid value of surroundFormat");
+            return 1;
+        }
+        final Context context = mService.mContext;
+        final AudioManager am = context.getSystemService(AudioManager.class);
+        getOutPrintWriter().println("Value of enabled for " + surroundFormat + " is: "
+                + am.isSurroundFormatEnabled(surroundFormat));
+        return 0;
+    }
+
+    private int setEncodedSurroundMode() {
+        String encodedSurroundModeText = getNextArg();
+
+        if (encodedSurroundModeText == null) {
+            getErrPrintWriter().println("Error: no encodedSurroundMode specified");
+            return 1;
+        }
+
+        int encodedSurroundMode = -1;
+        try {
+            encodedSurroundMode = Integer.parseInt(encodedSurroundModeText);
+        } catch (NumberFormatException e) {
+            getErrPrintWriter().println("Error: wrong format specified for encoded surround mode");
+            return 1;
+        }
+
+        if (encodedSurroundMode < 0) {
+            getErrPrintWriter().println("Error: invalid value of encodedSurroundMode");
+            return 1;
+        }
+
+        final Context context = mService.mContext;
+        final AudioManager am = context.getSystemService(AudioManager.class);
+        am.setEncodedSurroundMode(encodedSurroundMode);
+        return 0;
+    }
+
+    private int getEncodedSurroundMode() {
+        final Context context = mService.mContext;
+        final AudioManager am = context.getSystemService(AudioManager.class);
+        getOutPrintWriter().println("Encoded surround mode: " + am.getEncodedSurroundMode());
+        return 0;
+    }
+}
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 6238198..8615393 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -130,7 +130,9 @@
 import android.os.Process;
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
+import android.os.ResultReceiver;
 import android.os.ServiceManager;
+import android.os.ShellCallback;
 import android.os.SystemClock;
 import android.os.SystemProperties;
 import android.os.UserHandle;
@@ -1996,6 +1998,18 @@
         }
     }
 
+    @Override // Binder call
+    public void onShellCommand(FileDescriptor in, FileDescriptor out,
+            FileDescriptor err, String[] args, ShellCallback callback,
+            ResultReceiver resultReceiver) {
+        if (mContext.checkCallingOrSelfPermission(Manifest.permission.MANAGE_AUDIO_POLICY)
+                != PackageManager.PERMISSION_GRANTED) {
+            throw new SecurityException("Missing MANAGE_AUDIO_POLICY permission");
+        }
+        new AudioManagerShellCommand(AudioService.this).exec(this, in, out, err,
+                args, callback, resultReceiver);
+    }
+
     /** @see AudioManager#getSurroundFormats() */
     @Override
     public Map<Integer, Boolean> getSurroundFormats() {
@@ -10142,6 +10156,27 @@
         return AudioManager.SUCCESS;
     }
 
+    /** @see AudioPolicy#getFocusStack() */
+    public List<AudioFocusInfo> getFocusStack() {
+        enforceModifyAudioRoutingPermission();
+        return mMediaFocusControl.getFocusStack();
+    }
+
+    /** @see AudioPolicy#sendFocusLoss */
+    public boolean sendFocusLoss(@NonNull AudioFocusInfo focusLoser,
+            @NonNull IAudioPolicyCallback apcb) {
+        Objects.requireNonNull(focusLoser);
+        Objects.requireNonNull(apcb);
+        enforceModifyAudioRoutingPermission();
+        if (!mAudioPolicies.containsKey(apcb.asBinder())) {
+            throw new IllegalStateException("Only registered AudioPolicy can change focus");
+        }
+        if (!mAudioPolicies.get(apcb.asBinder()).mHasFocusListener) {
+            throw new IllegalStateException("AudioPolicy must have focus listener to change focus");
+        }
+        return mMediaFocusControl.sendFocusLoss(focusLoser);
+    }
+
     /** see AudioManager.hasRegisteredDynamicPolicy */
     public boolean hasRegisteredDynamicPolicy() {
         synchronized (mAudioPolicies) {
diff --git a/services/core/java/com/android/server/audio/MediaFocusControl.java b/services/core/java/com/android/server/audio/MediaFocusControl.java
index 66f6235..69a4c23 100644
--- a/services/core/java/com/android/server/audio/MediaFocusControl.java
+++ b/services/core/java/com/android/server/audio/MediaFocusControl.java
@@ -27,6 +27,7 @@
 import android.media.AudioSystem;
 import android.media.IAudioFocusDispatcher;
 import android.media.MediaMetrics;
+import android.media.audiopolicy.AudioPolicy;
 import android.media.audiopolicy.IAudioPolicyCallback;
 import android.os.Binder;
 import android.os.Build;
@@ -221,6 +222,51 @@
         }
     }
 
+    /**
+     * Return a copy of the focus stack for external consumption (composed of AudioFocusInfo
+     * instead of FocusRequester instances)
+     * @return a SystemApi-friendly version of the focus stack, in the same order (last entry
+     *         is top of focus stack, i.e. latest focus owner)
+     * @see AudioPolicy#getFocusStack()
+     */
+    @NonNull List<AudioFocusInfo> getFocusStack() {
+        synchronized (mAudioFocusLock) {
+            final ArrayList<AudioFocusInfo> stack = new ArrayList<>(mFocusStack.size());
+            for (FocusRequester fr : mFocusStack) {
+                stack.add(fr.toAudioFocusInfo());
+            }
+            return stack;
+        }
+    }
+
+    /**
+     * Send AUDIOFOCUS_LOSS to a specific stack entry.
+     * Note this method is supporting an external API, and is restricted to LOSS in order to
+     * prevent allowing the stack to be in an invalid state (e.g. entry inside stack has focus)
+     * @param focusLoser the stack entry that is exiting the stack through a focus loss
+     * @return false if the focusLoser wasn't found in the stack, true otherwise
+     * @see AudioPolicy#sendFocusLoss(AudioFocusInfo)
+     */
+    boolean sendFocusLoss(@NonNull AudioFocusInfo focusLoser) {
+        synchronized (mAudioFocusLock) {
+            FocusRequester loserToRemove = null;
+            for (FocusRequester fr : mFocusStack) {
+                if (fr.getClientId().equals(focusLoser.getClientId())) {
+                    fr.handleFocusLoss(AudioManager.AUDIOFOCUS_LOSS, null,
+                            false /*forceDuck*/);
+                    loserToRemove = fr;
+                    break;
+                }
+            }
+            if (loserToRemove != null) {
+                mFocusStack.remove(loserToRemove);
+                loserToRemove.release();
+                return true;
+            }
+        }
+        return false;
+    }
+
     @GuardedBy("mAudioFocusLock")
     private void notifyTopOfAudioFocusStack() {
         // notify the top of the stack it gained focus
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 3f55848..4c56999 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -1072,6 +1072,13 @@
                         + "setUserDisabledHdrTypesInternal");
                 return;
             }
+
+            // Verify if userDisabledHdrTypes contains expected HDR types
+            if (!isSubsetOf(Display.HdrCapabilities.HDR_TYPES, userDisabledHdrTypes)) {
+                Slog.e(TAG, "userDisabledHdrTypes contains unexpected types");
+                return;
+            }
+
             Arrays.sort(userDisabledHdrTypes);
             if (Arrays.equals(mUserDisabledHdrTypes, userDisabledHdrTypes)) {
                 return;
@@ -1094,6 +1101,15 @@
         }
     }
 
+    private boolean isSubsetOf(int[] sortedSuperset, int[] subset) {
+        for (int i : subset) {
+            if (Arrays.binarySearch(sortedSuperset, i) < 0) {
+                return false;
+            }
+        }
+        return true;
+    }
+
     private void setAreUserDisabledHdrTypesAllowedInternal(
             boolean areUserDisabledHdrTypesAllowed) {
         synchronized (mSyncRoot) {
diff --git a/services/core/java/com/android/server/display/DisplayManagerShellCommand.java b/services/core/java/com/android/server/display/DisplayManagerShellCommand.java
index 9412c93..43a850c 100644
--- a/services/core/java/com/android/server/display/DisplayManagerShellCommand.java
+++ b/services/core/java/com/android/server/display/DisplayManagerShellCommand.java
@@ -20,9 +20,11 @@
 import android.content.Intent;
 import android.hardware.display.DisplayManager;
 import android.os.ShellCommand;
+import android.util.Slog;
 import android.view.Display;
 
 import java.io.PrintWriter;
+import java.util.Arrays;
 
 class DisplayManagerShellCommand extends ShellCommand {
     private static final String TAG = "DisplayManagerShellCommand";
@@ -60,6 +62,20 @@
                 return setAmbientColorTemperatureOverride();
             case "constrain-launcher-metrics":
                 return setConstrainLauncherMetrics();
+            case "set-user-preferred-display-mode":
+                return setUserPreferredDisplayMode();
+            case "clear-user-preferred-display-mode":
+                return clearUserPreferredDisplayMode();
+            case "get-user-preferred-display-mode":
+                return getUserPreferredDisplayMode();
+            case "set-match-content-frame-rate-pref":
+                return setMatchContentFrameRateUserPreference();
+            case "get-match-content-frame-rate-pref":
+                return getMatchContentFrameRateUserPreference();
+            case "set-user-disabled-hdr-types":
+                return setUserDisabledHdrTypes();
+            case "get-user-disabled-hdr-types":
+                return getUserDisabledHdrTypes();
             default:
                 return handleDefaultCommands(cmd);
         }
@@ -93,6 +109,21 @@
         pw.println("  constrain-launcher-metrics [true|false]");
         pw.println("    Sets if Display#getRealSize and getRealMetrics should be constrained for ");
         pw.println("    Launcher.");
+        pw.println("  set-user-preferred-display-mode WIDTH HEIGHT REFRESH-RATE");
+        pw.println("    Sets the user preferred display mode which has fields WIDTH, HEIGHT and "
+                + "REFRESH-RATE");
+        pw.println("  clear-user-preferred-display-mode");
+        pw.println("    Clears the user preferred display mode");
+        pw.println("  get-user-preferred-display-mode");
+        pw.println("    Returns the user preferred display mode or null id no mode is set by user");
+        pw.println("  set-match-content-frame-rate-pref PREFERENCE");
+        pw.println("    Sets the match content frame rate preference as PREFERENCE ");
+        pw.println("  get-match-content-frame-rate-pref");
+        pw.println("    Returns the match content frame rate preference");
+        pw.println("  set-user-disabled-hdr-types TYPES...");
+        pw.println("    Sets the user disabled HDR types as TYPES");
+        pw.println("  get-user-disabled-hdr-types");
+        pw.println("    Returns the user disabled HDR types");
         pw.println();
         Intent.printIntentArgsHelp(pw , "");
     }
@@ -166,4 +197,152 @@
         mService.setShouldConstrainMetricsForLauncher(constrain);
         return 0;
     }
+
+    private int setUserPreferredDisplayMode() {
+        final String widthText = getNextArg();
+        if (widthText == null) {
+            getErrPrintWriter().println("Error: no width specified");
+            return 1;
+        }
+
+        final String heightText = getNextArg();
+        if (heightText == null) {
+            getErrPrintWriter().println("Error: no height specified");
+            return 1;
+        }
+
+        final String refreshRateText = getNextArg();
+        if (refreshRateText == null) {
+            getErrPrintWriter().println("Error: no refresh-rate specified");
+            return 1;
+        }
+
+        final int width, height;
+        final float refreshRate;
+        try {
+            width = Integer.parseInt(widthText);
+            height = Integer.parseInt(heightText);
+            refreshRate = Float.parseFloat(refreshRateText);
+        } catch (NumberFormatException e) {
+            getErrPrintWriter().println("Error: invalid format of width, height or refresh rate");
+            return 1;
+        }
+        if (width < 0 || height < 0 || refreshRate <= 0.0f) {
+            getErrPrintWriter().println("Error: invalid value of width, height or refresh rate");
+            return 1;
+        }
+
+        final Context context = mService.getContext();
+        final DisplayManager dm = context.getSystemService(DisplayManager.class);
+        dm.setUserPreferredDisplayMode(new Display.Mode(width, height, refreshRate));
+        return 0;
+    }
+
+    private int clearUserPreferredDisplayMode() {
+        final Context context = mService.getContext();
+        final DisplayManager dm = context.getSystemService(DisplayManager.class);
+        dm.clearUserPreferredDisplayMode();
+        return 0;
+    }
+
+    private int getUserPreferredDisplayMode() {
+        final Context context = mService.getContext();
+        final DisplayManager dm = context.getSystemService(DisplayManager.class);
+        final Display.Mode mode =  dm.getUserPreferredDisplayMode();
+        if (mode == null) {
+            getOutPrintWriter().println("User preferred display mode: null");
+            return 0;
+        }
+
+        getOutPrintWriter().println("User preferred display mode: " + mode.getPhysicalWidth() + " "
+                + mode.getPhysicalHeight() + " " + mode.getRefreshRate());
+        return 0;
+    }
+
+    private int setMatchContentFrameRateUserPreference() {
+        final String matchContentFrameRatePrefText = getNextArg();
+        if (matchContentFrameRatePrefText == null) {
+            getErrPrintWriter().println("Error: no matchContentFrameRatePref specified");
+            return 1;
+        }
+
+        final int matchContentFrameRatePreference;
+        try {
+            matchContentFrameRatePreference = Integer.parseInt(matchContentFrameRatePrefText);
+        } catch (NumberFormatException e) {
+            getErrPrintWriter().println("Error: invalid format of matchContentFrameRatePreference");
+            return 1;
+        }
+        if (matchContentFrameRatePreference < 0) {
+            getErrPrintWriter().println("Error: invalid value of matchContentFrameRatePreference");
+            return 1;
+        }
+
+        final Context context = mService.getContext();
+        final DisplayManager dm = context.getSystemService(DisplayManager.class);
+
+        final int refreshRateSwitchingType =
+                toRefreshRateSwitchingType(matchContentFrameRatePreference);
+        dm.setRefreshRateSwitchingType(refreshRateSwitchingType);
+        return 0;
+    }
+
+    private int getMatchContentFrameRateUserPreference() {
+        final Context context = mService.getContext();
+        final DisplayManager dm = context.getSystemService(DisplayManager.class);
+        getOutPrintWriter().println("Match content frame rate type: "
+                + dm.getMatchContentFrameRateUserPreference());
+        return 0;
+    }
+
+    private int setUserDisabledHdrTypes() {
+        final String[] userDisabledHdrTypesText = getAllArgs();
+        if (userDisabledHdrTypesText == null) {
+            getErrPrintWriter().println("Error: no userDisabledHdrTypes specified");
+            return 1;
+        }
+
+        int[] userDisabledHdrTypes = new int[userDisabledHdrTypesText.length];
+        try {
+            int index = 0;
+            for (String userDisabledHdrType : userDisabledHdrTypesText) {
+                userDisabledHdrTypes[index++] = Integer.parseInt(userDisabledHdrType);
+            }
+        } catch (NumberFormatException e) {
+            getErrPrintWriter().println("Error: invalid format of userDisabledHdrTypes");
+            return 1;
+        }
+
+        final Context context = mService.getContext();
+        final DisplayManager dm = context.getSystemService(DisplayManager.class);
+        dm.setUserDisabledHdrTypes(userDisabledHdrTypes);
+        return 0;
+    }
+
+    private int getUserDisabledHdrTypes() {
+        final Context context = mService.getContext();
+        final DisplayManager dm = context.getSystemService(DisplayManager.class);
+        final int[] userDisabledHdrTypes = dm.getUserDisabledHdrTypes();
+        getOutPrintWriter().println("User disabled HDR types: "
+                + Arrays.toString(userDisabledHdrTypes));
+        return 0;
+    }
+
+    @DisplayManager.SwitchingType
+    private int toRefreshRateSwitchingType(
+            @DisplayManager.MatchContentFrameRateType int matchContentFrameRateType) {
+        switch (matchContentFrameRateType) {
+            case DisplayManager.MATCH_CONTENT_FRAMERATE_NEVER:
+                return DisplayManager.SWITCHING_TYPE_NONE;
+            case DisplayManager.MATCH_CONTENT_FRAMERATE_SEAMLESSS_ONLY:
+                return DisplayManager.SWITCHING_TYPE_WITHIN_GROUPS;
+            case DisplayManager.MATCH_CONTENT_FRAMERATE_ALWAYS:
+                return DisplayManager.SWITCHING_TYPE_ACROSS_AND_WITHIN_GROUPS;
+            case DisplayManager.MATCH_CONTENT_FRAMERATE_UNKNOWN:
+            default:
+                Slog.e(TAG, matchContentFrameRateType + " is not a valid value of "
+                        + "matchContentFrameRate type.");
+                return -1;
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
index 05e1bdd..3d91fee 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
@@ -39,6 +39,7 @@
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.util.ArrayMap;
+import android.util.EventLog;
 import android.util.Slog;
 import android.view.IWindowManager;
 import android.view.WindowManager;
@@ -49,6 +50,7 @@
 import com.android.internal.inputmethod.InputBindResult;
 import com.android.internal.inputmethod.UnbindReason;
 import com.android.internal.view.IInputMethod;
+import com.android.server.EventLogTags;
 import com.android.server.wm.WindowManagerInternal;
 
 /**
@@ -58,6 +60,9 @@
     static final boolean DEBUG = false;
     private static final String TAG = InputMethodBindingController.class.getSimpleName();
 
+    /** Time in milliseconds that the IME service has to bind before it is reconnected. */
+    static final long TIME_TO_RECONNECT = 3 * 1000;
+
     @NonNull private final InputMethodManagerService mService;
     @NonNull private final Context mContext;
     @NonNull private final ArrayMap<String, InputMethodInfo> mMethodMap;
@@ -239,7 +244,7 @@
     }
 
     /**
-     * Indicates whether {@link #getVisibleConnection} is currently in use.
+     * Indicates whether {@link #mVisibleConnection} is currently in use.
      */
     boolean isVisibleBound() {
         return mVisibleBound;
@@ -248,11 +253,6 @@
     /**
      * Used to bring IME service up to visible adjustment while it is being shown.
      */
-    @NonNull
-    ServiceConnection getVisibleConnection() {
-        return mVisibleConnection;
-    }
-
     private final ServiceConnection mVisibleConnection = new ServiceConnection() {
         @Override public void onBindingDied(ComponentName name) {
             synchronized (mMethodMap) {
@@ -334,7 +334,7 @@
                     // We consider this to be a new bind attempt, since the system
                     // should now try to restart the service for us.
                     mLastBindTime = SystemClock.uptimeMillis();
-                    mService.clearClientSessionsLocked();
+                    clearCurMethodAndSessionsLocked();
                     mService.clearInputShowRequestLocked();
                     mService.unbindCurrentClientLocked(UnbindReason.DISCONNECT_IME);
                 }
@@ -358,11 +358,12 @@
         }
 
         mCurId = null;
-        mService.clearClientSessionsLocked();
+        clearCurMethodAndSessionsLocked();
     }
 
     @GuardedBy("mMethodMap")
-    void clearCurMethodLocked() {
+    private void clearCurMethodAndSessionsLocked() {
+        mService.clearClientSessionsLocked();
         mCurMethod = null;
         mCurMethodUid = Process.INVALID_UID;
     }
@@ -382,7 +383,7 @@
 
     @GuardedBy("mMethodMap")
     @NonNull
-    InputBindResult bindCurrentMethodLocked(int displayIdToShowIme) {
+    InputBindResult bindCurrentMethodLocked() {
         InputMethodInfo info = mMethodMap.get(mSelectedMethodId);
         if (info == null) {
             throw new IllegalArgumentException("Unknown id: " + mSelectedMethodId);
@@ -394,7 +395,7 @@
             mCurId = info.getId();
             mLastBindTime = SystemClock.uptimeMillis();
 
-            addFreshWindowTokenLocked(displayIdToShowIme);
+            addFreshWindowTokenLocked();
             return new InputBindResult(
                     InputBindResult.ResultCode.SUCCESS_WAITING_IME_BINDING,
                     null, null, mCurId, mCurSeq, false);
@@ -419,7 +420,8 @@
     }
 
     @GuardedBy("mMethodMap")
-    private void addFreshWindowTokenLocked(int displayIdToShowIme) {
+    private void addFreshWindowTokenLocked() {
+        int displayIdToShowIme = mService.getDisplayIdToShowIme();
         mCurToken = new Binder();
 
         mService.setCurTokenDisplayId(displayIdToShowIme);
@@ -438,7 +440,7 @@
     }
 
     @GuardedBy("mMethodMap")
-    void unbindMainConnectionLocked() {
+    private void unbindMainConnectionLocked() {
         mContext.unbindService(mMainConnection);
         mHasConnection = false;
     }
@@ -460,17 +462,61 @@
     }
 
     @GuardedBy("mMethodMap")
-    boolean bindCurrentInputMethodServiceVisibleConnectionLocked() {
+    private boolean bindCurrentInputMethodServiceVisibleConnectionLocked() {
         mVisibleBound = bindCurrentInputMethodServiceLocked(mVisibleConnection,
                 IME_VISIBLE_BIND_FLAGS);
         return mVisibleBound;
     }
 
     @GuardedBy("mMethodMap")
-    boolean bindCurrentInputMethodServiceMainConnectionLocked() {
+    private boolean bindCurrentInputMethodServiceMainConnectionLocked() {
         mHasConnection = bindCurrentInputMethodServiceLocked(mMainConnection,
                 mImeConnectionBindFlags);
         return mHasConnection;
     }
 
+    /**
+     * Bind the IME so that it can be shown.
+     *
+     * <p>
+     * Performs a rebind if no binding is achieved in {@link #TIME_TO_RECONNECT} milliseconds.
+     */
+    @GuardedBy("mMethodMap")
+    void setCurrentMethodVisibleLocked() {
+        if (mCurMethod != null) {
+            if (DEBUG) Slog.d(TAG, "setCurrentMethodVisibleLocked: mCurToken=" + mCurToken);
+            if (mHasConnection && !mVisibleBound) {
+                bindCurrentInputMethodServiceVisibleConnectionLocked();
+            }
+            return;
+        }
+
+        long bindingDuration = SystemClock.uptimeMillis() - mLastBindTime;
+        if (mHasConnection && bindingDuration >= TIME_TO_RECONNECT) {
+            // The client has asked to have the input method shown, but
+            // we have been sitting here too long with a connection to the
+            // service and no interface received, so let's disconnect/connect
+            // to try to prod things along.
+            EventLog.writeEvent(EventLogTags.IMF_FORCE_RECONNECT_IME, getSelectedMethodId(),
+                    bindingDuration, 1);
+            Slog.w(TAG, "Force disconnect/connect to the IME in setCurrentMethodVisibleLocked()");
+            unbindMainConnectionLocked();
+            bindCurrentInputMethodServiceMainConnectionLocked();
+        } else {
+            if (DEBUG) {
+                Slog.d(TAG, "Can't show input: connection = " + mHasConnection + ", time = "
+                        + (TIME_TO_RECONNECT - bindingDuration));
+            }
+        }
+    }
+
+    /**
+     * Remove the binding needed for the IME to be shown.
+     */
+    @GuardedBy("mMethodMap")
+    void setCurrentMethodNotVisibleLocked() {
+        if (mVisibleBound) {
+            unbindVisibleConnectionLocked();
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 3c6b096..bff4f27 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -49,6 +49,8 @@
 import static android.view.WindowManager.DISPLAY_IME_POLICY_HIDE;
 import static android.view.WindowManager.DISPLAY_IME_POLICY_LOCAL;
 
+import static com.android.server.inputmethod.InputMethodBindingController.TIME_TO_RECONNECT;
+
 import static java.lang.annotation.RetentionPolicy.SOURCE;
 
 import android.Manifest;
@@ -111,12 +113,10 @@
 import android.os.UserManager;
 import android.provider.Settings;
 import android.text.TextUtils;
-import android.text.style.SuggestionSpan;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.EventLog;
 import android.util.IndentingPrintWriter;
-import android.util.LruCache;
 import android.util.Pair;
 import android.util.PrintWriterPrinter;
 import android.util.Printer;
@@ -250,10 +250,6 @@
 
     static final int MSG_NOTIFY_IME_UID_TO_AUDIO_SERVICE = 7000;
 
-    static final long TIME_TO_RECONNECT = 3 * 1000;
-
-    static final int SECURE_SUGGESTION_SPANS_MAX_SIZE = 20;
-
     private static final int NOT_A_SUBTYPE_ID = InputMethodUtils.NOT_A_SUBTYPE_ID;
     private static final String TAG_TRY_SUPPRESSING_IME_SWITCHER = "TrySuppressingImeSwitcher";
     private static final String HANDLER_THREAD_NAME = "android.imms";
@@ -301,8 +297,6 @@
     // lock for this class.
     final ArrayList<InputMethodInfo> mMethodList = new ArrayList<>();
     final ArrayMap<String, InputMethodInfo> mMethodMap = new ArrayMap<>();
-    private final LruCache<SuggestionSpan, InputMethodInfo> mSecureSuggestionSpans =
-            new LruCache<>(SECURE_SUGGESTION_SPANS_MAX_SIZE);
     final InputMethodSubtypeSwitchingController mSwitchingController;
 
     /**
@@ -312,13 +306,16 @@
     private int mMethodMapUpdateCount = 0;
 
     /**
-     * Indicates whether {@link InputMethodBindingController#getVisibleConnection} is currently
-     * in use.
+     * The display id for which the latest startInput was called.
      */
-    private boolean isVisibleBound() {
-        return mBindingController.isVisibleBound();
+    @GuardedBy("mMethodMap")
+    int getDisplayIdToShowIme() {
+        return mDisplayIdToShowIme;
     }
 
+    @GuardedBy("mMethodMap")
+    private int mDisplayIdToShowIme = INVALID_DISPLAY;
+
     // Ongoing notification
     private NotificationManager mNotificationManager;
     KeyguardManager mKeyguardManager;
@@ -2354,10 +2351,10 @@
         }
         // Compute the final shown display ID with validated cs.selfReportedDisplayId for this
         // session & other conditions.
-        final int displayIdToShowIme = computeImeDisplayIdForTarget(cs.selfReportedDisplayId,
+        mDisplayIdToShowIme = computeImeDisplayIdForTarget(cs.selfReportedDisplayId,
                 mImeDisplayValidator);
 
-        if (displayIdToShowIme == INVALID_DISPLAY) {
+        if (mDisplayIdToShowIme == INVALID_DISPLAY) {
             mImeHiddenByDisplayPolicy = true;
             hideCurrentInputLocked(mCurFocusedWindow, 0, null,
                     SoftInputShowHideReason.HIDE_DISPLAY_IME_POLICY_HIDE);
@@ -2378,7 +2375,7 @@
         // Check if the input method is changing.
         // We expect the caller has already verified that the client is allowed to access this
         // display ID.
-        if (isSelectedMethodBound(displayIdToShowIme)) {
+        if (isSelectedMethodBound()) {
             if (cs.curSession != null) {
                 // Fast case: if we are already connected to the input method,
                 // then just return it.
@@ -2394,13 +2391,13 @@
 
         mBindingController.unbindCurrentMethodLocked();
 
-        return mBindingController.bindCurrentMethodLocked(displayIdToShowIme);
+        return mBindingController.bindCurrentMethodLocked();
     }
 
-    private boolean isSelectedMethodBound(int displayIdToShowIme) {
+    private boolean isSelectedMethodBound() {
         String curId = getCurId();
         return curId != null && curId.equals(getSelectedMethodId())
-                && displayIdToShowIme == mCurTokenDisplayId;
+                && mDisplayIdToShowIme == mCurTokenDisplayId;
     }
 
     @GuardedBy("mMethodMap")
@@ -2590,7 +2587,6 @@
 
             finishSessionLocked(mEnabledSession);
             mEnabledSession = null;
-            mBindingController.clearCurMethodLocked();
             scheduleNotifyImeUidToAudioService(Process.INVALID_UID);
         }
         hideStatusBarIconLocked();
@@ -3047,42 +3043,20 @@
             return false;
         }
 
-        boolean res = false;
-        IInputMethod curMethod = getCurMethod();
-        if (curMethod != null) {
-            if (DEBUG) Slog.d(TAG, "showCurrentInputLocked: mCurToken=" + getCurToken());
+        mBindingController.setCurrentMethodVisibleLocked();
+        if (getCurMethod() != null) {
             // create a placeholder token for IMS so that IMS cannot inject windows into client app.
             Binder showInputToken = new Binder();
             mShowRequestWindowMap.put(showInputToken, windowToken);
+            IInputMethod curMethod = getCurMethod();
             executeOrSendMessage(curMethod, mCaller.obtainMessageIIOOO(MSG_SHOW_SOFT_INPUT,
                     getImeShowFlagsLocked(), reason, curMethod, resultReceiver,
                     showInputToken));
             mInputShown = true;
-            if (hasConnection() && !isVisibleBound()) {
-                mBindingController.bindCurrentInputMethodServiceVisibleConnectionLocked();
-            }
-            res = true;
-        } else {
-            long bindingDuration = SystemClock.uptimeMillis() - getLastBindTime();
-            if (hasConnection() && bindingDuration >= TIME_TO_RECONNECT) {
-                // The client has asked to have the input method shown, but
-                // we have been sitting here too long with a connection to the
-                // service and no interface received, so let's disconnect/connect
-                // to try to prod things along.
-                EventLog.writeEvent(EventLogTags.IMF_FORCE_RECONNECT_IME, getSelectedMethodId(),
-                        bindingDuration, 1);
-                Slog.w(TAG, "Force disconnect/connect to the IME in showCurrentInputLocked()");
-                mBindingController.unbindMainConnectionLocked();
-                mBindingController.bindCurrentInputMethodServiceMainConnectionLocked();
-            } else {
-                if (DEBUG) {
-                    Slog.d(TAG, "Can't show input: connection = " + hasConnection() + ", time = "
-                            + (TIME_TO_RECONNECT - bindingDuration));
-                }
-            }
+            return true;
         }
 
-        return res;
+        return false;
     }
 
     @Override
@@ -3166,9 +3140,7 @@
         } else {
             res = false;
         }
-        if (hasConnection() && isVisibleBound()) {
-            mBindingController.unbindVisibleConnectionLocked();
-        }
+        mBindingController.setCurrentMethodNotVisibleLocked();
         mInputShown = false;
         mShowRequested = false;
         mShowExplicitlyRequested = false;
@@ -3522,10 +3494,6 @@
         return mWindowManagerInternal.shouldRestoreImeVisibility(windowToken);
     }
 
-    private boolean isImeVisible() {
-        return (mImeWindowVis & InputMethodService.IME_VISIBLE) != 0;
-    }
-
     @GuardedBy("mMethodMap")
     private boolean canShowInputMethodPickerLocked(IInputMethodClient client) {
         // TODO(yukawa): multi-display support.
@@ -5114,7 +5082,8 @@
                     + " client=" + mCurFocusedWindowClient);
             focusedWindowClient = mCurFocusedWindowClient;
             p.println("  mCurId=" + getCurId() + " mHaveConnection=" + hasConnection()
-                    + " mBoundToMethod=" + mBoundToMethod + " mVisibleBound=" + isVisibleBound());
+                    + " mBoundToMethod=" + mBoundToMethod + " mVisibleBound="
+                    + mBindingController.isVisibleBound());
             p.println("  mCurToken=" + getCurToken());
             p.println("  mCurTokenDisplayId=" + mCurTokenDisplayId);
             p.println("  mCurHostInputToken=" + mCurHostInputToken);
diff --git a/services/core/java/com/android/server/location/provider/LocationProviderManager.java b/services/core/java/com/android/server/location/provider/LocationProviderManager.java
index 0ce24dd..ede8b32 100644
--- a/services/core/java/com/android/server/location/provider/LocationProviderManager.java
+++ b/services/core/java/com/android/server/location/provider/LocationProviderManager.java
@@ -177,7 +177,7 @@
     protected interface LocationTransport {
 
         void deliverOnLocationChanged(LocationResult locationResult,
-                @Nullable Runnable onCompleteCallback) throws Exception;
+                @Nullable IRemoteCallback onCompleteCallback) throws Exception;
         void deliverOnFlushComplete(int requestCode) throws Exception;
     }
 
@@ -197,9 +197,8 @@
 
         @Override
         public void deliverOnLocationChanged(LocationResult locationResult,
-                @Nullable Runnable onCompleteCallback) throws RemoteException {
-            mListener.onLocationChanged(locationResult.asList(),
-                    SingleUseCallback.wrap(onCompleteCallback));
+                @Nullable IRemoteCallback onCompleteCallback) throws RemoteException {
+            mListener.onLocationChanged(locationResult.asList(), onCompleteCallback);
         }
 
         @Override
@@ -227,7 +226,7 @@
 
         @Override
         public void deliverOnLocationChanged(LocationResult locationResult,
-                @Nullable Runnable onCompleteCallback)
+                @Nullable IRemoteCallback onCompleteCallback)
                 throws PendingIntent.CanceledException {
             BroadcastOptions options = BroadcastOptions.makeBasic();
             options.setDontSendToRestrictedApps(true);
@@ -243,20 +242,34 @@
                 intent.putExtra(KEY_LOCATIONS, locationResult.asList().toArray(new Location[0]));
             }
 
+            PendingIntent.OnFinished onFinished = null;
+
             // send() SHOULD only run the completion callback if it completes successfully. however,
-            // b/199464864 (which could not be fixed in the S timeframe) means that it's possible
+            // b/201299281 (which could not be fixed in the S timeframe) means that it's possible
             // for send() to throw an exception AND run the completion callback. if this happens, we
             // would over-release the wakelock... we take matters into our own hands to ensure that
             // the completion callback can only be run if send() completes successfully. this means
             // the completion callback may be run inline - but as we've never specified what thread
             // the callback is run on, this is fine.
-            GatedCallback gatedCallback = new GatedCallback(onCompleteCallback);
+            GatedCallback gatedCallback;
+            if (onCompleteCallback != null) {
+                gatedCallback = new GatedCallback(() -> {
+                    try {
+                        onCompleteCallback.sendResult(null);
+                    } catch (RemoteException e) {
+                        throw e.rethrowFromSystemServer();
+                    }
+                });
+                onFinished = (pI, i, rC, rD, rE) -> gatedCallback.run();
+            } else {
+                gatedCallback = new GatedCallback(null);
+            }
 
             mPendingIntent.send(
                     mContext,
                     0,
                     intent,
-                    (pI, i, rC, rD, rE) -> gatedCallback.run(),
+                    onFinished,
                     null,
                     null,
                     options.toBundle());
@@ -293,7 +306,7 @@
 
         @Override
         public void deliverOnLocationChanged(@Nullable LocationResult locationResult,
-                @Nullable Runnable onCompleteCallback)
+                @Nullable IRemoteCallback onCompleteCallback)
                 throws RemoteException {
             // ILocationCallback doesn't currently support completion callbacks
             Preconditions.checkState(onCompleteCallback == null);
@@ -714,6 +727,13 @@
 
         final PowerManager.WakeLock mWakeLock;
 
+        // b/206340085 - if we allocate a new wakelock releaser object for every delivery we
+        // increase the risk of resource starvation. if a client stops processing deliveries the
+        // system server binder allocation pool will be starved as we continue to queue up
+        // deliveries, each with a new allocation. in order to mitigate this, we use a single
+        // releaser object per registration rather than per delivery.
+        final ExternalWakeLockReleaser mWakeLockReleaser;
+
         private volatile ProviderTransport mProviderTransport;
         private int mNumLocationsDelivered = 0;
         private long mExpirationRealtimeMs = Long.MAX_VALUE;
@@ -727,6 +747,7 @@
                     .newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKELOCK_TAG);
             mWakeLock.setReferenceCounted(true);
             mWakeLock.setWorkSource(request.getWorkSource());
+            mWakeLockReleaser = new ExternalWakeLockReleaser(identity, mWakeLock);
         }
 
         @Override
@@ -943,7 +964,7 @@
                     }
 
                     listener.deliverOnLocationChanged(deliverLocationResult,
-                            mUseWakeLock ? mWakeLock::release : null);
+                            mUseWakeLock ? mWakeLockReleaser : null);
                     EVENT_LOG.logProviderDeliveredLocations(mName, locationResult.size(),
                             getIdentity());
                 }
@@ -2761,7 +2782,7 @@
         @GuardedBy("this")
         private boolean mRun;
 
-        GatedCallback(Runnable callback) {
+        GatedCallback(@Nullable Runnable callback) {
             mCallback = callback;
         }
 
@@ -2796,4 +2817,24 @@
             }
         }
     }
+
+    private static class ExternalWakeLockReleaser extends IRemoteCallback.Stub {
+
+        private final CallerIdentity mIdentity;
+        private final PowerManager.WakeLock mWakeLock;
+
+        ExternalWakeLockReleaser(CallerIdentity identity, PowerManager.WakeLock wakeLock) {
+            mIdentity = identity;
+            mWakeLock = Objects.requireNonNull(wakeLock);
+        }
+
+        @Override
+        public void sendResult(Bundle data) {
+            try {
+                mWakeLock.release();
+            } catch (RuntimeException e) {
+                Log.e(TAG, "wakelock over-released by " + mIdentity, e);
+            }
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index 0564e85..fdcf1fc 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -18,6 +18,7 @@
 
 import static android.content.pm.PackageInstaller.LOCATION_DATA_APP;
 
+import android.Manifest;
 import android.accounts.IAccountManager;
 import android.annotation.NonNull;
 import android.annotation.UserIdInt;
@@ -170,6 +171,20 @@
 
     @Override
     public int onCommand(String cmd) {
+        switch (Binder.getCallingUid()) {
+            case Process.ROOT_UID:
+            case Process.SHELL_UID:
+                break;
+            default:
+                // This is called from a test and is allowed as non-shell with the right permission
+                if ("install-incremental".equals(cmd)) {
+                    mContext.enforceCallingPermission(Manifest.permission.USE_SYSTEM_DATA_LOADERS,
+                            "Caller missing USE_SYSTEM_DATA_LOADERS permission to use " + cmd);
+                } else {
+                    throw new IllegalArgumentException("Caller must be root or shell");
+                }
+        }
+
         if (cmd == null) {
             return handleDefaultCommands(cmd);
         }
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java
index 111087f..a3b0e3e 100644
--- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java
@@ -1197,6 +1197,7 @@
             @Nullable @UserIdInt Integer userId,
             @NonNull Function<String, PackageStateInternal> pkgSettingFunction)
             throws NameNotFoundException {
+        mEnforcer.assertApprovedQuerent(mConnection.getCallingUid(), mProxy);
         synchronized (mLock) {
             mDebug.printState(writer, packageName, userId, pkgSettingFunction, mAttachedPkgStates);
         }
diff --git a/services/core/java/com/android/server/tv/interactive/TvIAppManagerService.java b/services/core/java/com/android/server/tv/interactive/TvIAppManagerService.java
index 122b3f3..1aa7598 100644
--- a/services/core/java/com/android/server/tv/interactive/TvIAppManagerService.java
+++ b/services/core/java/com/android/server/tv/interactive/TvIAppManagerService.java
@@ -42,6 +42,7 @@
 import android.media.tv.interactive.TvIAppService;
 import android.net.Uri;
 import android.os.Binder;
+import android.os.Bundle;
 import android.os.IBinder;
 import android.os.Process;
 import android.os.RemoteCallbackList;
@@ -652,6 +653,9 @@
                         userState.mServiceStateMap.put(componentName, serviceState);
                     } else if (serviceState.mService != null) {
                         serviceState.mService.prepare(type);
+                    } else {
+                        serviceState.mPendingPrepare = true;
+                        serviceState.mPendingPrepareType = type;
                     }
                 }
             } catch (RemoteException e) {
@@ -662,6 +666,40 @@
         }
 
         @Override
+        public void notifyAppLinkInfo(String tiasId, Bundle appLinkInfo, int userId) {
+            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
+                    Binder.getCallingUid(), userId, "notifyAppLinkInfo");
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                synchronized (mLock) {
+                    UserState userState = getOrCreateUserStateLocked(resolvedUserId);
+                    TvIAppState iAppState = userState.mIAppMap.get(tiasId);
+                    if (iAppState == null) {
+                        Slogf.e(TAG, "failed to notifyAppLinkInfo - unknown TIAS id "
+                                + tiasId);
+                        return;
+                    }
+                    ComponentName componentName = iAppState.mInfo.getComponent();
+                    ServiceState serviceState = userState.mServiceStateMap.get(componentName);
+                    if (serviceState == null) {
+                        serviceState = new ServiceState(
+                                componentName, tiasId, resolvedUserId);
+                        serviceState.addPendingAppLink(appLinkInfo);
+                        userState.mServiceStateMap.put(componentName, serviceState);
+                    } else if (serviceState.mService != null) {
+                        serviceState.mService.notifyAppLinkInfo(appLinkInfo);
+                    } else {
+                        serviceState.addPendingAppLink(appLinkInfo);
+                    }
+                }
+            } catch (RemoteException e) {
+                Slogf.e(TAG, "error in notifyAppLinkInfo", e);
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+
+        @Override
         public void createSession(final ITvIAppClient client, final String iAppServiceId, int type,
                 int seq, int userId) {
             final int callingUid = Binder.getCallingUid();
@@ -1237,6 +1275,7 @@
         private final ServiceConnection mConnection;
         private final ComponentName mComponent;
         private final String mIAppSeriviceId;
+        private final List<Bundle> mPendingAppLinkInfo = new ArrayList<>();
 
         private boolean mPendingPrepare = false;
         private Integer mPendingPrepareType = null;
@@ -1257,6 +1296,10 @@
             mConnection = new IAppServiceConnection(component, userId);
             mIAppSeriviceId = tias;
         }
+
+        private void addPendingAppLink(Bundle info) {
+            mPendingAppLinkInfo.add(info);
+        }
     }
 
     private final class IAppServiceConnection implements ServiceConnection {
@@ -1296,6 +1339,23 @@
                     }
                 }
 
+                if (!serviceState.mPendingAppLinkInfo.isEmpty()) {
+                    for (Iterator<Bundle> it = serviceState.mPendingAppLinkInfo.iterator();
+                            it.hasNext(); ) {
+                        Bundle appLinkInfo = it.next();
+                        final long identity = Binder.clearCallingIdentity();
+                        try {
+                            serviceState.mService.notifyAppLinkInfo(appLinkInfo);
+                            it.remove();
+                        } catch (RemoteException e) {
+                            Slogf.e(TAG, "error in notifyAppLinkInfo(" + appLinkInfo
+                                    + ") when onServiceConnected", e);
+                        } finally {
+                            Binder.restoreCallingIdentity(identity);
+                        }
+                    }
+                }
+
                 List<IBinder> tokensToBeRemoved = new ArrayList<>();
 
                 // And create sessions, if any.
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index eb68952..44d3623 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -167,7 +167,6 @@
 import android.content.res.Configuration;
 import android.graphics.Bitmap;
 import android.graphics.ColorSpace;
-import android.graphics.GraphicBuffer;
 import android.graphics.Insets;
 import android.graphics.Point;
 import android.graphics.Rect;
@@ -4092,8 +4091,7 @@
         // Make IME snapshot as trusted overlay
         InputMonitor.setTrustedOverlayInputInfo(imeSurface, t, getDisplayId(),
                 "IME-snapshot-surface");
-        GraphicBuffer graphicBuffer = GraphicBuffer.createFromHardwareBuffer(buffer);
-        t.setBuffer(imeSurface, graphicBuffer);
+        t.setBuffer(imeSurface, buffer);
         t.setColorSpace(mSurfaceControl, ColorSpace.get(ColorSpace.Named.SRGB));
         t.setRelativeLayer(imeSurface, activity.getSurfaceControl(), 1);
         t.setPosition(imeSurface, mInputMethodWindow.getDisplayFrame().left,
diff --git a/services/core/jni/com_android_server_UsbAlsaJackDetector.cpp b/services/core/jni/com_android_server_UsbAlsaJackDetector.cpp
index ccb4f59..1c574fb 100644
--- a/services/core/jni/com_android_server_UsbAlsaJackDetector.cpp
+++ b/services/core/jni/com_android_server_UsbAlsaJackDetector.cpp
@@ -151,13 +151,8 @@
         return -1;
     }
 
-    if (!jniRegisterNativeMethods(env, "com/android/server/usb/UsbAlsaJackDetector",
-            method_table, NELEM(method_table))) {
-      ALOGE("Can't register UsbAlsaJackDetector native methods");
-      return -1;
-    }
-
-    return 0;
+    return jniRegisterNativeMethods(env, "com/android/server/usb/UsbAlsaJackDetector",
+            method_table, NELEM(method_table));
 }
 
 }
diff --git a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
index 06f5aed..f0f779d 100644
--- a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
+++ b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
@@ -997,7 +997,7 @@
         }
     }
 
-    if (gnssHalAidl != nullptr) {
+    if (gnssHalAidl != nullptr && gnssHalAidl->getInterfaceVersion() >= 2) {
         sp<IAGnssAidl> agnssAidl;
         auto status = gnssHalAidl->getExtensionAGnss(&agnssAidl);
         if (checkAidlStatus(status, "Unable to get a handle to AGnss interface.")) {
diff --git a/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java b/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java
index fd295c0..9e83f8e 100644
--- a/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java
+++ b/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java
@@ -278,7 +278,7 @@
 
         assertThat(mBackupManagerService.getPendingInits()).isEmpty();
         assertThat(mBackupManagerService.isBackupRunning()).isFalse();
-        assertThat(mBackupManagerService.getCurrentOperations().size()).isEqualTo(0);
+        assertThat(mBackupManagerService.getOperationStorage().numOperations()).isEqualTo(0);
         verify(mOldJournal).delete();
     }
 
@@ -449,7 +449,7 @@
 
         assertThat(mBackupManagerService.getPendingInits()).isEmpty();
         assertThat(mBackupManagerService.isBackupRunning()).isFalse();
-        assertThat(mBackupManagerService.getCurrentOperations().size()).isEqualTo(0);
+        assertThat(mBackupManagerService.getOperationStorage().numOperations()).isEqualTo(0);
         assertThat(mBackupManagerService.getCurrentToken()).isEqualTo(1234L);
         verify(mBackupManagerService).writeRestoreTokens();
         verify(mOldJournal).delete();
@@ -2665,6 +2665,7 @@
         KeyValueBackupTask task =
                 new KeyValueBackupTask(
                         mBackupManagerService,
+                        mBackupManagerService.getOperationStorage(),
                         transportMock.mTransportConnection,
                         transportMock.transportData.transportDirName,
                         queue,
diff --git a/services/robotests/backup/src/com/android/server/backup/restore/ActiveRestoreSessionTest.java b/services/robotests/backup/src/com/android/server/backup/restore/ActiveRestoreSessionTest.java
index 9eb99ae..e0812d6 100644
--- a/services/robotests/backup/src/com/android/server/backup/restore/ActiveRestoreSessionTest.java
+++ b/services/robotests/backup/src/com/android/server/backup/restore/ActiveRestoreSessionTest.java
@@ -51,6 +51,7 @@
 
 import com.android.server.EventLogTags;
 import com.android.server.backup.BackupAgentTimeoutParameters;
+import com.android.server.backup.OperationStorage;
 import com.android.server.backup.TransportManager;
 import com.android.server.backup.UserBackupManagerService;
 import com.android.server.backup.internal.BackupHandler;
@@ -98,6 +99,7 @@
     @Mock private IRestoreObserver mObserver;
     @Mock private IBackupManagerMonitor mMonitor;
     @Mock private BackupEligibilityRules mBackupEligibilityRules;
+    @Mock private OperationStorage mOperationStorage;
     private ShadowLooper mShadowBackupLooper;
     private ShadowApplication mShadowApplication;
     private UserBackupManagerService.BackupWakeLock mWakeLock;
@@ -132,7 +134,9 @@
         // We need to mock BMS timeout parameters before initializing the BackupHandler since
         // the constructor of BackupHandler relies on it.
         when(mBackupManagerService.getAgentTimeoutParameters()).thenReturn(agentTimeoutParameters);
-        BackupHandler backupHandler = new BackupHandler(mBackupManagerService, handlerThread);
+
+        BackupHandler backupHandler =
+                new BackupHandler(mBackupManagerService, mOperationStorage, handlerThread);
 
         mWakeLock = createBackupWakeLock(application);
 
diff --git a/services/robotests/backup/src/com/android/server/backup/testing/BackupManagerServiceTestUtils.java b/services/robotests/backup/src/com/android/server/backup/testing/BackupManagerServiceTestUtils.java
index 77b5b61..fc3ec7b 100644
--- a/services/robotests/backup/src/com/android/server/backup/testing/BackupManagerServiceTestUtils.java
+++ b/services/robotests/backup/src/com/android/server/backup/testing/BackupManagerServiceTestUtils.java
@@ -120,7 +120,6 @@
         when(backupManagerService.getTransportManager()).thenReturn(transportManager);
         when(backupManagerService.getPackageManager()).thenReturn(packageManager);
         when(backupManagerService.getBackupHandler()).thenReturn(backupHandler);
-        when(backupManagerService.getCurrentOpLock()).thenReturn(new Object());
         when(backupManagerService.getQueueLock()).thenReturn(new Object());
         when(backupManagerService.getActivityManager()).thenReturn(mock(IActivityManager.class));
         when(backupManagerService.getWakelock()).thenReturn(wakeLock);
diff --git a/services/robotests/src/com/android/server/testing/shadows/ShadowKeyValueBackupTask.java b/services/robotests/src/com/android/server/testing/shadows/ShadowKeyValueBackupTask.java
index 06b7fb7..6a7d031 100644
--- a/services/robotests/src/com/android/server/testing/shadows/ShadowKeyValueBackupTask.java
+++ b/services/robotests/src/com/android/server/testing/shadows/ShadowKeyValueBackupTask.java
@@ -19,6 +19,7 @@
 import android.annotation.Nullable;
 
 import com.android.server.backup.DataChangedJournal;
+import com.android.server.backup.OperationStorage;
 import com.android.server.backup.UserBackupManagerService;
 import com.android.server.backup.internal.OnTaskFinishedListener;
 import com.android.server.backup.keyvalue.KeyValueBackupReporter;
@@ -56,6 +57,7 @@
     @Implementation
     protected void __constructor__(
             UserBackupManagerService backupManagerService,
+            OperationStorage operationStorage,
             TransportConnection transportConnection,
             String transportDirName,
             List<String> queue,
diff --git a/services/robotests/src/com/android/server/testing/shadows/ShadowPerformUnifiedRestoreTask.java b/services/robotests/src/com/android/server/testing/shadows/ShadowPerformUnifiedRestoreTask.java
index 71010a9..d985e1b 100644
--- a/services/robotests/src/com/android/server/testing/shadows/ShadowPerformUnifiedRestoreTask.java
+++ b/services/robotests/src/com/android/server/testing/shadows/ShadowPerformUnifiedRestoreTask.java
@@ -21,6 +21,7 @@
 import android.app.backup.IRestoreObserver;
 import android.content.pm.PackageInfo;
 
+import com.android.server.backup.OperationStorage;
 import com.android.server.backup.UserBackupManagerService;
 import com.android.server.backup.internal.OnTaskFinishedListener;
 import com.android.server.backup.restore.PerformUnifiedRestoreTask;
@@ -57,6 +58,7 @@
     @Implementation
     protected void __constructor__(
             UserBackupManagerService backupManagerService,
+            OperationStorage operationStorage,
             TransportConnection transportConnection,
             IRestoreObserver observer,
             IBackupManagerMonitor monitor,
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt
index d1d7cc6..d9dbf48 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt
@@ -19,6 +19,7 @@
 import android.content.Context
 import android.content.Intent
 import android.content.pm.PackageManager
+import android.content.pm.SigningDetails
 import android.content.pm.parsing.component.ParsedActivityImpl
 import android.content.pm.parsing.component.ParsedIntentInfoImpl
 import android.content.pm.verify.domain.DomainVerificationManager
@@ -26,6 +27,7 @@
 import android.os.Build
 import android.os.Process
 import android.util.ArraySet
+import android.util.IndentingPrintWriter
 import android.util.SparseArray
 import androidx.test.platform.app.InstrumentationRegistry
 import com.android.server.pm.parsing.pkg.AndroidPackage
@@ -46,6 +48,7 @@
 import org.mockito.Mockito.anyLong
 import org.mockito.Mockito.anyString
 import org.mockito.Mockito.eq
+import org.mockito.Mockito.mock
 import org.mockito.Mockito.verifyNoMoreInteractions
 import java.util.UUID
 import java.util.concurrent.atomic.AtomicBoolean
@@ -204,6 +207,14 @@
                 service(Type.QUERENT, "getInfo") {
                     getDomainVerificationInfo(it.targetPackageName)
                 },
+                service(Type.QUERENT, "printState") {
+                    printState(mock(IndentingPrintWriter::class.java), null, null)
+                },
+                service(Type.QUERENT, "printStateInternal") {
+                    printState(mock(IndentingPrintWriter::class.java), null, null) {
+                        mockPkgState(it, UUID.randomUUID())
+                    }
+                },
                 service(Type.VERIFIER, "setStatus") {
                     setDomainVerificationStatus(
                         it.targetDomainSetId,
@@ -311,6 +322,7 @@
                     }
                 )
             }
+            whenever(signingDetails) { SigningDetails.UNKNOWN }
         }
 
         fun mockPkgState(packageName: String, domainSetId: UUID) =
@@ -327,6 +339,7 @@
                     }
                 }
                 whenever(isSystem) { false }
+                whenever(signingDetails) { SigningDetails.UNKNOWN }
             }
     }
 
@@ -794,8 +807,12 @@
             }
 
             val valueAsInt = value as? Int
-            if (valueAsInt != null && valueAsInt == DomainVerificationManager.STATUS_OK) {
-                throw AssertionError("Expected call to return false, was $value")
+            if (valueAsInt != null) {
+                if (valueAsInt == DomainVerificationManager.STATUS_OK) {
+                    throw AssertionError("Expected call to return false, was $value")
+                }
+            } else {
+                throw AssertionError("Expected call to fail")
             }
         } catch (e: SecurityException) {
         } catch (e: PackageManager.NameNotFoundException) {
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java
index f92b872..28c1c81 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java
@@ -585,8 +585,8 @@
         doAnswer((invocation) -> {
             ((Region) invocation.getArguments()[1]).set(region);
             return null;
-        }).when(mMockMagnificationProcessor).getMagnificationRegion(eq(displayId),
-                any(), anyBoolean());
+        }).when(mMockMagnificationProcessor).getFullscreenMagnificationRegion(eq(displayId), any(),
+                anyBoolean());
         when(mMockSystemSupport.getCurrentUserIdLocked()).thenReturn(USER_ID2);
 
         final Region result = mServiceConnection.getMagnificationRegion(displayId);
@@ -620,7 +620,8 @@
     @Test
     public void resetMagnification() {
         final int displayId = 1;
-        when(mMockMagnificationProcessor.reset(displayId, true)).thenReturn(true);
+        when(mMockMagnificationProcessor.resetFullscreenMagnification(displayId, true)).thenReturn(
+                true);
 
         final boolean result = mServiceConnection.resetMagnification(displayId, true);
         assertThat(result, is(true));
@@ -629,7 +630,8 @@
     @Test
     public void resetMagnification_cantControlMagnification_returnFalse() {
         final int displayId = 1;
-        when(mMockMagnificationProcessor.reset(displayId, true)).thenReturn(true);
+        when(mMockMagnificationProcessor.resetFullscreenMagnification(displayId, true)).thenReturn(
+                true);
         when(mMockSecurityPolicy.canControlMagnification(mServiceConnection)).thenReturn(false);
 
         final boolean result = mServiceConnection.resetMagnification(displayId, true);
@@ -639,7 +641,8 @@
     @Test
     public void resetMagnification_serviceNotBelongCurrentUser_returnFalse() {
         final int displayId = 1;
-        when(mMockMagnificationProcessor.reset(displayId, true)).thenReturn(true);
+        when(mMockMagnificationProcessor.resetFullscreenMagnification(displayId, true)).thenReturn(
+                true);
         when(mMockSystemSupport.getCurrentUserIdLocked()).thenReturn(USER_ID2);
 
         final boolean result = mServiceConnection.resetMagnification(displayId, true);
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/MagnificationProcessorTest.java b/services/tests/servicestests/src/com/android/server/accessibility/MagnificationProcessorTest.java
index 9ebec98..621507e 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/MagnificationProcessorTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/MagnificationProcessorTest.java
@@ -98,7 +98,7 @@
                 .setScale(TEST_SCALE).build();
         setMagnificationActivated(TEST_DISPLAY, config);
 
-        float scale = mMagnificationProcessor.getScale(TEST_DISPLAY);
+        float scale = mMagnificationProcessor.getMagnificationConfig(TEST_DISPLAY).getScale();
 
         assertEquals(scale, TEST_SCALE, 0);
     }
@@ -111,20 +111,19 @@
         setMagnificationActivated(TEST_DISPLAY, config);
 
         float centerX = mMagnificationProcessor.getCenterX(
-                TEST_DISPLAY,  /* canControlMagnification= */true);
+                TEST_DISPLAY, /* canControlMagnification= */true);
 
         assertEquals(centerX, TEST_CENTER_X, 0);
     }
 
     @Test
-    public void getCenterX_canControlWindowMagnification_returnCenterX() {
+    public void getCenterX_controlWindowMagnification_returnCenterX() {
         final MagnificationConfig config = new MagnificationConfig.Builder()
                 .setMode(MAGNIFICATION_MODE_WINDOW)
                 .setCenterX(TEST_CENTER_X).build();
         setMagnificationActivated(TEST_DISPLAY, config);
 
-        float centerX = mMagnificationProcessor.getCenterX(
-                TEST_DISPLAY,  /* canControlMagnification= */true);
+        float centerX = mMagnificationProcessor.getMagnificationConfig(TEST_DISPLAY).getCenterX();
 
         assertEquals(centerX, TEST_CENTER_X, 0);
     }
@@ -143,14 +142,13 @@
     }
 
     @Test
-    public void getCenterY_canControlWindowMagnification_returnCenterY() {
+    public void getCenterY_controlWindowMagnification_returnCenterY() {
         final MagnificationConfig config = new MagnificationConfig.Builder()
                 .setMode(MAGNIFICATION_MODE_WINDOW)
                 .setCenterY(TEST_CENTER_Y).build();
         setMagnificationActivated(TEST_DISPLAY, config);
 
-        float centerY = mMagnificationProcessor.getCenterY(
-                TEST_DISPLAY,  /* canControlMagnification= */false);
+        float centerY = mMagnificationProcessor.getMagnificationConfig(TEST_DISPLAY).getCenterY();
 
         assertEquals(centerY, TEST_CENTER_Y, 0);
     }
@@ -159,7 +157,7 @@
     public void getMagnificationRegion_canControlFullscreenMagnification_returnRegion() {
         final Region region = new Region(10, 20, 100, 200);
         setMagnificationActivated(TEST_DISPLAY, MAGNIFICATION_MODE_FULLSCREEN);
-        mMagnificationProcessor.getMagnificationRegion(TEST_DISPLAY,
+        mMagnificationProcessor.getFullscreenMagnificationRegion(TEST_DISPLAY,
                 region,  /* canControlMagnification= */true);
 
         verify(mMockFullScreenMagnificationController).getMagnificationRegion(eq(TEST_DISPLAY),
@@ -167,17 +165,6 @@
     }
 
     @Test
-    public void getMagnificationRegion_canControlWindowMagnification_returnRegion() {
-        final Region region = new Region(10, 20, 100, 200);
-        setMagnificationActivated(TEST_DISPLAY, MAGNIFICATION_MODE_WINDOW);
-        mMagnificationProcessor.getMagnificationRegion(TEST_DISPLAY,
-                region,  /* canControlMagnification= */true);
-
-        verify(mMockWindowMagnificationManager).getMagnificationSourceBounds(eq(TEST_DISPLAY),
-                eq(region));
-    }
-
-    @Test
     public void getMagnificationRegion_fullscreenModeNotRegistered_shouldRegisterThenUnregister() {
         final Region region = new Region(10, 20, 100, 200);
         setMagnificationActivated(TEST_DISPLAY, MAGNIFICATION_MODE_FULLSCREEN);
@@ -188,7 +175,7 @@
                 any());
 
         final Region result = new Region();
-        mMagnificationProcessor.getMagnificationRegion(TEST_DISPLAY,
+        mMagnificationProcessor.getFullscreenMagnificationRegion(TEST_DISPLAY,
                 result, /* canControlMagnification= */true);
         assertEquals(region, result);
         verify(mMockFullScreenMagnificationController).register(TEST_DISPLAY);
@@ -237,7 +224,7 @@
     public void reset_fullscreenMagnificationActivated() {
         setMagnificationActivated(TEST_DISPLAY, MAGNIFICATION_MODE_FULLSCREEN);
 
-        mMagnificationProcessor.reset(TEST_DISPLAY, /* animate= */false);
+        mMagnificationProcessor.resetFullscreenMagnification(TEST_DISPLAY, /* animate= */false);
 
         verify(mMockFullScreenMagnificationController).reset(TEST_DISPLAY, false);
     }
@@ -246,7 +233,7 @@
     public void reset_windowMagnificationActivated() {
         setMagnificationActivated(TEST_DISPLAY, MAGNIFICATION_MODE_WINDOW);
 
-        mMagnificationProcessor.reset(TEST_DISPLAY, /* animate= */false);
+        mMagnificationProcessor.resetCurrentMagnification(TEST_DISPLAY, /* animate= */false);
 
         verify(mMockWindowMagnificationManager).reset(TEST_DISPLAY);
     }
diff --git a/services/tests/servicestests/src/com/android/server/backup/UserBackupManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/backup/UserBackupManagerServiceTest.java
index c36e1a8..bc95341 100644
--- a/services/tests/servicestests/src/com/android/server/backup/UserBackupManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/backup/UserBackupManagerServiceTest.java
@@ -35,6 +35,7 @@
 
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.server.backup.internal.LifecycleOperationStorage;
 import com.android.server.backup.internal.OnTaskFinishedListener;
 import com.android.server.backup.params.BackupParams;
 import com.android.server.backup.transport.BackupTransportClient;
@@ -60,7 +61,7 @@
     @Mock TransportConnection mTransportConnection;
     @Mock BackupTransportClient mBackupTransport;
     @Mock BackupEligibilityRules mBackupEligibilityRules;
-
+    @Mock LifecycleOperationStorage mOperationStorage;
 
     private TestBackupService mService;
 
@@ -68,7 +69,7 @@
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
 
-        mService = new TestBackupService(mContext, mPackageManager);
+        mService = new TestBackupService(mContext, mPackageManager, mOperationStorage);
         mService.setEnabled(true);
         mService.setSetupComplete(true);
     }
@@ -173,8 +174,9 @@
         boolean isEnabledStatePersisted = false;
         boolean shouldUseNewBackupEligibilityRules = false;
 
-        TestBackupService(Context context, PackageManager packageManager) {
-            super(context, packageManager);
+        TestBackupService(Context context, PackageManager packageManager,
+                LifecycleOperationStorage operationStorage) {
+            super(context, packageManager, operationStorage);
         }
 
         @Override
diff --git a/services/tests/servicestests/src/com/android/server/backup/internal/BackupHandlerTest.java b/services/tests/servicestests/src/com/android/server/backup/internal/BackupHandlerTest.java
index fa35e3f..3c79d8b 100644
--- a/services/tests/servicestests/src/com/android/server/backup/internal/BackupHandlerTest.java
+++ b/services/tests/servicestests/src/com/android/server/backup/internal/BackupHandlerTest.java
@@ -18,7 +18,6 @@
 
 import static org.mockito.Mockito.when;
 import static org.testng.Assert.assertFalse;
-import static org.testng.Assert.assertThrows;
 import static org.testng.Assert.assertTrue;
 
 import android.os.HandlerThread;
@@ -28,6 +27,7 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.server.backup.BackupAgentTimeoutParameters;
+import com.android.server.backup.OperationStorage;
 import com.android.server.backup.UserBackupManagerService;
 
 import org.junit.After;
@@ -46,6 +46,7 @@
     private static final int MESSAGE_TIMEOUT_MINUTES = 1;
 
     @Mock private UserBackupManagerService mUserBackupManagerService;
+    @Mock private OperationStorage mOperationStorage;
     @Mock private BackupAgentTimeoutParameters mTimeoutParameters;
 
     private HandlerThread mHandlerThread;
@@ -114,7 +115,7 @@
         private final boolean mShouldStop;
 
         TestBackupHandler(boolean shouldStop) {
-            super(mUserBackupManagerService, mHandlerThread);
+            super(mUserBackupManagerService, mOperationStorage, mHandlerThread);
 
             mShouldStop = shouldStop;
         }
diff --git a/telephony/java/android/telephony/AccessNetworkConstants.java b/telephony/java/android/telephony/AccessNetworkConstants.java
index 1d7a476..4469ffc 100644
--- a/telephony/java/android/telephony/AccessNetworkConstants.java
+++ b/telephony/java/android/telephony/AccessNetworkConstants.java
@@ -17,6 +17,7 @@
 package android.telephony;
 
 import android.annotation.IntDef;
+import android.annotation.NonNull;
 import android.annotation.SystemApi;
 import android.hardware.radio.V1_5.AccessNetwork;
 
@@ -28,6 +29,8 @@
  */
 public final class AccessNetworkConstants {
 
+    private static final String TAG = AccessNetworkConstants.class.getSimpleName();
+
     /**
      * Wireless transportation type
      *
@@ -108,6 +111,21 @@
                 default: return Integer.toString(type);
             }
         }
+
+        /** @hide */
+        public static @RadioAccessNetworkType int fromString(@NonNull String str) {
+            switch (str.toUpperCase()) {
+                case "GERAN" : return GERAN;
+                case "UTRAN" : return UTRAN;
+                case "EUTRAN" : return EUTRAN;
+                case "CDMA2000" : return CDMA2000;
+                case "IWLAN" : return IWLAN;
+                case "NGRAN" : return NGRAN;
+                default:
+                    Rlog.e(TAG, "Invalid access network type " + str);
+                    return UNKNOWN;
+            }
+        }
     }
 
     /**
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 7d24b76..c80d35b 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -5381,6 +5381,34 @@
     public static final String KEY_UNTHROTTLE_DATA_RETRY_WHEN_TAC_CHANGES_BOOL =
             "unthrottle_data_retry_when_tac_changes_bool";
 
+    /**
+     * IWLAN handover rules that determine whether handover is allowed or disallowed between
+     * cellular and IWLAN.
+     *
+     * The handover rules will be matched in the order. Here are some sample rules.
+     * <string-array name="iwlan_handover_rules" num="5">
+     *     <!-- Handover from IWLAN to 2G/3G is not allowed -->
+     *     <item value="source=IWLAN, target=GERAN|UTRAN, type=disallowed"/>
+     *     <!-- Handover from 2G/3G to IWLAN is not allowed -->
+     *     <item value="source=GERAN|UTRAN, target:IWLAN, type=disallowed"/>
+     *     <!-- Handover from IWLAN to 3G/4G/5G is not allowed if the device is roaming. -->
+     *     <item value="source=IWLAN, target=UTRAN|EUTRAN|NGRAN, roaming=true, type=disallowed"/>
+     *     <!-- Handover from 4G to IWLAN is not allowed -->
+     *     <item value="source=EUTRAN, target=IWLAN, type=disallowed"/>
+     *     <!-- Handover is always allowed in any condition. -->
+     *     <item value="source=GERAN|UTRAN|EUTRAN|NGRAN|IWLAN,
+     *         target=GERAN|UTRAN|EUTRAN|NGRAN|IWLAN, type=allowed"/>
+     * </string-array>
+     *
+     * When handover is not allowed, frameworks will tear down the data network on source transport,
+     * and then setup a new one on the target transport when Qualified Network Service changes the
+     * preferred access networks for particular APN types.
+     *
+     * @hide
+     */
+    public static final String KEY_IWLAN_HANDOVER_POLICY_STRING_ARRAY =
+            "iwlan_handover_policy_string_array";
+
     /** The default value for every variable. */
     private final static PersistableBundle sDefaults;
 
@@ -5953,6 +5981,7 @@
         sDefaults.putInt(
                 KEY_OPPORTUNISTIC_TIME_TO_SCAN_AFTER_CAPABILITY_SWITCH_TO_PRIMARY_LONG,
                 120000);
+        sDefaults.putAll(ImsServiceEntitlement.getDefaults());
         sDefaults.putAll(Gps.getDefaults());
         sDefaults.putIntArray(KEY_CDMA_ENHANCED_ROAMING_INDICATOR_FOR_HOME_NETWORK_INT_ARRAY,
                 new int[] {
@@ -6043,6 +6072,9 @@
         sDefaults.putBoolean(KEY_UNTHROTTLE_DATA_RETRY_WHEN_TAC_CHANGES_BOOL, false);
         sDefaults.putBoolean(KEY_VONR_SETTING_VISIBILITY_BOOL, true);
         sDefaults.putBoolean(KEY_VONR_ENABLED_BOOL, false);
+        sDefaults.putStringArray(KEY_IWLAN_HANDOVER_POLICY_STRING_ARRAY, new String[]{
+                "source=GERAN|UTRAN|EUTRAN|NGRAN|IWLAN, "
+                        + "target=GERAN|UTRAN|EUTRAN|NGRAN|IWLAN, type=allowed"});
     }
 
     /**
diff --git a/telephony/java/com/android/internal/telephony/RILConstants.java b/telephony/java/com/android/internal/telephony/RILConstants.java
index 866fd2c..ba95841 100644
--- a/telephony/java/com/android/internal/telephony/RILConstants.java
+++ b/telephony/java/com/android/internal/telephony/RILConstants.java
@@ -530,6 +530,8 @@
     int RIL_REQUEST_GET_SLICING_CONFIG = 224;
     int RIL_REQUEST_ENABLE_VONR = 225;
     int RIL_REQUEST_IS_VONR_ENABLED = 226;
+    int RIL_REQUEST_SET_USAGE_SETTING = 227;
+    int RIL_REQUEST_GET_USAGE_SETTING = 228;
 
     /* Responses begin */
     int RIL_RESPONSE_ACKNOWLEDGEMENT = 800;