Merge "Finish removing NEW_FOOTER flag" into tm-dev
diff --git a/Android.bp b/Android.bp
index cd110de..8ac7de9 100644
--- a/Android.bp
+++ b/Android.bp
@@ -324,6 +324,7 @@
         "av-types-aidl-java",
         "tv_tuner_resource_manager_aidl_interface-java",
         "soundtrigger_middleware-aidl-java",
+        "modules-utils-build",
         "modules-utils-preconditions",
         "modules-utils-synchronous-result-receiver",
         "modules-utils-os",
diff --git a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
index fde59ea..c69e901 100644
--- a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
+++ b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
@@ -281,7 +281,7 @@
     private static final long NETWORK_SCORER_CACHE_DURATION_MILLIS = 5000L;
 
     // Cache the device provisioning package queried from resource config_deviceProvisioningPackage.
-    // Note that there is no synchronization on this method which is okay since in the worst case
+    // Note that there is no synchronization on this variable which is okay since in the worst case
     // scenario, they might be a few extra reads from resources.
     private String mCachedDeviceProvisioningPackage = null;
 
@@ -394,7 +394,7 @@
     private boolean mAllowRestrictedBucket;
 
     private volatile boolean mAppIdleEnabled;
-    private boolean mIsCharging;
+    private volatile boolean mIsCharging;
     private boolean mSystemServicesReady = false;
     // There was a system update, defaults need to be initialized after services are ready
     private boolean mPendingInitializeDefaults;
@@ -721,12 +721,10 @@
 
     @VisibleForTesting
     void setChargingState(boolean isCharging) {
-        synchronized (mAppIdleLock) {
-            if (mIsCharging != isCharging) {
-                if (DEBUG) Slog.d(TAG, "Setting mIsCharging to " + isCharging);
-                mIsCharging = isCharging;
-                postParoleStateChanged();
-            }
+        if (mIsCharging != isCharging) {
+            if (DEBUG) Slog.d(TAG, "Setting mIsCharging to " + isCharging);
+            mIsCharging = isCharging;
+            postParoleStateChanged();
         }
     }
 
@@ -1340,16 +1338,12 @@
     @Override
     public boolean isAppIdleFiltered(String packageName, int appId, int userId,
             long elapsedRealtime) {
-        if (getAppMinBucket(packageName, appId, userId) < AppIdleHistory.IDLE_BUCKET_CUTOFF) {
+        if (!mAppIdleEnabled || mIsCharging) {
             return false;
-        } else {
-            synchronized (mAppIdleLock) {
-                if (!mAppIdleEnabled || mIsCharging) {
-                    return false;
-                }
-            }
-            return isAppIdleUnfiltered(packageName, userId, elapsedRealtime);
         }
+
+        return isAppIdleUnfiltered(packageName, userId, elapsedRealtime)
+                && getAppMinBucket(packageName, appId, userId) >= AppIdleHistory.IDLE_BUCKET_CUTOFF;
     }
 
     static boolean isUserUsage(int reason) {
@@ -1803,7 +1797,7 @@
         }
     }
 
-    private boolean isActiveNetworkScorer(String packageName) {
+    private boolean isActiveNetworkScorer(@NonNull String packageName) {
         // Validity of network scorer cache is limited to a few seconds. Fetch it again
         // if longer since query.
         // This is a temporary optimization until there's a callback mechanism for changes to network scorer.
@@ -1813,7 +1807,7 @@
             mCachedNetworkScorer = mInjector.getActiveNetworkScorer();
             mCachedNetworkScorerAtMillis = now;
         }
-        return packageName != null && packageName.equals(mCachedNetworkScorer);
+        return packageName.equals(mCachedNetworkScorer);
     }
 
     private void informListeners(String packageName, int userId, int bucket, int reason,
@@ -2322,8 +2316,8 @@
          * Returns {@code true} if the supplied package is the wellbeing app. Otherwise,
          * returns {@code false}.
          */
-        boolean isWellbeingPackage(String packageName) {
-            return mWellbeingApp != null && mWellbeingApp.equals(packageName);
+        boolean isWellbeingPackage(@NonNull String packageName) {
+            return packageName.equals(mWellbeingApp);
         }
 
         boolean hasExactAlarmPermission(String packageName, int uid) {
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index c022bf3..9c391ab 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -562,14 +562,15 @@
     public abstract void unregisterProcessObserver(IProcessObserver processObserver);
 
     /**
-     * Checks if there is an unfinished instrumentation that targets the given uid.
+     * Gets the uid of the instrumentation source if there is an unfinished instrumentation that
+     * targets the given uid.
      *
      * @param uid The uid to be checked for
      *
-     * @return True, if there is an instrumentation whose target application uid matches the given
-     * uid, false otherwise
+     * @return the uid of the instrumentation source, if there is an instrumentation whose target
+     * application uid matches the given uid, and {@link android.os.Process#INVALID_UID} otherwise.
      */
-    public abstract boolean isUidCurrentlyInstrumented(int uid);
+    public abstract int getInstrumentationSourceUid(int uid);
 
     /** Is this a device owner app? */
     public abstract boolean isDeviceOwner(int uid);
@@ -791,10 +792,11 @@
          *
          * @param packageName The package name of the process.
          * @param uid The UID of the process.
-         * @param foregroundId The current foreground service notification ID, a negative value
-         *                     means this notification is being removed.
+         * @param foregroundId The current foreground service notification ID.
+         * @param canceling The given notification is being canceled.
          */
-        void onForegroundServiceNotificationUpdated(String packageName, int uid, int foregroundId);
+        void onForegroundServiceNotificationUpdated(String packageName, int uid, int foregroundId,
+                boolean canceling);
     }
 
     /**
diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java
index 638b30e..e9df50f 100644
--- a/core/java/android/app/Instrumentation.java
+++ b/core/java/android/app/Instrumentation.java
@@ -1058,10 +1058,11 @@
     }
     
     /**
-     * Sends the key events corresponding to the text to the app being
-     * instrumented.
-     * 
-     * @param text The text to be sent. 
+     * Sends the key events that result in the given text being typed into the currently focused
+     * window, and waits for it to be processed.
+     *
+     * @param text The text to be sent.
+     * @see #sendKeySync(KeyEvent)
      */
     public void sendStringSync(String text) {
         if (text == null) {
@@ -1084,11 +1085,12 @@
     }
 
     /**
-     * Send a key event to the currently focused window/view and wait for it to
-     * be processed.  Finished at some point after the recipient has returned
-     * from its event processing, though it may <em>not</em> have completely
-     * finished reacting from the event -- for example, if it needs to update
-     * its display as a result, it may still be in the process of doing that.
+     * Sends a key event to the currently focused window, and waits for it to be processed.
+     * <p>
+     * This method blocks until the recipient has finished handling the event. Note that the
+     * recipient may <em>not</em> have completely finished reacting from the event when this method
+     * returns. For example, it may still be in the process of updating its display or UI contents
+     * upon reacting to the injected event.
      *
      * @param event The event to send to the current focus.
      */
@@ -1116,34 +1118,42 @@
     }
 
     /**
-     * Sends an up and down key event sync to the currently focused window.
+     * Sends up and down key events with the given key code to the currently focused window, and
+     * waits for it to be processed.
      * 
-     * @param key The integer keycode for the event.
+     * @param keyCode The key code for the events to send.
+     * @see #sendKeySync(KeyEvent)
      */
-    public void sendKeyDownUpSync(int key) {        
-        sendKeySync(new KeyEvent(KeyEvent.ACTION_DOWN, key));
-        sendKeySync(new KeyEvent(KeyEvent.ACTION_UP, key));
-    }
-
-    /**
-     * Higher-level method for sending both the down and up key events for a
-     * particular character key code.  Equivalent to creating both KeyEvent
-     * objects by hand and calling {@link #sendKeySync}.  The event appears
-     * as if it came from keyboard 0, the built in one.
-     * 
-     * @param keyCode The key code of the character to send.
-     */
-    public void sendCharacterSync(int keyCode) {
+    public void sendKeyDownUpSync(int keyCode) {
         sendKeySync(new KeyEvent(KeyEvent.ACTION_DOWN, keyCode));
         sendKeySync(new KeyEvent(KeyEvent.ACTION_UP, keyCode));
     }
-    
+
     /**
-     * Dispatch a pointer event. Finished at some point after the recipient has
-     * returned from its event processing, though it may <em>not</em> have
-     * completely finished reacting from the event -- for example, if it needs
-     * to update its display as a result, it may still be in the process of
-     * doing that.
+     * Sends up and down key events with the given key code to the currently focused window, and
+     * waits for it to be processed.
+     * <p>
+     * Equivalent to {@link #sendKeyDownUpSync(int)}.
+     *
+     * @param keyCode The key code of the character to send.
+     * @see #sendKeySync(KeyEvent)
+     */
+    public void sendCharacterSync(int keyCode) {
+        sendKeyDownUpSync(keyCode);
+    }
+
+    /**
+     * Dispatches a pointer event into a window owned by the instrumented application, and waits for
+     * it to be processed.
+     * <p>
+     * If the motion event being injected is targeted at a window that is not owned by the
+     * instrumented application, the input injection will fail. See {@link #getUiAutomation()} for
+     * injecting events into all windows.
+     * <p>
+     * This method blocks until the recipient has finished handling the event. Note that the
+     * recipient may <em>not</em> have completely finished reacting from the event when this method
+     * returns. For example, it may still be in the process of updating its display or UI contents
+     * upon reacting to the injected event.
      * 
      * @param event A motion event describing the pointer action.  (As noted in 
      * {@link MotionEvent#obtain(long, long, int, float, float, int)}, be sure to use 
@@ -1155,10 +1165,10 @@
             event.setSource(InputDevice.SOURCE_TOUCHSCREEN);
         }
 
-        syncInputTransactionsAndInjectEvent(event);
+        syncInputTransactionsAndInjectEventIntoSelf(event);
     }
 
-    private void syncInputTransactionsAndInjectEvent(MotionEvent event) {
+    private void syncInputTransactionsAndInjectEventIntoSelf(MotionEvent event) {
         final boolean syncBefore = event.getAction() == MotionEvent.ACTION_DOWN
                 || event.isFromSource(InputDevice.SOURCE_MOUSE);
         final boolean syncAfter = event.getAction() == MotionEvent.ACTION_UP;
@@ -1169,8 +1179,9 @@
                         .syncInputTransactions(true /*waitForAnimations*/);
             }
 
+            // Direct the injected event into windows owned by the instrumentation target.
             InputManager.getInstance().injectInputEvent(
-                    event, InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_RESULT);
+                    event, InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_RESULT, Process.myUid());
 
             if (syncAfter) {
                 WindowManagerGlobal.getWindowManagerService()
@@ -1182,19 +1193,26 @@
     }
 
     /**
-     * Dispatch a trackball event. Finished at some point after the recipient has
-     * returned from its event processing, though it may <em>not</em> have
-     * completely finished reacting from the event -- for example, if it needs
-     * to update its display as a result, it may still be in the process of
-     * doing that.
-     * 
+     * Dispatches a trackball event into the currently focused window, and waits for it to be
+     * processed.
+     * <p>
+     * This method blocks until the recipient has finished handling the event. Note that the
+     * recipient may <em>not</em> have completely finished reacting from the event when this method
+     * returns. For example, it may still be in the process of updating its display or UI contents
+     * upon reacting to the injected event.
+     *
      * @param event A motion event describing the trackball action.  (As noted in 
      * {@link MotionEvent#obtain(long, long, int, float, float, int)}, be sure to use 
      * {@link SystemClock#uptimeMillis()} as the timebase.
      */
     public void sendTrackballEventSync(MotionEvent event) {
         validateNotAppThread();
-        if ((event.getSource() & InputDevice.SOURCE_CLASS_TRACKBALL) == 0) {
+        if (event.isFromSource(InputDevice.SOURCE_CLASS_POINTER)) {
+            throw new IllegalArgumentException(
+                    "Cannot inject pointer events from sendTrackballEventSync()."
+                            + " Use sendPointerSync() to inject pointer events.");
+        }
+        if (!event.isFromSource(InputDevice.SOURCE_CLASS_TRACKBALL)) {
             event.setSource(InputDevice.SOURCE_TRACKBALL);
         }
         InputManager.getInstance().injectInputEvent(event,
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 5dd68a9..0d258ce 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -3054,13 +3054,11 @@
      *
      * @param receiver The BroadcastReceiver to handle the broadcast.
      * @param filter Selects the Intent broadcasts to be received.
-     * @param flags Additional options for the receiver. For apps targeting
-     * {@link android.os.Build.VERSION_CODES#TIRAMISU},
-     *              either {@link #RECEIVER_EXPORTED} or
-     * {@link #RECEIVER_NOT_EXPORTED} must be specified if the receiver isn't being registered
-     *              for <a href="{@docRoot}guide/components/broadcasts#system-broadcasts">system
-     *              broadcasts</a> or an exception will be thrown. If
-     *              {@link #RECEIVER_EXPORTED} is specified, a receiver may additionally
+     * @param flags Additional options for the receiver. In a future release, either
+     * {@link #RECEIVER_EXPORTED} or {@link #RECEIVER_NOT_EXPORTED} must be specified if the
+     *             receiver isn't being registered for <a href="{@docRoot}guide/components
+     *              /broadcasts#system-broadcasts">system broadcasts</a> or an exception will be
+     *              thrown. If {@link #RECEIVER_EXPORTED} is specified, a receiver may additionally
      *              specify {@link #RECEIVER_VISIBLE_TO_INSTANT_APPS}. For a complete list of
      *              system broadcast actions, see the BROADCAST_ACTIONS.TXT file in the
      *              Android SDK. If both {@link #RECEIVER_EXPORTED} and
@@ -3137,13 +3135,11 @@
      *      no permission is required.
      * @param scheduler Handler identifying the thread that will receive
      *      the Intent.  If null, the main thread of the process will be used.
-     * @param flags Additional options for the receiver. For apps targeting
-     * {@link android.os.Build.VERSION_CODES#TIRAMISU},
-     *              either {@link #RECEIVER_EXPORTED} or
-     * {@link #RECEIVER_NOT_EXPORTED} must be specified if the receiver isn't being registered
-     *              for <a href="{@docRoot}guide/components/broadcasts#system-broadcasts">system
-     *              broadcasts</a> or an exception will be thrown. If
-     *              {@link #RECEIVER_EXPORTED} is specified, a receiver may additionally
+     * @param flags Additional options for the receiver. In a future release, either
+     * {@link #RECEIVER_EXPORTED} or {@link #RECEIVER_NOT_EXPORTED} must be specified if the
+     *             receiver isn't being registered for <a href="{@docRoot}guide/components
+     *              /broadcasts#system-broadcasts">system broadcasts</a> or an exception will be
+     *              thrown. If {@link #RECEIVER_EXPORTED} is specified, a receiver may additionally
      *              specify {@link #RECEIVER_VISIBLE_TO_INSTANT_APPS}. For a complete list of
      *              system broadcast actions, see the BROADCAST_ACTIONS.TXT file in the
      *              Android SDK. If both {@link #RECEIVER_EXPORTED} and
@@ -3207,13 +3203,11 @@
      *      no permission is required.
      * @param scheduler Handler identifying the thread that will receive
      *      the Intent. If {@code null}, the main thread of the process will be used.
-     * @param flags Additional options for the receiver. For apps targeting
-     * {@link android.os.Build.VERSION_CODES#TIRAMISU},
-     *              either {@link #RECEIVER_EXPORTED} or
-     * {@link #RECEIVER_NOT_EXPORTED} must be specified if the receiver isn't being registered
-     *              for <a href="{@docRoot}guide/components/broadcasts#system-broadcasts">system
-     *              broadcasts</a> or an exception will be thrown. If
-     *              {@link #RECEIVER_EXPORTED} is specified, a receiver may additionally
+     * @param flags Additional options for the receiver. In a future release, either
+     * {@link #RECEIVER_EXPORTED} or {@link #RECEIVER_NOT_EXPORTED} must be specified if the
+     *             receiver isn't being registered for <a href="{@docRoot}guide/components
+     *              /broadcasts#system-broadcasts">system broadcasts</a> or an exception will be
+     *              thrown. If {@link #RECEIVER_EXPORTED} is specified, a receiver may additionally
      *              specify {@link #RECEIVER_VISIBLE_TO_INSTANT_APPS}. For a complete list of
      *              system broadcast actions, see the BROADCAST_ACTIONS.TXT file in the
      *              Android SDK. If both {@link #RECEIVER_EXPORTED} and
@@ -3282,13 +3276,11 @@
      *      no permission is required.
      * @param scheduler Handler identifying the thread that will receive
      *      the Intent.  If null, the main thread of the process will be used.
-     * @param flags Additional options for the receiver. For apps targeting
-     * {@link android.os.Build.VERSION_CODES#TIRAMISU},
-     *              either {@link #RECEIVER_EXPORTED} or
-     * {@link #RECEIVER_NOT_EXPORTED} must be specified if the receiver isn't being registered
-     *              for <a href="{@docRoot}guide/components/broadcasts#system-broadcasts">system
-     *              broadcasts</a> or an exception will be thrown. If
-     *              {@link #RECEIVER_EXPORTED} is specified, a receiver may additionally
+     * @param flags Additional options for the receiver. In a future release, either
+     * {@link #RECEIVER_EXPORTED} or {@link #RECEIVER_NOT_EXPORTED} must be specified if the
+     *             receiver isn't being registered for <a href="{@docRoot}guide/components
+     *              /broadcasts#system-broadcasts">system broadcasts</a> or an exception will be
+     *              thrown. If {@link #RECEIVER_EXPORTED} is specified, a receiver may additionally
      *              specify {@link #RECEIVER_VISIBLE_TO_INSTANT_APPS}. For a complete list of
      *              system broadcast actions, see the BROADCAST_ACTIONS.TXT file in the
      *              Android SDK. If both {@link #RECEIVER_EXPORTED} and
diff --git a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
index 5680bcd..cb55e30 100644
--- a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
+++ b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
@@ -568,7 +568,8 @@
                 }
 
                 ParseResult<Integer> targetResult = FrameworkParsingPackageUtils.computeTargetSdkVersion(
-                        targetVer, targetCode, SDK_CODENAMES, input);
+                        targetVer, targetCode, SDK_CODENAMES, input,
+                        /* allowUnknownCodenames= */ false);
                 if (targetResult.isError()) {
                     return input.error(targetResult);
                 }
diff --git a/core/java/android/content/pm/parsing/FrameworkParsingPackageUtils.java b/core/java/android/content/pm/parsing/FrameworkParsingPackageUtils.java
index a65b681..6d74b81 100644
--- a/core/java/android/content/pm/parsing/FrameworkParsingPackageUtils.java
+++ b/core/java/android/content/pm/parsing/FrameworkParsingPackageUtils.java
@@ -36,6 +36,7 @@
 import android.util.apk.ApkSignatureVerifier;
 
 import com.android.internal.util.ArrayUtils;
+import com.android.modules.utils.build.UnboundedSdkLevel;
 
 import java.security.KeyFactory;
 import java.security.NoSuchAlgorithmException;
@@ -334,8 +335,9 @@
      * If {@code targetCode} is not specified, e.g. the value is {@code null}, then the {@code
      * targetVers} will be returned unmodified.
      * <p>
-     * Otherwise, the behavior varies based on whether the current platform is a pre-release
-     * version, e.g. the {@code platformSdkCodenames} array has length > 0:
+     * When {@code allowUnknownCodenames} is false, the behavior varies based on whether the
+     * current platform is a pre-release version, e.g. the {@code platformSdkCodenames} array has
+     * length > 0:
      * <ul>
      * <li>If this is a pre-release platform and the value specified by
      * {@code targetCode} is contained within the array of allowed pre-release
@@ -343,22 +345,32 @@
      * <li>If this is a released platform, this method will return -1 to
      * indicate that the package is not compatible with this platform.
      * </ul>
+     * <p>
+     * When {@code allowUnknownCodenames} is true, any codename that is not known (presumed to be
+     * a codename announced after the build of the current device) is allowed and this method will
+     * return {@link Build.VERSION_CODES#CUR_DEVELOPMENT}.
      *
-     * @param targetVers           targetSdkVersion number, if specified in the application
-     *                             manifest, or 0 otherwise
-     * @param targetCode           targetSdkVersion code, if specified in the application manifest,
-     *                             or {@code null} otherwise
-     * @param platformSdkCodenames array of allowed pre-release SDK codenames for this platform
+     * @param targetVers            targetSdkVersion number, if specified in the application
+     *                              manifest, or 0 otherwise
+     * @param targetCode            targetSdkVersion code, if specified in the application manifest,
+     *                              or {@code null} otherwise
+     * @param platformSdkCodenames  array of allowed pre-release SDK codenames for this platform
+     * @param allowUnknownCodenames allow unknown codenames, if true this method will accept unknown
+     *                              (presumed to be future) codenames
      * @return the targetSdkVersion to use at runtime if successful
      */
     public static ParseResult<Integer> computeTargetSdkVersion(@IntRange(from = 0) int targetVers,
             @Nullable String targetCode, @NonNull String[] platformSdkCodenames,
-            @NonNull ParseInput input) {
+            @NonNull ParseInput input, boolean allowUnknownCodenames) {
         // If it's a release SDK, return the version number unmodified.
         if (targetCode == null) {
             return input.success(targetVers);
         }
 
+        if (allowUnknownCodenames && UnboundedSdkLevel.isAtMost(targetCode)) {
+            return input.success(Build.VERSION_CODES.CUR_DEVELOPMENT);
+        }
+
         // If it's a pre-release SDK and the codename matches this platform, it
         // definitely targets this SDK.
         if (matchTargetCode(platformSdkCodenames, targetCode)) {
diff --git a/core/java/android/content/res/Configuration.java b/core/java/android/content/res/Configuration.java
index 01bf49e..dd3ddaf 100644
--- a/core/java/android/content/res/Configuration.java
+++ b/core/java/android/content/res/Configuration.java
@@ -751,11 +751,25 @@
     public static final int SCREEN_WIDTH_DP_UNDEFINED = 0;
 
     /**
-     * The current width of the available screen space, in dp units,
-     * corresponding to
-     * <a href="{@docRoot}guide/topics/resources/providing-resources.html#ScreenWidthQualifier">screen
-     * width</a> resource qualifier.  Set to
+     * The current width of the available screen space in dp units, excluding
+     * the area occupied by screen decorations at the edges of the display.
+     * Corresponds to the
+     * <a href="{@docRoot}guide/topics/resources/providing-resources.html#AvailableWidthHeightQualifier">
+     * available width</a> resource qualifier. Defaults to
      * {@link #SCREEN_WIDTH_DP_UNDEFINED} if no width is specified.
+     *
+     * <p>In multi-window mode, equals the width of the available display area
+     * of the app window, not the available display area of the device screen
+     * (for example, when apps are displayed side by side in split-screen mode
+     * in landscape orientation).
+     *
+     * <p>Differs from {@link android.view.WindowMetrics} by not including
+     * screen decorations in the width measurement and by expressing the
+     * measurement in dp rather than px. Use {@code screenWidthDp} to obtain the
+     * horizontal display area available to the app, excluding the area occupied
+     * by screen decorations. Use {@link android.view.WindowMetrics#getBounds()}
+     * to obtain the width of the display area available to the app, including
+     * the area occupied by screen decorations.
      */
     public int screenWidthDp;
 
@@ -766,11 +780,26 @@
     public static final int SCREEN_HEIGHT_DP_UNDEFINED = 0;
 
     /**
-     * The current height of the available screen space, in dp units,
-     * corresponding to
-     * <a href="{@docRoot}guide/topics/resources/providing-resources.html#ScreenHeightQualifier">screen
-     * height</a> resource qualifier.  Set to
+     * The current height of the available screen space in dp units, excluding
+     * the area occupied by screen decorations at the edges of the display (such
+     * as the status bar, navigation bar, and cutouts). Corresponds to the
+     * <a href="{@docRoot}guide/topics/resources/providing-resources.html#AvailableWidthHeightQualifier">
+     * available height</a> resource qualifier. Defaults to
      * {@link #SCREEN_HEIGHT_DP_UNDEFINED} if no height is specified.
+     *
+     * <p>In multi-window mode, equals the height of the available display area
+     * of the app window, not the available display area of the device screen
+     * (for example, when apps are displayed one above another in split-screen
+     * mode in portrait orientation).
+     *
+     * <p>Differs from {@link android.view.WindowMetrics} by not including
+     * screen decorations in the height measurement and by expressing the
+     * measurement in dp rather than px. Use {@code screenHeightDp} to obtain
+     * the vertical display area available to the app, excluding the area
+     * occupied by screen decorations. Use
+     * {@link android.view.WindowMetrics#getBounds()} to obtain the height of
+     * the display area available to the app, including the area occupied by
+     * screen decorations.
      */
     public int screenHeightDp;
 
diff --git a/core/java/android/hardware/fingerprint/IUdfpsHbmListener.aidl b/core/java/android/hardware/fingerprint/IUdfpsHbmListener.aidl
index f4d22da..9c2aa66 100644
--- a/core/java/android/hardware/fingerprint/IUdfpsHbmListener.aidl
+++ b/core/java/android/hardware/fingerprint/IUdfpsHbmListener.aidl
@@ -24,31 +24,20 @@
  * @hide
  */
 oneway interface IUdfpsHbmListener {
-
-    /** HBM that applies to the whole screen. */
-    const int GLOBAL_HBM = 0;
-
-    /** HBM that only applies to a portion of the screen. */
-    const int LOCAL_HBM = 1;
-
     /**
      * UdfpsController will call this method when the HBM is enabled.
      *
-     * @param hbmType The type of HBM that was enabled. See
-     *        {@link com.android.systemui.biometrics.UdfpsHbmTypes}.
      * @param displayId The displayId for which the HBM is enabled. See
      *        {@link android.view.Display#getDisplayId()}.
      */
-    void onHbmEnabled(int hbmType, int displayId);
+    void onHbmEnabled(int displayId);
 
     /**
      * UdfpsController will call this method when the HBM is disabled.
      *
-     * @param hbmType The type of HBM that was disabled. See
-     *        {@link com.android.systemui.biometrics.UdfpsHbmTypes}.
      * @param displayId The displayId for which the HBM is disabled. See
      *        {@link android.view.Display#getDisplayId()}.
      */
-    void onHbmDisabled(int hbmType, int displayId);
+    void onHbmDisabled(int displayId);
 }
 
diff --git a/core/java/android/hardware/input/IInputManager.aidl b/core/java/android/hardware/input/IInputManager.aidl
index e1ffd4a..57e84bd 100644
--- a/core/java/android/hardware/input/IInputManager.aidl
+++ b/core/java/android/hardware/input/IInputManager.aidl
@@ -57,10 +57,11 @@
     // Temporarily changes the pointer speed.
     void tryPointerSpeed(int speed);
 
-    // Injects an input event into the system.  To inject into windows owned by other
-    // applications, the caller must have the INJECT_EVENTS permission.
+    // Injects an input event into the system. The caller must have the INJECT_EVENTS permission.
+    // The caller can target windows owned by a certain UID by providing a valid UID, or by
+    // providing {@link android.os.Process#INVALID_UID} to target all windows.
     @UnsupportedAppUsage
-    boolean injectInputEvent(in InputEvent ev, int mode);
+    boolean injectInputEvent(in InputEvent ev, int mode, int targetUid);
 
     VerifiedInputEvent verifyInputEvent(in InputEvent ev);
 
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index c38a847..0bcabdd 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -45,6 +45,7 @@
 import android.os.InputEventInjectionSync;
 import android.os.Looper;
 import android.os.Message;
+import android.os.Process;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.ServiceManager.ServiceNotFoundException;
@@ -1107,14 +1108,58 @@
         }
     }
 
+    /**
+     * Injects an input event into the event system, targeting windows owned by the provided uid.
+     *
+     * If a valid targetUid is provided, the system will only consider injecting the input event
+     * into windows owned by the provided uid. If the input event is targeted at a window that is
+     * not owned by the provided uid, input injection will fail and a RemoteException will be
+     * thrown.
+     *
+     * The synchronization mode determines whether the method blocks while waiting for
+     * input injection to proceed.
+     * <p>
+     * Requires the {@link android.Manifest.permission.INJECT_EVENTS} permission.
+     * </p><p>
+     * Make sure you correctly set the event time and input source of the event
+     * before calling this method.
+     * </p>
+     *
+     * @param event The event to inject.
+     * @param mode The synchronization mode.  One of:
+     * {@link android.os.InputEventInjectionSync.NONE},
+     * {@link android.os.InputEventInjectionSync.WAIT_FOR_RESULT}, or
+     * {@link android.os.InputEventInjectionSync.WAIT_FOR_FINISHED}.
+     * @param targetUid The uid to target, or {@link android.os.Process#INVALID_UID} to target all
+     *                 windows.
+     * @return True if input event injection succeeded.
+     *
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.INJECT_EVENTS)
+    public boolean injectInputEvent(InputEvent event, int mode, int targetUid) {
+        if (event == null) {
+            throw new IllegalArgumentException("event must not be null");
+        }
+        if (mode != InputEventInjectionSync.NONE
+                && mode != InputEventInjectionSync.WAIT_FOR_FINISHED
+                && mode != InputEventInjectionSync.WAIT_FOR_RESULT) {
+            throw new IllegalArgumentException("mode is invalid");
+        }
+
+        try {
+            return mIm.injectInputEvent(event, mode, targetUid);
+        } catch (RemoteException ex) {
+            throw ex.rethrowFromSystemServer();
+        }
+    }
 
     /**
      * Injects an input event into the event system on behalf of an application.
      * The synchronization mode determines whether the method blocks while waiting for
      * input injection to proceed.
      * <p>
-     * Requires {@link android.Manifest.permission.INJECT_EVENTS} to inject into
-     * windows that are owned by other applications.
+     * Requires the {@link android.Manifest.permission.INJECT_EVENTS} permission.
      * </p><p>
      * Make sure you correctly set the event time and input source of the event
      * before calling this method.
@@ -1129,22 +1174,10 @@
      *
      * @hide
      */
+    @RequiresPermission(Manifest.permission.INJECT_EVENTS)
     @UnsupportedAppUsage
     public boolean injectInputEvent(InputEvent event, int mode) {
-        if (event == null) {
-            throw new IllegalArgumentException("event must not be null");
-        }
-        if (mode != InputEventInjectionSync.NONE
-                && mode != InputEventInjectionSync.WAIT_FOR_FINISHED
-                && mode != InputEventInjectionSync.WAIT_FOR_RESULT) {
-            throw new IllegalArgumentException("mode is invalid");
-        }
-
-        try {
-            return mIm.injectInputEvent(event, mode);
-        } catch (RemoteException ex) {
-            throw ex.rethrowFromSystemServer();
-        }
+        return injectInputEvent(event, mode, Process.INVALID_UID);
     }
 
     /**
@@ -1421,8 +1454,11 @@
             }
 
             mInputDevices = new SparseArray<InputDevice>();
-            for (int i = 0; i < ids.length; i++) {
-                mInputDevices.put(ids[i], null);
+            // TODO(b/223905476): remove when the rootcause is fixed.
+            if (ids != null) {
+                for (int i = 0; i < ids.length; i++) {
+                    mInputDevices.put(ids[i], null);
+                }
             }
         }
     }
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index c4cb319..a64e63e 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -300,6 +300,10 @@
      * When it is set by any of these owners, it prevents all users from using
      * Wi-Fi tethering. Other forms of tethering are not affected.
      *
+     * This user restriction disables only Wi-Fi tethering.
+     * Use {@link #DISALLOW_CONFIG_TETHERING} to limit all forms of tethering.
+     * When {@link #DISALLOW_CONFIG_TETHERING} is set, this user restriction becomes obsolete.
+     *
      * <p>The default value is <code>false</code>.
      *
      * <p>Key for user restrictions.
@@ -735,7 +739,7 @@
     public static final String DISALLOW_CONFIG_DATE_TIME = "no_config_date_time";
 
     /**
-     * Specifies if a user is disallowed from configuring Tethering and portable hotspots
+     * Specifies if a user is disallowed from using and configuring Tethering and portable hotspots
      * via Settings.
      *
      * <p>This restriction can only be set by a device owner, a profile owner on the primary
diff --git a/core/java/android/provider/DeviceConfig.java b/core/java/android/provider/DeviceConfig.java
index 4731504..d25e456 100644
--- a/core/java/android/provider/DeviceConfig.java
+++ b/core/java/android/provider/DeviceConfig.java
@@ -915,7 +915,8 @@
      * @param name        The name of the property to create or update.
      * @param value       The value to store for the property.
      * @param makeDefault Whether to make the new value the default one.
-     * @return True if the value was set, false if the storage implementation throws errors.
+     * @return {@code true} if the value was set, {@code false} if the storage implementation throws
+     * errors.
      * @hide
      * @see #resetToDefaults(int, String).
      */
@@ -939,7 +940,7 @@
      *
      * @param properties the complete set of properties to set for a specific namespace.
      * @throws BadConfigException if the provided properties are banned by RescueParty.
-     * @return True if the values were set, false otherwise.
+     * @return {@code true} if the values were set, {@code false} otherwise.
      * @hide
      */
     @SystemApi
@@ -955,8 +956,8 @@
      *
      * @param namespace   The namespace containing the property to delete.
      * @param name        The name of the property to delete.
-     * @return True if the property was deleted or it did not exist in the first place.
-     * False if the storage implementation throws errors.
+     * @return {@code true} if the property was deleted or it did not exist in the first place.
+     * Return {@code false} if the storage implementation throws errors.
      * @hide
      */
     @SystemApi
diff --git a/core/java/android/service/dreams/DreamService.java b/core/java/android/service/dreams/DreamService.java
index 001707d..d4f8a3b 100644
--- a/core/java/android/service/dreams/DreamService.java
+++ b/core/java/android/service/dreams/DreamService.java
@@ -1038,14 +1038,14 @@
         }
         mFinished = true;
 
+        mOverlayConnection.unbind(this);
+
         if (mDreamToken == null) {
             Slog.w(mTag, "Finish was called before the dream was attached.");
             stopSelf();
             return;
         }
 
-        mOverlayConnection.unbind(this);
-
         try {
             // finishSelf will unbind the dream controller from the dream service. This will
             // trigger DreamService.this.onDestroy and DreamService.this will die.
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 0fc3657..d7940e2 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -12049,13 +12049,16 @@
     }
 
     /**
-     * Compute the view's coordinate within the surface.
+     * Gets the coordinates of this view in the coordinate space of the
+     * {@link Surface} that contains the view.
      *
-     * <p>Computes the coordinates of this view in its surface. The argument
-     * must be an array of two integers. After the method returns, the array
-     * contains the x and y location in that order.</p>
+     * <p>After the method returns, the argument array contains the x- and
+     * y-coordinates of the view relative to the view's left and top edges,
+     * respectively.
      *
-     * @param location an array of two integers in which to hold the coordinates
+     * @param location A two-element integer array in which the view coordinates
+     *      are stored. The x-coordinate is at index 0; the y-coordinate, at
+     *      index 1.
      */
     public void getLocationInSurface(@NonNull @Size(2) int[] location) {
         getLocationInWindow(location);
@@ -25571,11 +25574,27 @@
     }
 
     /**
-     * <p>Computes the coordinates of this view on the screen. The argument
-     * must be an array of two integers. After the method returns, the array
-     * contains the x and y location in that order.</p>
+     * Gets the global coordinates of this view. The coordinates are in the
+     * coordinate space of the device screen, irrespective of system decorations
+     * and whether the system is in multi-window mode.
      *
-     * @param outLocation an array of two integers in which to hold the coordinates
+     * <p>In multi-window mode, the global coordinate space encompasses the
+     * entire device screen, ignoring the bounds of the app window. For
+     * example, if the view is in the bottom portion of a horizontal split
+     * screen, the top edge of the screen&mdash;not the top edge of the
+     * window&mdash;is the origin from which the y-coordinate is calculated.
+     *
+     * <p><b>Note:</b> In multiple-screen scenarios, the global coordinate space
+     * is restricted to the screen on which the view is displayed. The
+     * coordinate space does not span multiple screens.
+     *
+     * <p>After the method returns, the argument array contains the x- and
+     * y-coordinates of the view relative to the view's left and top edges,
+     * respectively.
+     *
+     * @param outLocation A two-element integer array in which the view
+     *      coordinates are stored. The x-coordinate is at index 0; the
+     *      y-coordinate, at index 1.
      */
     public void getLocationOnScreen(@Size(2) int[] outLocation) {
         getLocationInWindow(outLocation);
@@ -25588,11 +25607,20 @@
     }
 
     /**
-     * <p>Computes the coordinates of this view in its window. The argument
-     * must be an array of two integers. After the method returns, the array
-     * contains the x and y location in that order.</p>
+     * Gets the coordinates of this view in the coordinate space of the window
+     * that contains the view, irrespective of system decorations.
      *
-     * @param outLocation an array of two integers in which to hold the coordinates
+     * <p>In multi-window mode, the origin of the coordinate space is the
+     * top left corner of the window that contains the view. In full screen
+     * mode, the origin is the top left corner of the device screen.
+     *
+     * <p>After the method returns, the argument array contains the x- and
+     * y-coordinates of the view relative to the view's left and top edges,
+     * respectively.
+     *
+     * @param outLocation A two-element integer array in which the view
+     *      coordinates are stored. The x-coordinate is at index 0; the
+     *      y-coordinate, at index 1.
      */
     public void getLocationInWindow(@Size(2) int[] outLocation) {
         if (outLocation == null || outLocation.length < 2) {
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index 88089b5..d4a8a16 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -167,16 +167,6 @@
     public static final String EXTRA_PRIVATE_RETAIN_IN_ON_STOP
             = "com.android.internal.app.ChooserActivity.EXTRA_PRIVATE_RETAIN_IN_ON_STOP";
 
-    /**
-     * Boolean extra added to "unbundled Sharesheet" delegation intents to signal whether the app
-     * prediction service is available. Our query of the service <em>availability</em> depends on
-     * privileges that are only available in the system, even though the service itself would then
-     * be available to the unbundled component. For now, we just include the query result as part of
-     * the handover intent.
-     * TODO: investigate whether the privileged query is necessary to determine the availability.
-     */
-    public static final String EXTRA_IS_APP_PREDICTION_SERVICE_AVAILABLE =
-            "com.android.internal.app.ChooserActivity.EXTRA_IS_APP_PREDICTION_SERVICE_AVAILABLE";
 
     /**
      * Transition name for the first image preview.
@@ -985,6 +975,12 @@
     }
 
     @Override
+    protected void onResume() {
+        super.onResume();
+        Log.d(TAG, "onResume: " + getComponentName().flattenToShortString());
+    }
+
+    @Override
     public void onConfigurationChanged(Configuration newConfig) {
         super.onConfigurationChanged(newConfig);
         ViewPager viewPager = findViewById(R.id.profile_pager);
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index ea8589b..bfd8ff9 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -157,8 +157,6 @@
     /** See {@link #setRetainInOnStop}. */
     private boolean mRetainInOnStop;
 
-    protected static final int REQUEST_CODE_RETURN_FROM_DELEGATE_CHOOSER = 20;
-
     private static final String EXTRA_SHOW_FRAGMENT_ARGS = ":settings:show_fragment_args";
     private static final String EXTRA_FRAGMENT_ARG_KEY = ":settings:fragment_args_key";
     private static final String OPEN_LINKS_COMPONENT_KEY = "app_link_state";
@@ -1374,18 +1372,6 @@
                 .write();
     }
 
-    @Override
-    public void onActivityResult(int requestCode, int resultCode, Intent data) {
-        switch (requestCode) {
-            case REQUEST_CODE_RETURN_FROM_DELEGATE_CHOOSER:
-                // Repeat the delegate's result as our own.
-                setResult(resultCode, data);
-                finish();
-                break;
-            default:
-                super.onActivityResult(requestCode, resultCode, data);
-        }
-    }
 
     public void onActivityStarted(TargetInfo cti) {
         // Do nothing
diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl
index 089179d..634063a 100644
--- a/core/java/com/android/internal/statusbar/IStatusBar.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl
@@ -169,7 +169,7 @@
     /**
     * Used to hide the authentication dialog, e.g. when the application cancels authentication.
     */
-    void hideAuthenticationDialog();
+    void hideAuthenticationDialog(long requestId);
     /* Used to notify the biometric service of events that occur outside of an operation. */
     void setBiometicContextListener(in IBiometricContextListener listener);
 
diff --git a/core/java/com/android/internal/statusbar/IStatusBarService.aidl b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
index 2ee5e79..46b4630 100644
--- a/core/java/com/android/internal/statusbar/IStatusBarService.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
@@ -131,7 +131,7 @@
     // Used to show an error - the dialog will dismiss after a certain amount of time
     void onBiometricError(int modality, int error, int vendorCode);
     // Used to hide the authentication dialog, e.g. when the application cancels authentication
-    void hideAuthenticationDialog();
+    void hideAuthenticationDialog(long requestId);
     // Used to notify the biometric service of events that occur outside of an operation.
     void setBiometicContextListener(in IBiometricContextListener listener);
 
diff --git a/core/java/com/android/server/SystemConfig.java b/core/java/com/android/server/SystemConfig.java
index db41d33..1feb5d4 100644
--- a/core/java/com/android/server/SystemConfig.java
+++ b/core/java/com/android/server/SystemConfig.java
@@ -88,8 +88,8 @@
     private static final int ALLOW_HIDDENAPI_WHITELISTING = 0x040;
     private static final int ALLOW_ASSOCIATIONS = 0x080;
     // ALLOW_OVERRIDE_APP_RESTRICTIONS allows to use "allow-in-power-save-except-idle",
-    // "allow-in-power-save", "allow-in-data-usage-save", "allow-unthrottled-location",
-    // and "allow-ignore-location-settings".
+    // "allow-in-power-save", "allow-in-data-usage-save","allow-unthrottled-location",
+    // "allow-ignore-location-settings" and "allow-adas-location-settings".
     private static final int ALLOW_OVERRIDE_APP_RESTRICTIONS = 0x100;
     private static final int ALLOW_IMPLICIT_BROADCASTS = 0x200;
     private static final int ALLOW_VENDOR_APEX = 0x400;
@@ -234,6 +234,10 @@
     // without throttling, as read from the configuration files.
     final ArraySet<String> mAllowUnthrottledLocation = new ArraySet<>();
 
+    // These are the packages that are allow-listed to be able to retrieve location when
+    // the location state is driver assistance only.
+    final ArrayMap<String, ArraySet<String>> mAllowAdasSettings = new ArrayMap<>();
+
     // These are the packages that are white-listed to be able to retrieve location even when user
     // location settings are off, for emergency purposes, as read from the configuration files.
     final ArrayMap<String, ArraySet<String>> mAllowIgnoreLocationSettings = new ArrayMap<>();
@@ -394,6 +398,10 @@
         return mAllowUnthrottledLocation;
     }
 
+    public ArrayMap<String, ArraySet<String>> getAllowAdasLocationSettings() {
+        return mAllowAdasSettings;
+    }
+
     public ArrayMap<String, ArraySet<String>> getAllowIgnoreLocationSettings() {
         return mAllowIgnoreLocationSettings;
     }
@@ -1007,6 +1015,34 @@
                         }
                         XmlUtils.skipCurrentTag(parser);
                     } break;
+                    case "allow-adas-location-settings" : {
+                        if (allowOverrideAppRestrictions) {
+                            String pkgname = parser.getAttributeValue(null, "package");
+                            String attributionTag = parser.getAttributeValue(null,
+                                    "attributionTag");
+                            if (pkgname == null) {
+                                Slog.w(TAG, "<" + name + "> without package in "
+                                        + permFile + " at " + parser.getPositionDescription());
+                            } else {
+                                ArraySet<String> tags = mAllowAdasSettings.get(pkgname);
+                                if (tags == null || !tags.isEmpty()) {
+                                    if (tags == null) {
+                                        tags = new ArraySet<>(1);
+                                        mAllowAdasSettings.put(pkgname, tags);
+                                    }
+                                    if (!"*".equals(attributionTag)) {
+                                        if ("null".equals(attributionTag)) {
+                                            attributionTag = null;
+                                        }
+                                        tags.add(attributionTag);
+                                    }
+                                }
+                            }
+                        } else {
+                            logNotAllowedInPartition(name, permFile, parser);
+                        }
+                        XmlUtils.skipCurrentTag(parser);
+                    } break;
                     case "allow-ignore-location-settings": {
                         if (allowOverrideAppRestrictions) {
                             String pkgname = parser.getAttributeValue(null, "package");
diff --git a/core/res/OWNERS b/core/res/OWNERS
index ca8b3f8..95d2712 100644
--- a/core/res/OWNERS
+++ b/core/res/OWNERS
@@ -42,3 +42,6 @@
 # PowerProfile
 per-file res/xml/power_profile.xml = file:/BATTERY_STATS_OWNERS
 per-file res/xml/power_profile_test.xml = file:/BATTERY_STATS_OWNERS
+
+# Telephony
+per-file res/values/config_telephony.xml = file:/platform/frameworks/opt/telephony:/OWNERS
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index e6d4bab..60d875c 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -690,6 +690,11 @@
            will apply, regardless of device state. -->
     <string-array name="config_perDeviceStateRotationLockDefaults" />
 
+    <!-- Dock behavior -->
+
+    <!-- Control whether to start dream immediately upon docking even if the lockscreen is unlocked.
+         This defaults to true to be consistent with historical behavior. -->
+    <bool name="config_startDreamImmediatelyOnDock">true</bool>
 
     <!-- Desk dock behavior -->
 
@@ -2698,41 +2703,10 @@
     <string-array name="config_mobile_tcp_buffers">
     </string-array>
 
-    <!-- Configure tcp buffer sizes per network type in the form:
-         network-type:rmem_min,rmem_def,rmem_max,wmem_min,wmem_def,wmem_max
-
-         The network-type must be a valid DataConfigNetworkType value. If no value is found for the
-         network-type in use, config_tcp_buffers will be used instead.
-    -->
-    <string-array name="config_network_type_tcp_buffers">
-    </string-array>
-
-    <!-- Configure tcp buffer sizes in the form:
-         rmem_min,rmem_def,rmem_max,wmem_min,wmem_def,wmem_max
-         If this is configured as an empty string, the system default will be applied.
-
-         For now this config is used by mobile data only. In the future it should be
-         used by Wi-Fi as well.
-    -->
-    <string name="config_tcp_buffers" translatable="false"></string>
-
     <!-- Configure ethernet tcp buffersizes in the form:
          rmem_min,rmem_def,rmem_max,wmem_min,wmem_def,wmem_max -->
     <string name="config_ethernet_tcp_buffers" translatable="false">524288,1048576,3145728,524288,1048576,2097152</string>
 
-    <!-- What source to use to estimate link upstream and downstream bandwidth capacities.
-         Default is bandwidth_estimator.
-         Values are bandwidth_estimator, carrier_config and modem. -->
-    <string name="config_bandwidthEstimateSource">bandwidth_estimator</string>
-
-    <!-- Whether force to enable telephony new data stack or not -->
-    <bool name="config_force_enable_telephony_new_data_stack">true</bool>
-
-    <!-- Whether to adopt the predefined handover policies for IWLAN.
-         {@see CarrierConfigManager#KEY_IWLAN_HANDOVER_POLICY_STRING_ARRAY}
-    -->
-    <bool name="config_enable_iwlan_handover_policy">true</bool>
-
     <!-- Whether WiFi display is supported by this device.
          There are many prerequisites for this feature to work correctly.
          Here are a few of them:
@@ -3312,27 +3286,6 @@
     <!-- String array containing numbers that shouldn't be logged. Country-specific. -->
     <string-array name="unloggable_phone_numbers" />
 
-    <!-- Cellular data service package name to bind to by default. If none is specified in an overlay, an
-         empty string is passed in -->
-    <string name="config_wwan_data_service_package" translatable="false">com.android.phone</string>
-
-    <!-- IWLAN data service package name to bind to by default. If none is specified in an overlay, an
-         empty string is passed in -->
-    <string name="config_wlan_data_service_package" translatable="false"></string>
-
-    <!-- Boolean indicating whether the Iwlan data service supports persistence of iwlan ipsec
-         tunnels across service restart. If iwlan tunnels are not persisted across restart,
-         Framework will clean up dangling data connections when service restarts -->
-    <bool name="config_wlan_data_service_conn_persistence_on_restart">true</bool>
-
-    <!-- Cellular data service class name to bind to by default. If none is specified in an overlay, an
-         empty string is passed in -->
-    <string name="config_wwan_data_service_class" translatable="false"></string>
-
-    <!-- IWLAN data service class name to bind to by default. If none is specified in an overlay, an
-         empty string is passed in -->
-    <string name="config_wlan_data_service_class" translatable="false"></string>
-
     <bool name="config_networkSamplingWakesDevice">true</bool>
 
     <!--From SmsMessage-->
@@ -3432,11 +3385,6 @@
         <item>2</item>    <!-- USAGE_SETTING_DATA_CENTRIC -->
     </integer-array>
 
-    <!-- When a radio power off request is received, we will delay completing the request until
-         either IMS moves to the deregistered state or the timeout defined by this configuration
-         elapses. If 0, this feature is disabled and we do not delay radio power off requests.-->
-    <integer name="config_delay_for_ims_dereg_millis">0</integer>
-
     <!--Thresholds for LTE dbm in status bar-->
     <integer-array translatable="false" name="config_lteDbmThresholds">
         <item>-140</item>    <!-- SIGNAL_STRENGTH_NONE_OR_UNKNOWN -->
@@ -4400,24 +4348,6 @@
 
     <bool name="config_keepRestrictedProfilesInBackground">true</bool>
 
-    <!-- Cellular network service package name to bind to by default. -->
-    <string name="config_wwan_network_service_package" translatable="false">com.android.phone</string>
-
-    <!-- Cellular network service class name to bind to by default.-->
-    <string name="config_wwan_network_service_class" translatable="false"></string>
-
-    <!-- IWLAN network service package name to bind to by default. If none is specified in an overlay, an
-         empty string is passed in -->
-    <string name="config_wlan_network_service_package" translatable="false"></string>
-
-    <!-- IWLAN network service class name to bind to by default. If none is specified in an overlay, an
-         empty string is passed in -->
-    <string name="config_wlan_network_service_class" translatable="false"></string>
-    <!-- Telephony qualified networks service package name to bind to by default. -->
-    <string name="config_qualified_networks_service_package" translatable="false"></string>
-
-    <!-- Telephony qualified networks service class name to bind to by default. -->
-    <string name="config_qualified_networks_service_class" translatable="false"></string>
     <!-- Wear devices: Controls the radios affected by Activity Mode. -->
     <string-array name="config_wearActivityModeRadios">
         <item>"wifi"</item>
diff --git a/core/res/res/values/config_telephony.xml b/core/res/res/values/config_telephony.xml
new file mode 100644
index 0000000..cd3578c
--- /dev/null
+++ b/core/res/res/values/config_telephony.xml
@@ -0,0 +1,112 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2022 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<resources>
+    <!-- This file defines Android telephony related resources -->
+
+    <!-- Whether force to enable telephony new data stack or not -->
+    <bool name="config_force_enable_telephony_new_data_stack">true</bool>
+    <java-symbol type="bool" name="config_force_enable_telephony_new_data_stack" />
+
+    <!-- Configure tcp buffer sizes per network type in the form:
+         network-type:rmem_min,rmem_def,rmem_max,wmem_min,wmem_def,wmem_max
+
+         The network-type must be a valid DataConfigNetworkType value. If no value is found for the
+         network-type in use, config_tcp_buffers will be used instead.
+    -->
+    <string-array name="config_network_type_tcp_buffers">
+    </string-array>
+    <java-symbol type="array" name="config_network_type_tcp_buffers" />
+
+    <!-- Configure tcp buffer sizes in the form:
+         rmem_min,rmem_def,rmem_max,wmem_min,wmem_def,wmem_max
+         If this is configured as an empty string, the system default will be applied.
+    -->
+    <string name="config_tcp_buffers" translatable="false"></string>
+    <java-symbol type="string"  name="config_tcp_buffers" />
+
+    <!-- What source to use to estimate link upstream and downstream bandwidth capacities.
+         Default is bandwidth_estimator.
+         Values are bandwidth_estimator, carrier_config and modem. -->
+    <string name="config_bandwidthEstimateSource">bandwidth_estimator</string>
+    <java-symbol type="string" name="config_bandwidthEstimateSource" />
+
+    <!-- Whether to adopt the predefined handover policies for IWLAN.
+         {@see CarrierConfigManager#KEY_IWLAN_HANDOVER_POLICY_STRING_ARRAY}
+    -->
+    <bool name="config_enable_iwlan_handover_policy">true</bool>
+    <java-symbol type="bool" name="config_enable_iwlan_handover_policy" />
+
+    <!-- When a radio power off request is received, we will delay completing the request until
+         either IMS moves to the deregistered state or the timeout defined by this configuration
+         elapses. If 0, this feature is disabled and we do not delay radio power off requests.-->
+    <integer name="config_delay_for_ims_dereg_millis">0</integer>
+    <java-symbol type="integer" name="config_delay_for_ims_dereg_millis" />
+
+    <!-- Boolean indicating whether the Iwlan data service supports persistence of iwlan ipsec
+         tunnels across service restart. If iwlan tunnels are not persisted across restart,
+         Framework will clean up dangling data connections when service restarts -->
+    <bool name="config_wlan_data_service_conn_persistence_on_restart">true</bool>
+    <java-symbol type="bool" name="config_wlan_data_service_conn_persistence_on_restart" />
+
+    <!-- Cellular data service package name to bind to by default. If none is specified in an
+         overlay, an empty string is passed in -->
+    <string name="config_wwan_data_service_package" translatable="false">com.android.phone</string>
+    <java-symbol type="string" name="config_wwan_data_service_package" />
+
+    <!-- IWLAN data service package name to bind to by default. If none is specified in an overlay,
+         an empty string is passed in -->
+    <string name="config_wlan_data_service_package" translatable="false"></string>
+    <java-symbol type="string" name="config_wlan_data_service_package" />
+
+    <!-- Cellular data service class name to bind to by default. If none is specified in an overlay,
+         an empty string is passed in -->
+    <string name="config_wwan_data_service_class" translatable="false"></string>
+    <java-symbol type="string" name="config_wwan_data_service_class" />
+
+    <!-- IWLAN data service class name to bind to by default. If none is specified in an overlay, an
+         empty string is passed in -->
+    <string name="config_wlan_data_service_class" translatable="false"></string>
+    <java-symbol type="string" name="config_wlan_data_service_class" />
+
+    <!-- Cellular network service package name to bind to by default. -->
+    <string name="config_wwan_network_service_package" translatable="false">
+        com.android.phone
+    </string>
+    <java-symbol type="string" name="config_wwan_network_service_package" />
+
+    <!-- Cellular network service class name to bind to by default.-->
+    <string name="config_wwan_network_service_class" translatable="false"></string>
+    <java-symbol type="string" name="config_wwan_network_service_class" />
+
+    <!-- IWLAN network service package name to bind to by default. If none is specified in an
+         overlay, an empty string is passed in -->
+    <string name="config_wlan_network_service_package" translatable="false"></string>
+    <java-symbol type="string" name="config_wlan_network_service_package" />
+
+    <!-- IWLAN network service class name to bind to by default. If none is specified in an overlay,
+         an empty string is passed in -->
+    <string name="config_wlan_network_service_class" translatable="false"></string>
+    <java-symbol type="string" name="config_wlan_network_service_class" />
+
+    <!-- Telephony qualified networks service package name to bind to by default. -->
+    <string name="config_qualified_networks_service_package" translatable="false"></string>
+    <java-symbol type="string" name="config_qualified_networks_service_package" />
+
+    <!-- Telephony qualified networks service class name to bind to by default. -->
+    <string name="config_qualified_networks_service_class" translatable="false"></string>
+    <java-symbol type="string" name="config_qualified_networks_service_class" />
+</resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 910bfe9..7069e716 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -294,17 +294,6 @@
   <java-symbol type="bool" name="config_enableBurnInProtection" />
   <java-symbol type="bool" name="config_hotswapCapable" />
   <java-symbol type="bool" name="config_mms_content_disposition_support" />
-  <java-symbol type="string" name="config_wwan_network_service_package" />
-  <java-symbol type="string" name="config_wlan_network_service_package" />
-  <java-symbol type="string" name="config_wwan_network_service_class" />
-  <java-symbol type="string" name="config_wlan_network_service_class" />
-  <java-symbol type="bool" name="config_wlan_data_service_conn_persistence_on_restart" />
-  <java-symbol type="string" name="config_wwan_data_service_package" />
-  <java-symbol type="string" name="config_wlan_data_service_package" />
-  <java-symbol type="string" name="config_wwan_data_service_class" />
-  <java-symbol type="string" name="config_wlan_data_service_class" />
-  <java-symbol type="string" name="config_qualified_networks_service_package" />
-  <java-symbol type="string" name="config_qualified_networks_service_class" />
   <java-symbol type="bool" name="config_networkSamplingWakesDevice" />
   <java-symbol type="bool" name="config_showMenuShortcutsWhenKeyboardPresent" />
   <java-symbol type="bool" name="config_sip_wifi_only" />
@@ -472,10 +461,6 @@
   <java-symbol type="integer" name="config_safe_media_volume_usb_mB" />
   <java-symbol type="integer" name="config_mobile_mtu" />
   <java-symbol type="array"   name="config_mobile_tcp_buffers" />
-  <java-symbol type="array"   name="config_network_type_tcp_buffers" />
-  <java-symbol type="string"  name="config_tcp_buffers" />
-  <java-symbol type="bool" name="config_force_enable_telephony_new_data_stack" />
-  <java-symbol type="bool" name="config_enable_iwlan_handover_policy" />
   <java-symbol type="integer" name="config_volte_replacement_rat"/>
   <java-symbol type="integer" name="config_valid_wappush_index" />
   <java-symbol type="integer" name="config_overrideHasPermanentMenuKey" />
@@ -489,10 +474,8 @@
   <java-symbol type="integer" name="config_num_physical_slots" />
   <java-symbol type="integer" name="config_default_cellular_usage_setting" />
   <java-symbol type="array" name="config_supported_cellular_usage_settings" />
-  <java-symbol type="integer" name="config_delay_for_ims_dereg_millis" />
   <java-symbol type="array" name="config_integrityRuleProviderPackages" />
   <java-symbol type="bool" name="config_useAssistantVolume" />
-  <java-symbol type="string" name="config_bandwidthEstimateSource" />
   <java-symbol type="integer" name="config_smartSelectionInitializedTimeoutMillis" />
   <java-symbol type="integer" name="config_smartSelectionInitializingTimeoutMillis" />
   <java-symbol type="integer" name="config_preferKeepClearForFocusDelayMillis" />
@@ -1732,6 +1715,7 @@
   <java-symbol type="attr" name="dialogTitleIconsDecorLayout" />
   <java-symbol type="bool" name="config_allowAllRotations" />
   <java-symbol type="bool" name="config_annoy_dianne" />
+  <java-symbol type="bool" name="config_startDreamImmediatelyOnDock" />
   <java-symbol type="bool" name="config_carDockEnablesAccelerometer" />
   <java-symbol type="bool" name="config_customUserSwitchUi" />
   <java-symbol type="bool" name="config_deskDockEnablesAccelerometer" />
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 449fb02..58a2073 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -273,6 +273,7 @@
         <permission name="android.permission.LOCAL_MAC_ADDRESS"/>
         <permission name="android.permission.MANAGE_ACCESSIBILITY"/>
         <permission name="android.permission.MANAGE_DEVICE_ADMINS"/>
+        <permission name="android.permission.ACCESS_FPS_COUNTER"/>
         <permission name="android.permission.MANAGE_GAME_MODE"/>
         <permission name="android.permission.MANAGE_GAME_ACTIVITY" />
         <permission name="android.permission.MANAGE_LOW_POWER_STANDBY" />
diff --git a/framework-jarjar-rules.txt b/framework-jarjar-rules.txt
index be21f4e..03b268d 100644
--- a/framework-jarjar-rules.txt
+++ b/framework-jarjar-rules.txt
@@ -5,3 +5,6 @@
 # Framework-specific renames.
 rule android.net.wifi.WifiAnnotations* android.internal.wifi.WifiAnnotations@1
 rule com.android.server.vcn.util.** com.android.server.vcn.repackaged.util.@1
+
+# for modules-utils-build dependency
+rule com.android.modules.utils.build.** android.internal.modules.utils.build.@1
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/common/DeviceStateManagerFoldingFeatureProducer.java b/libs/WindowManager/Jetpack/src/androidx/window/common/DeviceStateManagerFoldingFeatureProducer.java
index 6987401..fdcb7be 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/common/DeviceStateManagerFoldingFeatureProducer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/common/DeviceStateManagerFoldingFeatureProducer.java
@@ -31,11 +31,13 @@
 import android.util.SparseIntArray;
 
 import androidx.window.util.BaseDataProducer;
+import androidx.window.util.DataProducer;
 
 import com.android.internal.R;
 
 import java.util.List;
 import java.util.Optional;
+import java.util.Set;
 
 /**
  * An implementation of {@link androidx.window.util.DataProducer} that returns the device's posture
@@ -48,7 +50,6 @@
             DeviceStateManagerFoldingFeatureProducer.class.getSimpleName();
     private static final boolean DEBUG = false;
 
-    private final Context mContext;
     private final SparseIntArray mDeviceStateToPostureMap = new SparseIntArray();
 
     private int mCurrentDeviceState = INVALID_DEVICE_STATE;
@@ -57,9 +58,12 @@
         mCurrentDeviceState = state;
         notifyDataChanged();
     };
+    @NonNull
+    private final DataProducer<String> mRawFoldSupplier;
 
-    public DeviceStateManagerFoldingFeatureProducer(@NonNull Context context) {
-        mContext = context;
+    public DeviceStateManagerFoldingFeatureProducer(@NonNull Context context,
+            @NonNull DataProducer<String> rawFoldSupplier) {
+        mRawFoldSupplier = rawFoldSupplier;
         String[] deviceStatePosturePairs = context.getResources()
                 .getStringArray(R.array.config_device_state_postures);
         for (String deviceStatePosturePair : deviceStatePosturePairs) {
@@ -97,12 +101,21 @@
     @Nullable
     public Optional<List<CommonFoldingFeature>> getData() {
         final int globalHingeState = globalHingeState();
-        String displayFeaturesString = mContext.getResources().getString(
-                R.string.config_display_features);
-        if (TextUtils.isEmpty(displayFeaturesString)) {
+        Optional<String> displayFeaturesString = mRawFoldSupplier.getData();
+        if (displayFeaturesString.isEmpty() || TextUtils.isEmpty(displayFeaturesString.get())) {
             return Optional.empty();
         }
-        return Optional.of(parseListFromString(displayFeaturesString, globalHingeState));
+        return Optional.of(parseListFromString(displayFeaturesString.get(), globalHingeState));
+    }
+
+    @Override
+    protected void onListenersChanged(Set<Runnable> callbacks) {
+        super.onListenersChanged(callbacks);
+        if (callbacks.isEmpty()) {
+            mRawFoldSupplier.removeDataChangedCallback(this::notifyDataChanged);
+        } else {
+            mRawFoldSupplier.addDataChangedCallback(this::notifyDataChanged);
+        }
     }
 
     private int globalHingeState() {
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/common/RawFoldingFeatureProducer.java b/libs/WindowManager/Jetpack/src/androidx/window/common/RawFoldingFeatureProducer.java
new file mode 100644
index 0000000..69ad1ba
--- /dev/null
+++ b/libs/WindowManager/Jetpack/src/androidx/window/common/RawFoldingFeatureProducer.java
@@ -0,0 +1,132 @@
+/*
+ * 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 androidx.window.common;
+
+import android.annotation.NonNull;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.Looper;
+import android.provider.Settings;
+import android.text.TextUtils;
+
+import androidx.window.util.BaseDataProducer;
+
+import com.android.internal.R;
+
+import java.util.Optional;
+import java.util.Set;
+
+/**
+ * Implementation of {@link androidx.window.util.DataProducer} that produces a
+ * {@link String} that can be parsed to a {@link CommonFoldingFeature}.
+ * {@link RawFoldingFeatureProducer} searches for the value in two places. The first check is in
+ * settings where the {@link String} property is saved with the key
+ * {@link RawFoldingFeatureProducer#DISPLAY_FEATURES}. If this value is null or empty then the
+ * value in {@link android.content.res.Resources} is used. If both are empty then
+ * {@link RawFoldingFeatureProducer#getData()} returns an empty object.
+ * {@link RawFoldingFeatureProducer} listens to changes in the setting so that it can override
+ * the system {@link CommonFoldingFeature} data.
+ */
+public final class RawFoldingFeatureProducer extends BaseDataProducer<String> {
+    private static final String DISPLAY_FEATURES = "display_features";
+
+    private final Uri mDisplayFeaturesUri =
+            Settings.Global.getUriFor(DISPLAY_FEATURES);
+
+    private final ContentResolver mResolver;
+    private final ContentObserver mObserver;
+    private final String mResourceFeature;
+    private boolean mRegisteredObservers;
+
+    public RawFoldingFeatureProducer(@NonNull Context context) {
+        mResolver = context.getContentResolver();
+        mObserver = new SettingsObserver();
+        mResourceFeature = context.getResources().getString(R.string.config_display_features);
+    }
+
+    @Override
+    @NonNull
+    public Optional<String> getData() {
+        String displayFeaturesString = getFeatureString();
+        if (displayFeaturesString == null) {
+            return Optional.empty();
+        }
+        return Optional.of(displayFeaturesString);
+    }
+
+    /**
+     * Returns the {@link String} representation for a {@link CommonFoldingFeature} from settings if
+     * present and falls back to the resource value if empty or {@code null}.
+     */
+    private String getFeatureString() {
+        String settingsFeature = Settings.Global.getString(mResolver, DISPLAY_FEATURES);
+        if (TextUtils.isEmpty(settingsFeature)) {
+            return mResourceFeature;
+        }
+        return settingsFeature;
+    }
+
+    @Override
+    protected void onListenersChanged(Set<Runnable> callbacks) {
+        if (callbacks.isEmpty()) {
+            unregisterObserversIfNeeded();
+        } else {
+            registerObserversIfNeeded();
+        }
+    }
+
+    /**
+     * Registers settings observers, if needed. When settings observers are registered for this
+     * producer callbacks for changes in data will be triggered.
+     */
+    private void registerObserversIfNeeded() {
+        if (mRegisteredObservers) {
+            return;
+        }
+        mRegisteredObservers = true;
+        mResolver.registerContentObserver(mDisplayFeaturesUri, false /* notifyForDescendants */,
+                mObserver /* ContentObserver */);
+    }
+
+    /**
+     * Unregisters settings observers, if needed. When settings observers are unregistered for this
+     * producer callbacks for changes in data will not be triggered.
+     */
+    private void unregisterObserversIfNeeded() {
+        if (!mRegisteredObservers) {
+            return;
+        }
+        mRegisteredObservers = false;
+        mResolver.unregisterContentObserver(mObserver);
+    }
+
+    private final class SettingsObserver extends ContentObserver {
+        SettingsObserver() {
+            super(new Handler(Looper.getMainLooper()));
+        }
+
+        @Override
+        public void onChange(boolean selfChange, Uri uri) {
+            if (mDisplayFeaturesUri.equals(uri)) {
+                notifyDataChanged();
+            }
+        }
+    }
+}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/common/SettingsDisplayFeatureProducer.java b/libs/WindowManager/Jetpack/src/androidx/window/common/SettingsDisplayFeatureProducer.java
deleted file mode 100644
index 0e696eb..0000000
--- a/libs/WindowManager/Jetpack/src/androidx/window/common/SettingsDisplayFeatureProducer.java
+++ /dev/null
@@ -1,124 +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 androidx.window.common;
-
-import static androidx.window.common.CommonFoldingFeature.COMMON_STATE_FLAT;
-import static androidx.window.common.CommonFoldingFeature.COMMON_STATE_HALF_OPENED;
-import static androidx.window.common.CommonFoldingFeature.COMMON_STATE_UNKNOWN;
-import static androidx.window.common.CommonFoldingFeature.parseListFromString;
-
-import android.annotation.NonNull;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.database.ContentObserver;
-import android.net.Uri;
-import android.os.Handler;
-import android.os.Looper;
-import android.provider.Settings;
-import android.text.TextUtils;
-
-import androidx.window.util.BaseDataProducer;
-
-import java.util.Collections;
-import java.util.List;
-import java.util.Optional;
-
-/**
- * Implementation of {@link androidx.window.util.DataProducer} that produces
- * {@link CommonFoldingFeature} parsed from a string stored in {@link Settings}.
- */
-public final class SettingsDisplayFeatureProducer
-        extends BaseDataProducer<List<CommonFoldingFeature>> {
-    private static final String DISPLAY_FEATURES = "display_features";
-    private static final String DEVICE_POSTURE = "device_posture";
-
-    private final Uri mDevicePostureUri =
-            Settings.Global.getUriFor(DEVICE_POSTURE);
-    private final Uri mDisplayFeaturesUri =
-            Settings.Global.getUriFor(DISPLAY_FEATURES);
-
-    private final ContentResolver mResolver;
-    private final ContentObserver mObserver;
-    private boolean mRegisteredObservers;
-
-    public SettingsDisplayFeatureProducer(@NonNull Context context) {
-        mResolver = context.getContentResolver();
-        mObserver = new SettingsObserver();
-    }
-
-    private int getPosture() {
-        int posture = Settings.Global.getInt(mResolver, DEVICE_POSTURE, COMMON_STATE_UNKNOWN);
-        if (posture == COMMON_STATE_HALF_OPENED || posture == COMMON_STATE_FLAT) {
-            return posture;
-        } else {
-            return COMMON_STATE_UNKNOWN;
-        }
-    }
-
-    @Override
-    @NonNull
-    public Optional<List<CommonFoldingFeature>> getData() {
-        String displayFeaturesString = Settings.Global.getString(mResolver, DISPLAY_FEATURES);
-        if (displayFeaturesString == null) {
-            return Optional.empty();
-        }
-
-        if (TextUtils.isEmpty(displayFeaturesString)) {
-            return Optional.of(Collections.emptyList());
-        }
-        return Optional.of(parseListFromString(displayFeaturesString, getPosture()));
-    }
-
-    /**
-     * Registers settings observers, if needed. When settings observers are registered for this
-     * producer callbacks for changes in data will be triggered.
-     */
-    public void registerObserversIfNeeded() {
-        if (mRegisteredObservers) {
-            return;
-        }
-        mRegisteredObservers = true;
-        mResolver.registerContentObserver(mDisplayFeaturesUri, false /* notifyForDescendants */,
-                mObserver /* ContentObserver */);
-        mResolver.registerContentObserver(mDevicePostureUri, false, mObserver);
-    }
-
-    /**
-     * Unregisters settings observers, if needed. When settings observers are unregistered for this
-     * producer callbacks for changes in data will not be triggered.
-     */
-    public void unregisterObserversIfNeeded() {
-        if (!mRegisteredObservers) {
-            return;
-        }
-        mRegisteredObservers = false;
-        mResolver.unregisterContentObserver(mObserver);
-    }
-
-    private final class SettingsObserver extends ContentObserver {
-        SettingsObserver() {
-            super(new Handler(Looper.getMainLooper()));
-        }
-
-        @Override
-        public void onChange(boolean selfChange, Uri uri) {
-            if (mDisplayFeaturesUri.equals(uri) || mDevicePostureUri.equals(uri)) {
-                notifyDataChanged();
-            }
-        }
-    }
-}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
index a4fbdbc..2f7d958 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
@@ -37,9 +37,8 @@
 import androidx.window.common.CommonFoldingFeature;
 import androidx.window.common.DeviceStateManagerFoldingFeatureProducer;
 import androidx.window.common.EmptyLifecycleCallbacksAdapter;
-import androidx.window.common.SettingsDisplayFeatureProducer;
+import androidx.window.common.RawFoldingFeatureProducer;
 import androidx.window.util.DataProducer;
-import androidx.window.util.PriorityDataProducer;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -62,17 +61,14 @@
     private final Map<Activity, Consumer<WindowLayoutInfo>> mWindowLayoutChangeListeners =
             new ArrayMap<>();
 
-    private final SettingsDisplayFeatureProducer mSettingsDisplayFeatureProducer;
     private final DataProducer<List<CommonFoldingFeature>> mFoldingFeatureProducer;
 
     public WindowLayoutComponentImpl(Context context) {
         ((Application) context.getApplicationContext())
                 .registerActivityLifecycleCallbacks(new NotifyOnConfigurationChanged());
-        mSettingsDisplayFeatureProducer = new SettingsDisplayFeatureProducer(context);
-        mFoldingFeatureProducer = new PriorityDataProducer<>(List.of(
-                mSettingsDisplayFeatureProducer,
-                new DeviceStateManagerFoldingFeatureProducer(context)
-        ));
+        RawFoldingFeatureProducer foldingFeatureProducer = new RawFoldingFeatureProducer(context);
+        mFoldingFeatureProducer = new DeviceStateManagerFoldingFeatureProducer(context,
+                foldingFeatureProducer);
         mFoldingFeatureProducer.addDataChangedCallback(this::onDisplayFeaturesChanged);
     }
 
@@ -85,7 +81,7 @@
     public void addWindowLayoutInfoListener(@NonNull Activity activity,
             @NonNull Consumer<WindowLayoutInfo> consumer) {
         mWindowLayoutChangeListeners.put(activity, consumer);
-        updateRegistrations();
+        onDisplayFeaturesChanged();
     }
 
     /**
@@ -96,7 +92,7 @@
     public void removeWindowLayoutInfoListener(
             @NonNull Consumer<WindowLayoutInfo> consumer) {
         mWindowLayoutChangeListeners.values().remove(consumer);
-        updateRegistrations();
+        onDisplayFeaturesChanged();
     }
 
     void updateWindowLayout(@NonNull Activity activity,
@@ -210,15 +206,6 @@
         return features;
     }
 
-    private void updateRegistrations() {
-        if (hasListeners()) {
-            mSettingsDisplayFeatureProducer.registerObserversIfNeeded();
-        } else {
-            mSettingsDisplayFeatureProducer.unregisterObserversIfNeeded();
-        }
-        onDisplayFeaturesChanged();
-    }
-
     private final class NotifyOnConfigurationChanged extends EmptyLifecycleCallbacksAdapter {
         @Override
         public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SampleSidecarImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SampleSidecarImpl.java
index c7b7093..970f0a2 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SampleSidecarImpl.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SampleSidecarImpl.java
@@ -34,9 +34,8 @@
 import androidx.window.common.CommonFoldingFeature;
 import androidx.window.common.DeviceStateManagerFoldingFeatureProducer;
 import androidx.window.common.EmptyLifecycleCallbacksAdapter;
-import androidx.window.common.SettingsDisplayFeatureProducer;
+import androidx.window.common.RawFoldingFeatureProducer;
 import androidx.window.util.DataProducer;
-import androidx.window.util.PriorityDataProducer;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -52,16 +51,13 @@
 
     private final DataProducer<List<CommonFoldingFeature>> mFoldingFeatureProducer;
 
-    private final SettingsDisplayFeatureProducer mSettingsFoldingFeatureProducer;
 
     SampleSidecarImpl(Context context) {
         ((Application) context.getApplicationContext())
                 .registerActivityLifecycleCallbacks(new NotifyOnConfigurationChanged());
-        mSettingsFoldingFeatureProducer = new SettingsDisplayFeatureProducer(context);
-        mFoldingFeatureProducer = new PriorityDataProducer<>(List.of(
-                mSettingsFoldingFeatureProducer,
-                new DeviceStateManagerFoldingFeatureProducer(context)
-        ));
+        DataProducer<String> settingsFeatureProducer = new RawFoldingFeatureProducer(context);
+        mFoldingFeatureProducer = new DeviceStateManagerFoldingFeatureProducer(context,
+                settingsFeatureProducer);
 
         mFoldingFeatureProducer.addDataChangedCallback(this::onDisplayFeaturesChanged);
     }
@@ -142,10 +138,7 @@
     @Override
     protected void onListenersChanged() {
         if (hasListeners()) {
-            mSettingsFoldingFeatureProducer.registerObserversIfNeeded();
             onDisplayFeaturesChanged();
-        } else {
-            mSettingsFoldingFeatureProducer.unregisterObserversIfNeeded();
         }
     }
 
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/util/BaseDataProducer.java b/libs/WindowManager/Jetpack/src/androidx/window/util/BaseDataProducer.java
index 0a46703451..930db3b 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/util/BaseDataProducer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/util/BaseDataProducer.java
@@ -33,13 +33,17 @@
     @Override
     public final void addDataChangedCallback(@NonNull Runnable callback) {
         mCallbacks.add(callback);
+        onListenersChanged(mCallbacks);
     }
 
     @Override
     public final void removeDataChangedCallback(@NonNull Runnable callback) {
         mCallbacks.remove(callback);
+        onListenersChanged(mCallbacks);
     }
 
+    protected void onListenersChanged(Set<Runnable> callbacks) {}
+
     /**
      * Called to notify all registered callbacks that the data provided by {@link #getData()} has
      * changed.
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/util/PriorityDataProducer.java b/libs/WindowManager/Jetpack/src/androidx/window/util/PriorityDataProducer.java
deleted file mode 100644
index 990ae20..0000000
--- a/libs/WindowManager/Jetpack/src/androidx/window/util/PriorityDataProducer.java
+++ /dev/null
@@ -1,56 +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 androidx.window.util;
-
-import android.annotation.Nullable;
-
-import java.util.List;
-import java.util.Optional;
-
-/**
- * Implementation of {@link DataProducer} that delegates calls to {@link #getData()} to the list of
- * provided child producers.
- * <p>
- * The value returned is based on the precedence of the supplied children where the producer with
- * index 0 has a higher precedence than producers that come later in the list. When a producer with
- * a higher precedence has a non-empty value returned from {@link #getData()}, its value will be
- * returned from an instance of this class, ignoring all other producers with lower precedence.
- *
- * @param <T> The type of data this producer returns through {@link #getData()}.
- */
-public final class PriorityDataProducer<T> extends BaseDataProducer<T> {
-    private final List<DataProducer<T>> mChildProducers;
-
-    public PriorityDataProducer(List<DataProducer<T>> childProducers) {
-        mChildProducers = childProducers;
-        for (DataProducer<T> childProducer : mChildProducers) {
-            childProducer.addDataChangedCallback(this::notifyDataChanged);
-        }
-    }
-
-    @Nullable
-    @Override
-    public Optional<T> getData() {
-        for (DataProducer<T> childProducer : mChildProducers) {
-            final Optional<T> data = childProducer.getData();
-            if (data.isPresent()) {
-                return data;
-            }
-        }
-        return Optional.empty();
-    }
-}
diff --git a/libs/WindowManager/Shell/res/drawable/home_icon.xml b/libs/WindowManager/Shell/res/drawable/home_icon.xml
new file mode 100644
index 0000000..1669d01
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/home_icon.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright (C) 2022 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android"
+    android:gravity="center">
+    <item android:gravity="center">
+        <shape
+            android:shape="oval">
+            <stroke
+                android:color="@color/tv_pip_edu_text_home_icon"
+                android:width="1sp" />
+            <solid android:color="@android:color/transparent" />
+            <size
+                android:width="@dimen/pip_menu_edu_text_home_icon_outline"
+                android:height="@dimen/pip_menu_edu_text_home_icon_outline"/>
+        </shape>
+    </item>
+    <item
+        android:width="@dimen/pip_menu_edu_text_home_icon"
+        android:height="@dimen/pip_menu_edu_text_home_icon"
+        android:gravity="center">
+        <vector
+            android:width="24sp"
+            android:height="24sp"
+            android:viewportWidth="24"
+            android:viewportHeight="24">
+            <path
+                android:fillColor="@color/tv_pip_edu_text_home_icon"
+                android:pathData="M12,3L4,9v12h5v-7h6v7h5V9z"/>
+        </vector>
+    </item>
+</layer-list>
diff --git a/libs/WindowManager/Shell/res/drawable/tv_pip_menu_background.xml b/libs/WindowManager/Shell/res/drawable/tv_pip_menu_background.xml
new file mode 100644
index 0000000..0c62792
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/tv_pip_menu_background.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2022 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="rectangle">
+    <corners android:radius="@dimen/pip_menu_background_corner_radius" />
+    <solid android:color="@color/tv_pip_menu_background"/>
+    <stroke android:width="@dimen/pip_menu_border_width"
+            android:color="@color/tv_pip_menu_background"/>
+</shape>
diff --git a/libs/WindowManager/Shell/res/drawable/tv_pip_menu_border.xml b/libs/WindowManager/Shell/res/drawable/tv_pip_menu_border.xml
index 9bc0311..846fdb3 100644
--- a/libs/WindowManager/Shell/res/drawable/tv_pip_menu_border.xml
+++ b/libs/WindowManager/Shell/res/drawable/tv_pip_menu_border.xml
@@ -14,9 +14,20 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-<shape xmlns:android="http://schemas.android.com/apk/res/android"
-    android:shape="rectangle">
-    <corners android:radius="@dimen/pip_menu_border_radius" />
-    <stroke android:width="@dimen/pip_menu_border_width"
-            android:color="@color/tv_pip_menu_focus_border" />
-</shape>
\ No newline at end of file
+<selector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:exitFadeDuration="@integer/pip_menu_fade_animation_duration">
+    <item android:state_activated="true">
+        <shape android:shape="rectangle">
+            <corners android:radius="@dimen/pip_menu_border_corner_radius" />
+            <stroke android:width="@dimen/pip_menu_border_width"
+                    android:color="@color/tv_pip_menu_focus_border" />
+        </shape>
+    </item>
+    <item>
+        <shape android:shape="rectangle">
+            <corners android:radius="@dimen/pip_menu_border_corner_radius" />
+            <stroke android:width="@dimen/pip_menu_border_width"
+                    android:color="@color/tv_pip_menu_background"/>
+        </shape>
+    </item>
+</selector>
diff --git a/libs/WindowManager/Shell/res/layout/tv_pip_menu.xml b/libs/WindowManager/Shell/res/layout/tv_pip_menu.xml
index b826d03..dbd5a9b 100644
--- a/libs/WindowManager/Shell/res/layout/tv_pip_menu.xml
+++ b/libs/WindowManager/Shell/res/layout/tv_pip_menu.xml
@@ -16,26 +16,41 @@
 -->
 <!-- Layout for TvPipMenuView -->
 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
-             android:id="@+id/tv_pip_menu"
-             android:layout_width="match_parent"
-             android:layout_height="match_parent">
+            android:id="@+id/tv_pip_menu"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:gravity="center|top">
+
+    <View
+        android:id="@+id/tv_pip"
+        android:layout_width="0dp"
+        android:layout_height="0dp"
+        android:layout_marginTop="@dimen/pip_menu_outer_space"
+        android:layout_marginStart="@dimen/pip_menu_outer_space"
+        android:layout_marginEnd="@dimen/pip_menu_outer_space"/>
 
     <ScrollView
         android:id="@+id/tv_pip_menu_scroll"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:gravity="center_horizontal"
+        android:layout_alignTop="@+id/tv_pip"
+        android:layout_alignStart="@+id/tv_pip"
+        android:layout_alignEnd="@+id/tv_pip"
+        android:layout_alignBottom="@+id/tv_pip"
         android:scrollbars="none"
-        android:layout_margin="@dimen/pip_menu_outer_space"
         android:visibility="gone"/>
 
     <HorizontalScrollView
         android:id="@+id/tv_pip_menu_horizontal_scroll"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
+        android:layout_alignTop="@+id/tv_pip"
+        android:layout_alignStart="@+id/tv_pip"
+        android:layout_alignEnd="@+id/tv_pip"
+        android:layout_alignBottom="@+id/tv_pip"
         android:gravity="center_vertical"
-        android:scrollbars="none"
-        android:layout_margin="@dimen/pip_menu_outer_space">
+        android:scrollbars="none">
 
         <LinearLayout
             android:id="@+id/tv_pip_menu_action_buttons"
@@ -89,10 +104,44 @@
     </HorizontalScrollView>
 
     <View
+        android:id="@+id/tv_pip_border"
+        android:layout_width="0dp"
+        android:layout_height="0dp"
+        android:layout_marginTop="@dimen/pip_menu_outer_space_frame"
+        android:layout_marginStart="@dimen/pip_menu_outer_space_frame"
+        android:layout_marginEnd="@dimen/pip_menu_outer_space_frame"
+        android:background="@drawable/tv_pip_menu_border"/>
+
+    <FrameLayout
+        android:id="@+id/tv_pip_menu_edu_text_container"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:layout_below="@+id/tv_pip"
+        android:layout_alignBottom="@+id/tv_pip_menu_frame"
+        android:layout_alignStart="@+id/tv_pip"
+        android:layout_alignEnd="@+id/tv_pip"
+        android:background="@color/tv_pip_menu_background"
+        android:clipChildren="true">
+
+        <TextView
+            android:id="@+id/tv_pip_menu_edu_text"
+            android:layout_width="wrap_content"
+            android:layout_height="@dimen/pip_menu_edu_text_view_height"
+            android:layout_gravity="bottom|center"
+            android:gravity="center"
+            android:paddingBottom="@dimen/pip_menu_border_width"
+            android:text="@string/pip_edu_text"
+            android:singleLine="true"
+            android:ellipsize="marquee"
+            android:marqueeRepeatLimit="1"
+            android:scrollHorizontally="true"
+            android:textAppearance="@style/TvPipEduText"/>
+    </FrameLayout>
+
+    <View
         android:id="@+id/tv_pip_menu_frame"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
-        android:alpha="0"
         android:layout_margin="@dimen/pip_menu_outer_space_frame"
         android:background="@drawable/tv_pip_menu_border"/>
 
diff --git a/libs/WindowManager/Shell/res/layout/tv_pip_menu_background.xml b/libs/WindowManager/Shell/res/layout/tv_pip_menu_background.xml
new file mode 100644
index 0000000..5af4020
--- /dev/null
+++ b/libs/WindowManager/Shell/res/layout/tv_pip_menu_background.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright (C) 2022 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+<!-- Layout for the back surface of the PiP menu -->
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+    <View
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:layout_margin="@dimen/pip_menu_outer_space_frame"
+        android:background="@drawable/tv_pip_menu_background"
+        android:elevation="@dimen/pip_menu_elevation"/>
+</FrameLayout>
+
diff --git a/libs/WindowManager/Shell/res/values-tvdpi/dimen.xml b/libs/WindowManager/Shell/res/values-tvdpi/dimen.xml
index 558ec51..776b18e 100644
--- a/libs/WindowManager/Shell/res/values-tvdpi/dimen.xml
+++ b/libs/WindowManager/Shell/res/values-tvdpi/dimen.xml
@@ -22,7 +22,12 @@
     <dimen name="pip_menu_button_margin">4dp</dimen>
     <dimen name="pip_menu_button_wrapper_margin">26dp</dimen>
     <dimen name="pip_menu_border_width">4dp</dimen>
-    <dimen name="pip_menu_border_radius">4dp</dimen>
+    <integer name="pip_menu_fade_animation_duration">500</integer>
+    <!-- The pip menu front border corner radius is 2dp smaller than
+        the background corner radius to hide the background from
+        showing through. -->
+    <dimen name="pip_menu_border_corner_radius">4dp</dimen>
+    <dimen name="pip_menu_background_corner_radius">6dp</dimen>
     <dimen name="pip_menu_outer_space">24dp</dimen>
 
     <!-- outer space minus border width -->
@@ -30,5 +35,14 @@
 
     <dimen name="pip_menu_arrow_size">24dp</dimen>
     <dimen name="pip_menu_arrow_elevation">5dp</dimen>
+
+    <dimen name="pip_menu_elevation">1dp</dimen>
+
+    <dimen name="pip_menu_edu_text_view_height">24dp</dimen>
+    <dimen name="pip_menu_edu_text_home_icon">9sp</dimen>
+    <dimen name="pip_menu_edu_text_home_icon_outline">14sp</dimen>
+    <integer name="pip_edu_text_show_duration_ms">10500</integer>
+    <integer name="pip_edu_text_window_exit_animation_duration_ms">1000</integer>
+    <integer name="pip_edu_text_view_exit_animation_duration_ms">300</integer>
 </resources>
 
diff --git a/libs/WindowManager/Shell/res/values/colors_tv.xml b/libs/WindowManager/Shell/res/values/colors_tv.xml
index 64b146e..fa90fe3 100644
--- a/libs/WindowManager/Shell/res/values/colors_tv.xml
+++ b/libs/WindowManager/Shell/res/values/colors_tv.xml
@@ -23,4 +23,8 @@
     <color name="tv_pip_menu_icon_bg_focused">#E8EAED</color>
     <color name="tv_pip_menu_icon_bg_unfocused">#990E0E0F</color>
     <color name="tv_pip_menu_focus_border">#E8EAED</color>
-</resources>
\ No newline at end of file
+    <color name="tv_pip_menu_background">#1E232C</color>
+
+    <color name="tv_pip_edu_text">#99D2E3FC</color>
+    <color name="tv_pip_edu_text_home_icon">#D2E3FC</color>
+</resources>
diff --git a/libs/WindowManager/Shell/res/values/styles.xml b/libs/WindowManager/Shell/res/values/styles.xml
index 7733201..19f7c3e 100644
--- a/libs/WindowManager/Shell/res/values/styles.xml
+++ b/libs/WindowManager/Shell/res/values/styles.xml
@@ -47,4 +47,13 @@
         <item name="android:layout_width">96dp</item>
         <item name="android:layout_height">48dp</item>
     </style>
+
+    <style name="TvPipEduText">
+        <item name="android:fontFamily">@*android:string/config_headlineFontFamilyMedium</item>
+        <item name="android:textAllCaps">true</item>
+        <item name="android:textSize">10sp</item>
+        <item name="android:lineSpacingExtra">4sp</item>
+        <item name="android:lineHeight">16sp</item>
+        <item name="android:textColor">@color/tv_pip_edu_text</item>
+    </style>
 </resources>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/CenteredImageSpan.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/CenteredImageSpan.java
new file mode 100644
index 0000000..6efdd57
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/CenteredImageSpan.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.pip.tv;
+
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.text.style.ImageSpan;
+
+/** An ImageSpan for a Drawable that is centered vertically in the line. */
+public class CenteredImageSpan extends ImageSpan {
+
+    private Drawable mDrawable;
+
+    public CenteredImageSpan(Drawable drawable) {
+        super(drawable);
+    }
+
+    @Override
+    public int getSize(
+            Paint paint, CharSequence text, int start, int end, Paint.FontMetricsInt fontMetrics) {
+        final Drawable drawable = getCachedDrawable();
+        final Rect rect = drawable.getBounds();
+
+        if (fontMetrics != null) {
+            Paint.FontMetricsInt paintFontMetrics = paint.getFontMetricsInt();
+            fontMetrics.ascent = paintFontMetrics.ascent;
+            fontMetrics.descent = paintFontMetrics.descent;
+            fontMetrics.top = paintFontMetrics.top;
+            fontMetrics.bottom = paintFontMetrics.bottom;
+        }
+
+        return rect.right;
+    }
+
+    @Override
+    public void draw(
+            Canvas canvas,
+            CharSequence text,
+            int start,
+            int end,
+            float x,
+            int top,
+            int y,
+            int bottom,
+            Paint paint) {
+        final Drawable drawable = getCachedDrawable();
+        canvas.save();
+        final int transY = (bottom - drawable.getBounds().bottom) / 2;
+        canvas.translate(x, transY);
+        drawable.draw(canvas);
+        canvas.restore();
+    }
+
+    private Drawable getCachedDrawable() {
+        if (mDrawable == null) {
+            mDrawable = getDrawable();
+        }
+        return mDrawable;
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java
index 1aefd77..21d5d40 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java
@@ -27,6 +27,7 @@
 
 import android.content.Context;
 import android.content.res.Resources;
+import android.graphics.Insets;
 import android.graphics.Rect;
 import android.os.SystemClock;
 import android.util.ArraySet;
@@ -150,6 +151,10 @@
         mKeepClearAlgorithm.setScreenSize(screenSize);
         mKeepClearAlgorithm.setMovementBounds(insetBounds);
         mKeepClearAlgorithm.setStashOffset(mTvPipBoundsState.getStashOffset());
+        mKeepClearAlgorithm.setPipPermanentDecorInsets(
+                mTvPipBoundsState.getPipMenuPermanentDecorInsets());
+        mKeepClearAlgorithm.setPipTemporaryDecorInsets(
+                mTvPipBoundsState.getPipMenuTemporaryDecorInsets());
 
         final Placement placement = mKeepClearAlgorithm.calculatePipPosition(
                 pipSize,
@@ -340,6 +345,7 @@
         final DisplayLayout displayLayout = mTvPipBoundsState.getDisplayLayout();
         final float expandedRatio =
                 mTvPipBoundsState.getDesiredTvExpandedAspectRatio(); // width / height
+        final Insets pipDecorations = mTvPipBoundsState.getPipMenuPermanentDecorInsets();
 
         final Size expandedSize;
         if (expandedRatio == 0) {
@@ -352,7 +358,8 @@
             if (mTvPipBoundsState.getTvFixedPipOrientation() == ORIENTATION_HORIZONTAL) {
                 expandedSize = mTvPipBoundsState.getTvExpandedSize();
             } else {
-                int maxHeight = displayLayout.height() - (2 * mScreenEdgeInsets.y);
+                int maxHeight = displayLayout.height() - (2 * mScreenEdgeInsets.y)
+                        - pipDecorations.top - pipDecorations.bottom;
                 float aspectRatioHeight = mFixedExpandedWidthInPx / expandedRatio;
 
                 if (maxHeight > aspectRatioHeight) {
@@ -374,7 +381,8 @@
             if (mTvPipBoundsState.getTvFixedPipOrientation() == ORIENTATION_VERTICAL) {
                 expandedSize = mTvPipBoundsState.getTvExpandedSize();
             } else {
-                int maxWidth = displayLayout.width() - (2 * mScreenEdgeInsets.x);
+                int maxWidth = displayLayout.width() - (2 * mScreenEdgeInsets.x)
+                        - pipDecorations.left - pipDecorations.right;
                 float aspectRatioWidth = mFixedExpandedHeightInPx * expandedRatio;
                 if (maxWidth > aspectRatioWidth) {
                     if (DEBUG) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java
index 9865548..ea07499 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java
@@ -24,6 +24,7 @@
 import android.content.Context;
 import android.content.pm.ActivityInfo;
 import android.content.pm.PackageManager;
+import android.graphics.Insets;
 import android.util.Size;
 import android.view.Gravity;
 
@@ -60,7 +61,8 @@
     private @Orientation int mTvFixedPipOrientation;
     private int mTvPipGravity;
     private @Nullable Size mTvExpandedSize;
-
+    private @NonNull Insets mPipMenuPermanentDecorInsets = Insets.NONE;
+    private @NonNull Insets mPipMenuTemporaryDecorInsets = Insets.NONE;
 
     public TvPipBoundsState(@NonNull Context context) {
         super(context);
@@ -159,4 +161,19 @@
         return mIsTvExpandedPipSupported;
     }
 
+    public void setPipMenuPermanentDecorInsets(@NonNull Insets permanentInsets) {
+        mPipMenuPermanentDecorInsets = permanentInsets;
+    }
+
+    public @NonNull Insets getPipMenuPermanentDecorInsets() {
+        return mPipMenuPermanentDecorInsets;
+    }
+
+    public void setPipMenuTemporaryDecorInsets(@NonNull Insets temporaryDecorInsets) {
+        mPipMenuTemporaryDecorInsets = temporaryDecorInsets;
+    }
+
+    public @NonNull Insets getPipMenuTemporaryDecorInsets() {
+        return mPipMenuTemporaryDecorInsets;
+    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
index 46b8e60..0e1f5a2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
@@ -115,6 +115,7 @@
     private int mPipForceCloseDelay;
 
     private int mResizeAnimationDuration;
+    private int mEduTextWindowExitAnimationDurationMs;
 
     public static Pip create(
             Context context,
@@ -323,11 +324,15 @@
         }
     }
 
+    private void updatePinnedStackBounds() {
+        updatePinnedStackBounds(mResizeAnimationDuration);
+    }
+
     /**
      * Update the PiP bounds based on the state of the PiP and keep clear areas.
      * Animates to the current PiP bounds, and schedules unstashing the PiP if necessary.
      */
-    private void updatePinnedStackBounds() {
+    private void updatePinnedStackBounds(int animationDuration) {
         if (mState == STATE_NO_PIP) {
             return;
         }
@@ -353,23 +358,26 @@
             mUnstashRunnable = null;
         }
         if (!disallowStashing && placement.getUnstashDestinationBounds() != null) {
-            mUnstashRunnable = () -> movePinnedStackTo(placement.getUnstashDestinationBounds());
+            mUnstashRunnable = () -> {
+                movePinnedStackTo(placement.getUnstashDestinationBounds(), animationDuration);
+            };
             mMainHandler.postAtTime(mUnstashRunnable, placement.getUnstashTime());
         }
     }
 
     /** Animates the PiP to the given bounds. */
     private void movePinnedStackTo(Rect bounds) {
+        movePinnedStackTo(bounds, mResizeAnimationDuration);
+    }
+
+    /** Animates the PiP to the given bounds with the given animation duration. */
+    private void movePinnedStackTo(Rect bounds, int animationDuration) {
         if (DEBUG) {
             ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
                     "%s: movePinnedStack() - new pip bounds: %s", TAG, bounds.toShortString());
         }
         mPipTaskOrganizer.scheduleAnimateResizePip(bounds,
-                mResizeAnimationDuration, rect -> {
-                    if (DEBUG) {
-                        ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
-                                "%s: movePinnedStack() animation done", TAG);
-                    }
+                animationDuration, rect -> {
                     mTvPipMenuController.updateExpansionState();
                 });
     }
@@ -408,6 +416,11 @@
         onPipDisappeared();
     }
 
+    @Override
+    public void closeEduText() {
+        updatePinnedStackBounds(mEduTextWindowExitAnimationDurationMs);
+    }
+
     private void registerSessionListenerForCurrentUser() {
         mPipMediaController.registerSessionListenerForCurrentUser();
     }
@@ -457,6 +470,7 @@
             ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
                     "%s: onPipTransition_Started(), state=%s", TAG, stateToName(mState));
         }
+        mTvPipMenuController.notifyPipAnimating(true);
     }
 
     @Override
@@ -465,6 +479,7 @@
             ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
                     "%s: onPipTransition_Canceled(), state=%s", TAG, stateToName(mState));
         }
+        mTvPipMenuController.notifyPipAnimating(false);
     }
 
     @Override
@@ -476,6 +491,7 @@
             ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
                     "%s: onPipTransition_Finished(), state=%s", TAG, stateToName(mState));
         }
+        mTvPipMenuController.notifyPipAnimating(false);
     }
 
     private void setState(@State int state) {
@@ -491,6 +507,8 @@
         final Resources res = mContext.getResources();
         mResizeAnimationDuration = res.getInteger(R.integer.config_pipResizeAnimationDuration);
         mPipForceCloseDelay = res.getInteger(R.integer.config_pipForceCloseDelay);
+        mEduTextWindowExitAnimationDurationMs =
+                res.getInteger(R.integer.pip_edu_text_window_exit_animation_duration_ms);
     }
 
     private void registerTaskStackListenerCallback(TaskStackListenerImpl taskStackListener) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipKeepClearAlgorithm.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipKeepClearAlgorithm.kt
index 5ac7a72..9ede443 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipKeepClearAlgorithm.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipKeepClearAlgorithm.kt
@@ -16,6 +16,7 @@
 
 package com.android.wm.shell.pip.tv
 
+import android.graphics.Insets
 import android.graphics.Point
 import android.graphics.Rect
 import android.util.Size
@@ -94,6 +95,13 @@
     private var lastAreasOverlappingUnstashPosition: Set<Rect> = emptySet()
     private var lastStashTime: Long = Long.MIN_VALUE
 
+    /** Spaces around the PiP that we should leave space for when placing the PiP. Permanent PiP
+     * decorations are relevant for calculating intersecting keep clear areas */
+    private var pipPermanentDecorInsets = Insets.NONE
+    /** Spaces around the PiP that we should leave space for when placing the PiP. Temporary PiP
+     * decorations are not relevant for calculating intersecting keep clear areas */
+    private var pipTemporaryDecorInsets = Insets.NONE
+
     /**
      * Calculates the position the PiP should be placed at, taking into consideration the
      * given keep clear areas.
@@ -120,20 +128,29 @@
     ): Placement {
         val transformedRestrictedAreas = transformAndFilterAreas(restrictedAreas)
         val transformedUnrestrictedAreas = transformAndFilterAreas(unrestrictedAreas)
-        val pipAnchorBounds = getNormalPipAnchorBounds(pipSize, transformedMovementBounds)
 
+        val pipSizeWithAllDecors = addDecors(pipSize)
+        val pipAnchorBoundsWithAllDecors =
+                getNormalPipAnchorBounds(pipSizeWithAllDecors, transformedMovementBounds)
+
+        val pipAnchorBoundsWithPermanentDecors = removeTemporaryDecors(pipAnchorBoundsWithAllDecors)
         val result = calculatePipPositionTransformed(
-            pipAnchorBounds,
+            pipAnchorBoundsWithPermanentDecors,
             transformedRestrictedAreas,
             transformedUnrestrictedAreas
         )
 
-        val screenSpaceBounds = fromTransformedSpace(result.bounds)
+        val pipBounds = removePermanentDecors(fromTransformedSpace(result.bounds))
+        val anchorBounds = removePermanentDecors(fromTransformedSpace(result.anchorBounds))
+        val unstashedDestBounds = result.unstashDestinationBounds?.let {
+            removePermanentDecors(fromTransformedSpace(it))
+        }
+
         return Placement(
-            screenSpaceBounds,
-            fromTransformedSpace(result.anchorBounds),
-            getStashType(screenSpaceBounds, movementBounds),
-            result.unstashDestinationBounds?.let { fromTransformedSpace(it) },
+            pipBounds,
+            anchorBounds,
+            getStashType(pipBounds, movementBounds),
+            unstashedDestBounds,
             result.unstashTime
         )
     }
@@ -447,6 +464,16 @@
         transformedMovementBounds = toTransformedSpace(movementBounds)
     }
 
+    fun setPipPermanentDecorInsets(insets: Insets) {
+        if (pipPermanentDecorInsets == insets) return
+        pipPermanentDecorInsets = insets
+    }
+
+    fun setPipTemporaryDecorInsets(insets: Insets) {
+        if (pipTemporaryDecorInsets == insets) return
+        pipTemporaryDecorInsets = insets
+    }
+
     /**
      * @param open Whether this event marks the opening of an occupied segment
      * @param pos The coordinate of this event
@@ -735,6 +762,35 @@
         return horizontal && vertical
     }
 
+    /**
+     * Adds space around [size] to leave space for decorations that will be drawn around the pip
+     */
+    private fun addDecors(size: Size): Size {
+        val bounds = Rect(0, 0, size.width, size.height)
+        bounds.inset(pipPermanentDecorInsets)
+        bounds.inset(pipTemporaryDecorInsets)
+
+        return Size(bounds.width(), bounds.height())
+    }
+
+    /**
+     * Removes the space that was reserved for permanent decorations around the pip
+     */
+    private fun removePermanentDecors(bounds: Rect): Rect {
+        val pipDecorReverseInsets = Insets.subtract(Insets.NONE, pipPermanentDecorInsets)
+        bounds.inset(pipDecorReverseInsets)
+        return bounds
+    }
+
+    /**
+     * Removes the space that was reserved for temporary decorations around the pip
+     */
+    private fun removeTemporaryDecors(bounds: Rect): Rect {
+        val pipDecorReverseInsets = Insets.subtract(Insets.NONE, pipTemporaryDecorInsets)
+        bounds.inset(pipDecorReverseInsets)
+        return bounds
+    }
+
     private fun Rect.offsetCopy(dx: Int, dy: Int) = Rect(this).apply { offset(dx, dy) }
     private fun Rect.intersectsY(other: Rect) = bottom >= other.top && top <= other.bottom
     private fun Rect.intersectsX(other: Rect) = right >= other.left && left <= other.right
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java
index 35c34ac..7b8dcf7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java
@@ -25,13 +25,17 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.ParceledListSlice;
+import android.graphics.Insets;
 import android.graphics.Matrix;
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.os.Handler;
 import android.os.RemoteException;
+import android.view.LayoutInflater;
 import android.view.SurfaceControl;
 import android.view.SyncRtSurfaceTransactionApplier;
+import android.view.View;
+import android.view.ViewRootImpl;
 import android.view.WindowManagerGlobal;
 
 import androidx.annotation.Nullable;
@@ -53,15 +57,20 @@
 public class TvPipMenuController implements PipMenuController, TvPipMenuView.Listener {
     private static final String TAG = "TvPipMenuController";
     private static final boolean DEBUG = TvPipController.DEBUG;
+    private static final String BACKGROUND_WINDOW_TITLE = "PipBackgroundView";
 
     private final Context mContext;
     private final SystemWindows mSystemWindows;
     private final TvPipBoundsState mTvPipBoundsState;
     private final Handler mMainHandler;
+    private final int mPipMenuBorderWidth;
+    private final int mPipEduTextShowDurationMs;
+    private final int mPipEduTextHeight;
 
     private Delegate mDelegate;
     private SurfaceControl mLeash;
     private TvPipMenuView mPipMenuView;
+    private View mPipBackgroundView;
 
     // User can actively move the PiP via the DPAD.
     private boolean mInMoveMode;
@@ -74,6 +83,7 @@
     private RemoteAction mCloseAction;
 
     private SyncRtSurfaceTransactionApplier mApplier;
+    private SyncRtSurfaceTransactionApplier mBackgroundApplier;
     RectF mTmpSourceRectF = new RectF();
     RectF mTmpDestinationRectF = new RectF();
     Matrix mMoveTransform = new Matrix();
@@ -91,6 +101,7 @@
             if (DEBUG) e.printStackTrace();
         }
     };
+    private final Runnable mCloseEduTextRunnable = this::closeEduText;
 
     public TvPipMenuController(Context context, TvPipBoundsState tvPipBoundsState,
             SystemWindows systemWindows, PipMediaController pipMediaController,
@@ -113,6 +124,13 @@
                 mainHandler, Context.RECEIVER_EXPORTED);
 
         pipMediaController.addActionListener(this::onMediaActionsChanged);
+
+        mPipEduTextShowDurationMs = context.getResources()
+                .getInteger(R.integer.pip_edu_text_show_duration_ms);
+        mPipEduTextHeight = context.getResources()
+                .getDimensionPixelSize(R.dimen.pip_menu_edu_text_view_height);
+        mPipMenuBorderWidth = context.getResources()
+                .getDimensionPixelSize(R.dimen.pip_menu_border_width);
     }
 
     void setDelegate(Delegate delegate) {
@@ -138,24 +156,63 @@
         }
 
         mLeash = leash;
-        attachPipMenuView();
+        attachPipMenu();
     }
 
-    private void attachPipMenuView() {
+    private void attachPipMenu() {
         if (DEBUG) {
             ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
-                    "%s: attachPipMenuView()", TAG);
+                    "%s: attachPipMenu()", TAG);
         }
 
         if (mPipMenuView != null) {
-            detachPipMenuView();
+            detachPipMenu();
         }
 
+        attachPipBackgroundView();
+        attachPipMenuView();
+
+        mTvPipBoundsState.setPipMenuPermanentDecorInsets(Insets.of(-mPipMenuBorderWidth,
+                    -mPipMenuBorderWidth, -mPipMenuBorderWidth, -mPipMenuBorderWidth));
+        mTvPipBoundsState.setPipMenuTemporaryDecorInsets(Insets.of(0, 0, 0, -mPipEduTextHeight));
+        mMainHandler.postDelayed(mCloseEduTextRunnable, mPipEduTextShowDurationMs);
+    }
+
+    private void attachPipMenuView() {
         mPipMenuView = new TvPipMenuView(mContext);
         mPipMenuView.setListener(this);
-        mSystemWindows.addView(mPipMenuView,
-                getPipMenuLayoutParams(MENU_WINDOW_TITLE, 0 /* width */, 0 /* height */),
-                0, SHELL_ROOT_LAYER_PIP);
+        setUpViewSurfaceZOrder(mPipMenuView, 1);
+        addPipMenuViewToSystemWindows(mPipMenuView, MENU_WINDOW_TITLE);
+    }
+
+    private void attachPipBackgroundView() {
+        mPipBackgroundView = LayoutInflater.from(mContext)
+                .inflate(R.layout.tv_pip_menu_background, null);
+        setUpViewSurfaceZOrder(mPipBackgroundView, -1);
+        addPipMenuViewToSystemWindows(mPipBackgroundView, BACKGROUND_WINDOW_TITLE);
+    }
+
+    private void setUpViewSurfaceZOrder(View v, int zOrderRelativeToPip) {
+        v.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
+                @Override
+                public void onViewAttachedToWindow(View v) {
+                    v.getViewRootImpl().addSurfaceChangedCallback(
+                            new PipMenuSurfaceChangedCallback(v, zOrderRelativeToPip));
+                }
+
+                @Override
+                public void onViewDetachedFromWindow(View v) {
+                }
+        });
+    }
+
+    private void addPipMenuViewToSystemWindows(View v, String title) {
+        mSystemWindows.addView(v, getPipMenuLayoutParams(title, 0 /* width */, 0 /* height */),
+                0 /* displayId */, SHELL_ROOT_LAYER_PIP);
+    }
+
+    void notifyPipAnimating(boolean animating) {
+        mPipMenuView.setEduTextActive(!animating);
     }
 
     void showMovementMenuOnly() {
@@ -171,8 +228,7 @@
     @Override
     public void showMenu() {
         if (DEBUG) {
-            ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
-                    "%s: showMenu()", TAG);
+            ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: showMenu()", TAG);
         }
         mInMoveMode = false;
         mCloseAfterExitMoveMenu = false;
@@ -183,27 +239,31 @@
         if (mPipMenuView == null) {
             return;
         }
-        Rect menuBounds = getMenuBounds(mTvPipBoundsState.getBounds());
-        mSystemWindows.updateViewLayout(mPipMenuView, getPipMenuLayoutParams(
-                MENU_WINDOW_TITLE, menuBounds.width(), menuBounds.height()));
+        maybeCloseEduText();
         maybeUpdateMenuViewActions();
         updateExpansionState();
 
-        SurfaceControl menuSurfaceControl = getSurfaceControl();
-        if (menuSurfaceControl != null) {
-            SurfaceControl.Transaction t = new SurfaceControl.Transaction();
-            t.setRelativeLayer(mPipMenuView.getWindowSurfaceControl(), mLeash, 1);
-            t.setPosition(menuSurfaceControl, menuBounds.left, menuBounds.top);
-            t.apply();
-        }
         grantPipMenuFocus(true);
         if (mInMoveMode) {
             mPipMenuView.showMoveMenu(mDelegate.getPipGravity());
         } else {
-            mPipMenuView.showButtonMenu();
+            mPipMenuView.showButtonsMenu();
         }
     }
 
+    private void maybeCloseEduText() {
+        if (mMainHandler.hasCallbacks(mCloseEduTextRunnable)) {
+            mMainHandler.removeCallbacks(mCloseEduTextRunnable);
+            mCloseEduTextRunnable.run();
+        }
+    }
+
+    private void closeEduText() {
+        mTvPipBoundsState.setPipMenuTemporaryDecorInsets(Insets.NONE);
+        mPipMenuView.hideEduText();
+        mDelegate.closeEduText();
+    }
+
     void updateGravity(int gravity) {
         mPipMenuView.showMovementHints(gravity);
     }
@@ -214,12 +274,8 @@
         mPipMenuView.setIsExpanded(mTvPipBoundsState.isTvPipExpanded());
     }
 
-    private Rect getMenuBounds(Rect pipBounds) {
-        int extraSpaceInPx = mContext.getResources()
-                .getDimensionPixelSize(R.dimen.pip_menu_outer_space);
-        Rect menuBounds = new Rect(pipBounds);
-        menuBounds.inset(-extraSpaceInPx, -extraSpaceInPx);
-        return menuBounds;
+    private Rect calculateMenuSurfaceBounds(Rect pipBounds) {
+        return mPipMenuView.getPipMenuContainerBounds(pipBounds);
     }
 
     void closeMenu() {
@@ -227,11 +283,12 @@
             ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
                     "%s: closeMenu()", TAG);
         }
+
         if (mPipMenuView == null) {
             return;
         }
 
-        mPipMenuView.hideAll();
+        mPipMenuView.hideAllUserControls();
         grantPipMenuFocus(false);
         mDelegate.onMenuClosed();
     }
@@ -266,7 +323,7 @@
         }
         if (mInMoveMode) {
             mInMoveMode = false;
-            mPipMenuView.showButtonMenu();
+            mPipMenuView.showButtonsMenu();
             return true;
         }
         return false;
@@ -287,7 +344,8 @@
     @Override
     public void detach() {
         closeMenu();
-        detachPipMenuView();
+        mMainHandler.removeCallbacks(mCloseEduTextRunnable);
+        detachPipMenu();
         mLeash = null;
     }
 
@@ -346,20 +404,15 @@
 
     @Override
     public boolean isMenuVisible() {
-        boolean isVisible = mPipMenuView != null && mPipMenuView.isVisible();
-        if (DEBUG) {
-            ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
-                    "%s: isMenuVisible: %b", TAG, isVisible);
-        }
-        return isVisible;
+        return true;
     }
 
     /**
      * Does an immediate window crop of the PiP menu.
      */
     @Override
-    public void resizePipMenu(@android.annotation.Nullable SurfaceControl pipLeash,
-            @android.annotation.Nullable SurfaceControl.Transaction t,
+    public void resizePipMenu(@Nullable SurfaceControl pipLeash,
+            @Nullable SurfaceControl.Transaction t,
             Rect destinationBounds) {
         if (DEBUG) {
             ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
@@ -373,24 +426,36 @@
             return;
         }
 
-        SurfaceControl surfaceControl = getSurfaceControl();
-        SyncRtSurfaceTransactionApplier.SurfaceParams
-                params = new SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(surfaceControl)
-                .withWindowCrop(getMenuBounds(destinationBounds))
+        final Rect menuBounds = calculateMenuSurfaceBounds(destinationBounds);
+
+        final SurfaceControl frontSurface = getSurfaceControl(mPipMenuView);
+        final SyncRtSurfaceTransactionApplier.SurfaceParams frontParams =
+                new SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(frontSurface)
+                .withWindowCrop(menuBounds)
                 .build();
+
+        final SurfaceControl backSurface = getSurfaceControl(mPipBackgroundView);
+        final SyncRtSurfaceTransactionApplier.SurfaceParams backParams =
+                new SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(backSurface)
+                .withWindowCrop(menuBounds)
+                .build();
+
+        // TODO(b/226580399): switch to using SurfaceSyncer (see b/200284684) to synchronize the
+        // animations of the pip surface with the content of the front and back menu surfaces
+        mBackgroundApplier.scheduleApply(backParams);
         if (pipLeash != null && t != null) {
-            SyncRtSurfaceTransactionApplier.SurfaceParams
+            final SyncRtSurfaceTransactionApplier.SurfaceParams
                     pipParams = new SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(pipLeash)
                     .withMergeTransaction(t)
                     .build();
-            mApplier.scheduleApply(params, pipParams);
+            mApplier.scheduleApply(frontParams, pipParams);
         } else {
-            mApplier.scheduleApply(params);
+            mApplier.scheduleApply(frontParams);
         }
     }
 
-    private SurfaceControl getSurfaceControl() {
-        return mSystemWindows.getViewSurface(mPipMenuView);
+    private SurfaceControl getSurfaceControl(View v) {
+        return mSystemWindows.getViewSurface(v);
     }
 
     @Override
@@ -412,44 +477,52 @@
             return;
         }
 
-        Rect menuDestBounds = getMenuBounds(pipDestBounds);
-        Rect mTmpSourceBounds = new Rect();
+        final Rect menuDestBounds = calculateMenuSurfaceBounds(pipDestBounds);
+        final Rect tmpSourceBounds = new Rect();
         // If there is no pip leash supplied, that means the PiP leash is already finalized
         // resizing and the PiP menu is also resized. We then want to do a scale from the current
         // new menu bounds.
         if (pipLeash != null && transaction != null) {
             if (DEBUG) {
                 ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
-                        "%s: mTmpSourceBounds based on mPipMenuView.getBoundsOnScreen()", TAG);
+                        "%s: tmpSourceBounds based on mPipMenuView.getBoundsOnScreen()", TAG);
             }
-            mPipMenuView.getBoundsOnScreen(mTmpSourceBounds);
+            mPipMenuView.getBoundsOnScreen(tmpSourceBounds);
         } else {
             if (DEBUG) {
                 ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
-                        "%s: mTmpSourceBounds based on menu width and height", TAG);
+                        "%s: tmpSourceBounds based on menu width and height", TAG);
             }
-            mTmpSourceBounds.set(0, 0, menuDestBounds.width(), menuDestBounds.height());
+            tmpSourceBounds.set(0, 0, menuDestBounds.width(), menuDestBounds.height());
         }
 
-        mTmpSourceRectF.set(mTmpSourceBounds);
+        mTmpSourceRectF.set(tmpSourceBounds);
         mTmpDestinationRectF.set(menuDestBounds);
-        mMoveTransform.setRectToRect(mTmpSourceRectF, mTmpDestinationRectF, Matrix.ScaleToFit.FILL);
+        mMoveTransform.setTranslate(mTmpDestinationRectF.left, mTmpDestinationRectF.top);
 
-        SurfaceControl surfaceControl = getSurfaceControl();
-        SyncRtSurfaceTransactionApplier.SurfaceParams params =
-                new SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(
-                        surfaceControl)
+        final SurfaceControl frontSurface = getSurfaceControl(mPipMenuView);
+        final SyncRtSurfaceTransactionApplier.SurfaceParams frontParams =
+                new SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(frontSurface)
                         .withMatrix(mMoveTransform)
                         .build();
 
+        final SurfaceControl backSurface = getSurfaceControl(mPipBackgroundView);
+        final SyncRtSurfaceTransactionApplier.SurfaceParams backParams =
+                new SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(backSurface)
+                        .withMatrix(mMoveTransform)
+                        .build();
+
+        // TODO(b/226580399): switch to using SurfaceSyncer (see b/200284684) to synchronize the
+        // animations of the pip surface with the content of the front and back menu surfaces
+        mBackgroundApplier.scheduleApply(backParams);
         if (pipLeash != null && transaction != null) {
-            SyncRtSurfaceTransactionApplier.SurfaceParams
-                    pipParams = new SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(pipLeash)
+            final SyncRtSurfaceTransactionApplier.SurfaceParams pipParams =
+                    new SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(pipLeash)
                     .withMergeTransaction(transaction)
                     .build();
-            mApplier.scheduleApply(params, pipParams);
+            mApplier.scheduleApply(frontParams, pipParams);
         } else {
-            mApplier.scheduleApply(params);
+            mApplier.scheduleApply(frontParams);
         }
 
         if (mPipMenuView.getViewRootImpl() != null) {
@@ -470,29 +543,40 @@
         if (mApplier == null) {
             mApplier = new SyncRtSurfaceTransactionApplier(mPipMenuView);
         }
+        if (mBackgroundApplier == null) {
+            mBackgroundApplier = new SyncRtSurfaceTransactionApplier(mPipBackgroundView);
+        }
         return true;
     }
 
-    private void detachPipMenuView() {
-        if (mPipMenuView == null) {
-            return;
+    private void detachPipMenu() {
+        if (mPipMenuView != null) {
+            mApplier = null;
+            mSystemWindows.removeView(mPipMenuView);
+            mPipMenuView = null;
         }
 
-        mApplier = null;
-        mSystemWindows.removeView(mPipMenuView);
-        mPipMenuView = null;
+        if (mPipBackgroundView != null) {
+            mBackgroundApplier = null;
+            mSystemWindows.removeView(mPipBackgroundView);
+            mPipBackgroundView = null;
+        }
     }
 
     @Override
     public void updateMenuBounds(Rect destinationBounds) {
-        Rect menuBounds = getMenuBounds(destinationBounds);
+        final Rect menuBounds = calculateMenuSurfaceBounds(destinationBounds);
         if (DEBUG) {
             ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
                     "%s: updateMenuBounds: %s", TAG, menuBounds.toShortString());
         }
+        mSystemWindows.updateViewLayout(mPipBackgroundView,
+                getPipMenuLayoutParams(BACKGROUND_WINDOW_TITLE, menuBounds.width(),
+                        menuBounds.height()));
         mSystemWindows.updateViewLayout(mPipMenuView,
                 getPipMenuLayoutParams(MENU_WINDOW_TITLE, menuBounds.width(),
                         menuBounds.height()));
+
         if (mPipMenuView != null) {
             mPipMenuView.updateLayout(destinationBounds);
         }
@@ -538,6 +622,8 @@
 
         void onMenuClosed();
 
+        void closeEduText();
+
         void closePip();
     }
 
@@ -555,4 +641,30 @@
                     "%s: Unable to update focus, %s", TAG, e);
         }
     }
+
+    private class PipMenuSurfaceChangedCallback implements ViewRootImpl.SurfaceChangedCallback {
+        private final View mView;
+        private final int mZOrder;
+
+        PipMenuSurfaceChangedCallback(View v, int zOrder) {
+            mView = v;
+            mZOrder = zOrder;
+        }
+
+        @Override
+        public void surfaceCreated(SurfaceControl.Transaction t) {
+            final SurfaceControl sc = getSurfaceControl(mView);
+            if (sc != null) {
+                t.setRelativeLayer(sc, mLeash, mZOrder);
+            }
+        }
+
+        @Override
+        public void surfaceReplaced(SurfaceControl.Transaction t) {
+        }
+
+        @Override
+        public void surfaceDestroyed() {
+        }
+    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java
index 9529d04..5b0db8c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java
@@ -25,11 +25,17 @@
 import static android.view.KeyEvent.KEYCODE_DPAD_UP;
 import static android.view.KeyEvent.KEYCODE_ENTER;
 
+import android.animation.ValueAnimator;
 import android.app.PendingIntent;
 import android.app.RemoteAction;
 import android.content.Context;
 import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
 import android.os.Handler;
+import android.text.Annotation;
+import android.text.Spannable;
+import android.text.SpannableString;
+import android.text.SpannedString;
 import android.util.AttributeSet;
 import android.view.Gravity;
 import android.view.KeyEvent;
@@ -40,6 +46,7 @@
 import android.widget.FrameLayout;
 import android.widget.ImageView;
 import android.widget.LinearLayout;
+import android.widget.TextView;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
@@ -49,6 +56,7 @@
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 import java.util.Objects;
 
@@ -67,6 +75,15 @@
     private final LinearLayout mActionButtonsContainer;
     private final View mMenuFrameView;
     private final List<TvPipMenuActionButton> mAdditionalButtons = new ArrayList<>();
+    private final View mPipFrameView;
+    private final View mPipView;
+    private final TextView mEduTextView;
+    private final View mEduTextContainerView;
+    private final int mPipMenuOuterSpace;
+    private final int mPipMenuBorderWidth;
+    private final int mEduTextFadeExitAnimationDurationMs;
+    private final int mEduTextSlideExitAnimationDurationMs;
+    private int mEduTextHeight;
 
     private final ImageView mArrowUp;
     private final ImageView mArrowRight;
@@ -76,11 +93,13 @@
     private final ViewGroup mScrollView;
     private final ViewGroup mHorizontalScrollView;
 
-    private Rect mCurrentBounds;
+    private Rect mCurrentPipBounds;
 
     private final TvPipMenuActionButton mExpandButton;
     private final TvPipMenuActionButton mCloseButton;
 
+    private final int mPipMenuFadeAnimationDuration;
+
     public TvPipMenuView(@NonNull Context context) {
         this(context, null);
     }
@@ -116,21 +135,86 @@
         mHorizontalScrollView = findViewById(R.id.tv_pip_menu_horizontal_scroll);
 
         mMenuFrameView = findViewById(R.id.tv_pip_menu_frame);
+        mPipFrameView = findViewById(R.id.tv_pip_border);
+        mPipView = findViewById(R.id.tv_pip);
 
         mArrowUp = findViewById(R.id.tv_pip_menu_arrow_up);
         mArrowRight = findViewById(R.id.tv_pip_menu_arrow_right);
         mArrowDown = findViewById(R.id.tv_pip_menu_arrow_down);
         mArrowLeft = findViewById(R.id.tv_pip_menu_arrow_left);
+
+        mEduTextView = findViewById(R.id.tv_pip_menu_edu_text);
+        mEduTextContainerView = findViewById(R.id.tv_pip_menu_edu_text_container);
+
+        mPipMenuFadeAnimationDuration = context.getResources()
+                .getInteger(R.integer.pip_menu_fade_animation_duration);
+        mPipMenuOuterSpace = context.getResources()
+                .getDimensionPixelSize(R.dimen.pip_menu_outer_space);
+        mPipMenuBorderWidth = context.getResources()
+                .getDimensionPixelSize(R.dimen.pip_menu_border_width);
+        mEduTextHeight = context.getResources()
+                .getDimensionPixelSize(R.dimen.pip_menu_edu_text_view_height);
+        mEduTextFadeExitAnimationDurationMs = context.getResources()
+                .getInteger(R.integer.pip_edu_text_view_exit_animation_duration_ms);
+        mEduTextSlideExitAnimationDurationMs = context.getResources()
+                .getInteger(R.integer.pip_edu_text_window_exit_animation_duration_ms);
+
+        initEduText();
     }
 
-    void updateLayout(Rect updatedBounds) {
-        ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
-                "%s: update menu layout: %s", TAG, updatedBounds.toShortString());
-        boolean previouslyVertical =
-                mCurrentBounds != null && mCurrentBounds.height() > mCurrentBounds.width();
-        boolean vertical = updatedBounds.height() > updatedBounds.width();
+    void initEduText() {
+        final SpannedString eduText = (SpannedString) getResources().getText(R.string.pip_edu_text);
+        final SpannableString spannableString = new SpannableString(eduText);
+        Arrays.stream(eduText.getSpans(0, eduText.length(), Annotation.class)).findFirst()
+                .ifPresent(annotation -> {
+                    final Drawable icon =
+                            getResources().getDrawable(R.drawable.home_icon, mContext.getTheme());
+                    if (icon != null) {
+                        icon.mutate();
+                        icon.setBounds(0, 0, icon.getIntrinsicWidth(), icon.getIntrinsicHeight());
+                        spannableString.setSpan(new CenteredImageSpan(icon),
+                                eduText.getSpanStart(annotation),
+                                eduText.getSpanEnd(annotation),
+                                Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+                    }
+                });
 
-        mCurrentBounds = updatedBounds;
+        mEduTextView.setText(spannableString);
+    }
+
+    void setEduTextActive(boolean active) {
+        mEduTextView.setSelected(active);
+    }
+
+    void hideEduText() {
+        final ValueAnimator heightAnimation = ValueAnimator.ofInt(mEduTextHeight, 0);
+        heightAnimation.setDuration(mEduTextSlideExitAnimationDurationMs);
+        heightAnimation.setInterpolator(TvPipInterpolators.BROWSE);
+        heightAnimation.addUpdateListener(animator -> {
+            mEduTextHeight = (int) animator.getAnimatedValue();
+        });
+        mEduTextView.animate()
+                .alpha(0f)
+                .setInterpolator(TvPipInterpolators.EXIT)
+                .setDuration(mEduTextFadeExitAnimationDurationMs)
+                .withEndAction(() -> {
+                    mEduTextContainerView.setVisibility(GONE);
+                }).start();
+        heightAnimation.start();
+    }
+
+    void updateLayout(Rect updatedPipBounds) {
+        ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+                "%s: update menu layout: %s", TAG, updatedPipBounds.toShortString());
+
+        boolean previouslyVertical =
+                mCurrentPipBounds != null && mCurrentPipBounds.height() > mCurrentPipBounds.width();
+        boolean vertical = updatedPipBounds.height() > updatedPipBounds.width();
+
+        mCurrentPipBounds = updatedPipBounds;
+
+        updatePipFrameBounds();
+
         if (previouslyVertical == vertical) {
             if (DEBUG) {
                 ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
@@ -158,6 +242,38 @@
         mHorizontalScrollView.setVisibility(vertical ? GONE : VISIBLE);
     }
 
+    Rect getPipMenuContainerBounds(Rect pipBounds) {
+        final Rect menuUiBounds = new Rect(pipBounds);
+        menuUiBounds.inset(-mPipMenuOuterSpace, -mPipMenuOuterSpace);
+        menuUiBounds.bottom += mEduTextHeight;
+        return menuUiBounds;
+    }
+
+    /**
+     * Update mPipFrameView's bounds according to the new pip window bounds. We can't
+     * make mPipFrameView match_parent, because the pip menu might contain other content around
+     * the pip window (e.g. edu text).
+     * TvPipMenuView needs to account for this so that it can draw a white border around the whole
+     * pip menu when it gains focus.
+     */
+    private void updatePipFrameBounds() {
+        final ViewGroup.LayoutParams pipFrameParams = mPipFrameView.getLayoutParams();
+        if (pipFrameParams != null) {
+            pipFrameParams.width = mCurrentPipBounds.width() + 2 * mPipMenuBorderWidth;
+            pipFrameParams.height = mCurrentPipBounds.height() + 2 * mPipMenuBorderWidth;
+            mPipFrameView.setLayoutParams(pipFrameParams);
+        }
+
+        final ViewGroup.LayoutParams pipViewParams = mPipView.getLayoutParams();
+        if (pipViewParams != null) {
+            pipViewParams.width = mCurrentPipBounds.width();
+            pipViewParams.height = mCurrentPipBounds.height();
+            mPipView.setLayoutParams(pipViewParams);
+        }
+
+
+    }
+
     void setListener(@Nullable Listener listener) {
         mListener = listener;
     }
@@ -184,30 +300,32 @@
         if (DEBUG) {
             ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: showMoveMenu()", TAG);
         }
-        showMenuButtons(false);
+        showButtonsMenu(false);
         showMovementHints(gravity);
-        showMenuFrame(true);
+        setFrameHighlighted(true);
     }
 
-    void showButtonMenu() {
+    void showButtonsMenu() {
         if (DEBUG) {
-            ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: showButtonMenu()", TAG);
+            ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+                    "%s: showButtonsMenu()", TAG);
         }
-        showMenuButtons(true);
+        showButtonsMenu(true);
         hideMovementHints();
-        showMenuFrame(true);
+        setFrameHighlighted(true);
     }
 
     /**
      * Hides all menu views, including the menu frame.
      */
-    void hideAll() {
+    void hideAllUserControls() {
         if (DEBUG) {
-            ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: hideAll()", TAG);
+            ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+                    "%s: hideAllUserControls()", TAG);
         }
-        showMenuButtons(false);
+        showButtonsMenu(false);
         hideMovementHints();
-        showMenuFrame(false);
+        setFrameHighlighted(false);
     }
 
     private void animateAlphaTo(float alpha, View view) {
@@ -217,7 +335,7 @@
         view.animate()
                 .alpha(alpha)
                 .setInterpolator(alpha == 0f ? TvPipInterpolators.EXIT : TvPipInterpolators.ENTER)
-                .setDuration(500)
+                .setDuration(mPipMenuFadeAnimationDuration)
                 .withStartAction(() -> {
                     if (alpha != 0) {
                         view.setVisibility(VISIBLE);
@@ -230,15 +348,6 @@
                 });
     }
 
-    boolean isVisible() {
-        return mMenuFrameView.getAlpha() != 0f
-                || mActionButtonsContainer.getAlpha() != 0f
-                || mArrowUp.getAlpha() != 0f
-                || mArrowRight.getAlpha() != 0f
-                || mArrowDown.getAlpha() != 0f
-                || mArrowLeft.getAlpha() != 0f;
-    }
-
     void setAdditionalActions(List<RemoteAction> actions, RemoteAction closeAction,
             Handler mainHandler) {
         if (DEBUG) {
@@ -423,18 +532,18 @@
     }
 
     /**
-     * Show or hide the pip user actions.
+     * Show or hide the pip buttons menu.
      */
-    public void showMenuButtons(boolean show) {
+    public void showButtonsMenu(boolean show) {
         if (DEBUG) {
             ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
-                    "%s: showMenuButtons: %b", TAG, show);
+                    "%s: showUserActions: %b", TAG, show);
         }
         animateAlphaTo(show ? 1 : 0, mActionButtonsContainer);
     }
 
-    private void showMenuFrame(boolean show) {
-        animateAlphaTo(show ? 1 : 0, mMenuFrameView);
+    private void setFrameHighlighted(boolean highlighted) {
+        mMenuFrameView.setActivated(highlighted);
     }
 
     interface Listener {
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 fd6e59e..cde4247 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
@@ -20,8 +20,10 @@
 import static android.graphics.Color.WHITE;
 import static android.graphics.Color.alpha;
 import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
+import static android.view.ViewRootImpl.LOCAL_LAYOUT;
 import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS;
 import static android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS;
+import static android.view.WindowLayout.UNSPECIFIED_LENGTH;
 import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
 import static android.view.WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
 import static android.view.WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES;
@@ -51,6 +53,7 @@
 import android.app.ActivityManager;
 import android.app.ActivityManager.TaskDescription;
 import android.app.ActivityThread;
+import android.app.WindowConfiguration;
 import android.content.Context;
 import android.graphics.Canvas;
 import android.graphics.Color;
@@ -77,6 +80,7 @@
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.WindowInsets;
+import android.view.WindowLayout;
 import android.view.WindowManager;
 import android.view.WindowManagerGlobal;
 import android.window.ClientWindowFrames;
@@ -208,6 +212,8 @@
         final IWindowSession session = WindowManagerGlobal.getWindowSession();
         final SurfaceControl surfaceControl = new SurfaceControl();
         final ClientWindowFrames tmpFrames = new ClientWindowFrames();
+        final WindowLayout windowLayout = new WindowLayout();
+        final Rect displayCutoutSafe = new Rect();
 
         final InsetsSourceControl[] tmpControls = new InsetsSourceControl[0];
         final MergedConfiguration tmpMergedConfiguration = new MergedConfiguration();
@@ -244,9 +250,25 @@
         window.setOuter(snapshotSurface);
         try {
             Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "TaskSnapshot#relayout");
-            session.relayout(window, layoutParams, -1, -1, View.VISIBLE, 0,
-                    tmpFrames, tmpMergedConfiguration, surfaceControl, tmpInsetsState,
-                    tmpControls, new Bundle());
+            if (LOCAL_LAYOUT) {
+                if (!surfaceControl.isValid()) {
+                    session.updateVisibility(window, layoutParams, View.VISIBLE,
+                            tmpMergedConfiguration, surfaceControl, tmpInsetsState, tmpControls);
+                }
+                tmpInsetsState.getDisplayCutoutSafe(displayCutoutSafe);
+                final WindowConfiguration winConfig =
+                        tmpMergedConfiguration.getMergedConfiguration().windowConfiguration;
+                windowLayout.computeFrames(layoutParams, tmpInsetsState, displayCutoutSafe,
+                        winConfig.getBounds(), winConfig.getWindowingMode(), UNSPECIFIED_LENGTH,
+                        UNSPECIFIED_LENGTH, info.requestedVisibilities,
+                        null /* attachedWindowFrame */, 1f /* compatScale */, tmpFrames);
+                session.updateLayout(window, layoutParams, 0 /* flags */, tmpFrames,
+                        UNSPECIFIED_LENGTH, UNSPECIFIED_LENGTH);
+            } else {
+                session.relayout(window, layoutParams, -1, -1, View.VISIBLE, 0,
+                        tmpFrames, tmpMergedConfiguration, surfaceControl, tmpInsetsState,
+                        tmpControls, new Bundle());
+            }
             Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
         } catch (RemoteException e) {
             snapshotSurface.clearWindowSynced();
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaIntentTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaIntentTest.kt
index 3fe6f02..9a8c894 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaIntentTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaIntentTest.kt
@@ -16,6 +16,7 @@
 
 package com.android.wm.shell.flicker.pip
 
+import android.platform.test.annotations.Presubmit
 import android.view.Surface
 import androidx.test.filters.FlakyTest
 import androidx.test.filters.RequiresDevice
@@ -84,7 +85,7 @@
     override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales()
 
     /** {@inheritDoc}  */
-    @FlakyTest(bugId = 197726610)
+    @Presubmit
     @Test
     override fun pipLayerExpands() = super.pipLayerExpands()
 
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt
index 5fc80db..9f3fcea 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt
@@ -111,7 +111,7 @@
     /**
      * Checks that the visible region of [pipApp] always expands during the animation
      */
-    @Presubmit
+    @FlakyTest(bugId = 228012337)
     @Test
     fun pipLayerExpands() {
         val layerName = pipApp.component.toLayerName()
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt
index 6af01e2..c1ee1a7 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt
@@ -33,7 +33,6 @@
 import com.android.server.wm.flicker.statusBarLayerRotatesScales
 import com.android.wm.shell.flicker.helpers.FixedAppHelper
 import org.junit.Assume
-import org.junit.Before
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -69,11 +68,6 @@
     private val screenBoundsStart = WindowUtils.getDisplayBounds(testSpec.startRotation)
     private val screenBoundsEnd = WindowUtils.getDisplayBounds(testSpec.endRotation)
 
-    @Before
-    open fun before() {
-        Assume.assumeFalse(isShellTransitionsEnabled)
-    }
-
     override val transition: FlickerBuilder.() -> Unit
         get() = buildTransition(eachRun = false) {
             setup {
@@ -135,15 +129,30 @@
     /**
      * Checks that [pipApp] layer is within [screenBoundsStart] at the start of the transition
      */
-    @Presubmit
-    @Test
-    open fun pipLayerRotates_StartingBounds() {
+    private fun pipLayerRotates_StartingBounds_internal() {
         testSpec.assertLayersStart {
             visibleRegion(pipApp.component).coversAtMost(screenBoundsStart)
         }
     }
 
     /**
+     * Checks that [pipApp] layer is within [screenBoundsStart] at the start of the transition
+     */
+    @Presubmit
+    @Test
+    fun pipLayerRotates_StartingBounds() {
+        Assume.assumeFalse(isShellTransitionsEnabled)
+        pipLayerRotates_StartingBounds_internal()
+    }
+
+    @FlakyTest(bugId = 228024285)
+    @Test
+    fun pipLayerRotates_StartingBounds_ShellTransit() {
+        Assume.assumeTrue(isShellTransitionsEnabled)
+        pipLayerRotates_StartingBounds_internal()
+    }
+
+    /**
      * Checks that [pipApp] layer is within [screenBoundsEnd] at the end of the transition
      */
     @Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt
index 81403d0..e40f2bc 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt
@@ -47,7 +47,6 @@
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
 @Group4
-@FlakyTest(bugId = 218604389)
 open class SetRequestedOrientationWhilePinnedTest(
     testSpec: FlickerTestParameter
 ) : PipTransition(testSpec) {
diff --git a/location/java/android/location/LocationDeviceConfig.java b/location/java/android/location/LocationDeviceConfig.java
index c55eed9..7d22681 100644
--- a/location/java/android/location/LocationDeviceConfig.java
+++ b/location/java/android/location/LocationDeviceConfig.java
@@ -24,6 +24,30 @@
 public final class LocationDeviceConfig {
 
     /**
+     * Package/tag combinations that are allowlisted for ignoring location settings (may retrieve
+     * location even when user location settings are off), for advanced driver-assistance systems
+     * only.
+     *
+     * <p>Package/tag combinations are separated by commas (","), and with in each combination is a
+     * package name followed by 0 or more attribution tags, separated by semicolons (";"). If a
+     * package is followed by 0 attribution tags, this is interpreted the same as the wildcard
+     * value. There are two special interpreted values for attribution tags, the wildcard value
+     * ("*") which represents all attribution tags, and the null value ("null"), which is converted
+     * to the null string (since attribution tags may be null). This format implies that attribution
+     * tags which should be on this list may not contain semicolons.
+     *
+     * <p>Examples of valid entries:
+     *
+     * <ul>
+     *   <li>android
+     *   <li>android;*
+     *   <li>android;*,com.example.app;null;my_attr
+     *   <li>android;*,com.example.app;null;my_attr,com.example.otherapp;my_attr
+     * </ul>
+     */
+    public static final String ADAS_SETTINGS_ALLOWLIST = "adas_settings_allowlist";
+
+    /**
      * Package/tag combinations that are allowedlisted for ignoring location settings (may retrieve
      * location even when user location settings are off, and may ignore throttling, etc), for
      * emergency purposes only.
@@ -39,10 +63,10 @@
      * <p>Examples of valid entries:
      *
      * <ul>
-     *     <li>android</li>
-     *     <li>android;*</li>
-     *     <li>android;*,com.example.app;null;my_attr</li>
-     *     <li>android;*,com.example.app;null;my_attr,com.example.otherapp;my_attr</li>
+     *   <li>android
+     *   <li>android;*
+     *   <li>android;*,com.example.app;null;my_attr
+     *   <li>android;*,com.example.app;null;my_attr,com.example.otherapp;my_attr
      * </ul>
      */
     public static final String IGNORE_SETTINGS_ALLOWLIST = "ignore_settings_allowlist";
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 491a5cd..e7eda3e 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -5898,14 +5898,8 @@
      */
     @UnsupportedAppUsage
     @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
-    public void setWiredDeviceConnectionState(int device, int state, String address,
-            String name) {
-        final IAudioService service = getService();
-        int role = isOutputDevice(device)
-                ? AudioDeviceAttributes.ROLE_OUTPUT : AudioDeviceAttributes.ROLE_INPUT;
-        AudioDeviceAttributes attributes = new AudioDeviceAttributes(
-                role, AudioDeviceInfo.convertInternalDeviceToDeviceType(device), address,
-                name, new ArrayList<>()/*mAudioProfiles*/, new ArrayList<>()/*mAudioDescriptors*/);
+    public void setWiredDeviceConnectionState(int device, int state, String address, String name) {
+        AudioDeviceAttributes attributes = new AudioDeviceAttributes(device, address, name);
         setWiredDeviceConnectionState(attributes, state);
     }
 
diff --git a/media/tests/aidltests/Android.bp b/media/tests/aidltests/Android.bp
index c3d5fa2..7a25b6d 100644
--- a/media/tests/aidltests/Android.bp
+++ b/media/tests/aidltests/Android.bp
@@ -33,7 +33,6 @@
     libs: [
         "framework",
     ],
-    sdk_version: "current",
     platform_apis: true,
     test_suites: ["device-tests"],
     certificate: "platform",
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index a10ca9e..a28c4cf 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -575,6 +575,9 @@
     <uses-permission android:name="android.permission.ACCESS_KEYGUARD_SECURE_STORAGE" />
     <uses-permission android:name="android.permission.TRUST_LISTENER" />
 
+    <!-- Permission required for CTS test - CtsTaskFpsCallbackTestCases -->
+    <uses-permission android:name="android.permission.ACCESS_FPS_COUNTER" />
+
     <!-- Permission required for CTS test - CtsGameManagerTestCases -->
     <uses-permission android:name="android.permission.MANAGE_GAME_MODE" />
 
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/StatusBarStateController.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/StatusBarStateController.java
index 9829918..9ed3bac 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/StatusBarStateController.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/StatusBarStateController.java
@@ -94,6 +94,12 @@
         }
 
         /**
+         * Callback to be notified about upcoming state changes. Typically, is immediately followed
+         * by #onStateChanged, unless there was an intentional delay in updating the state changed.
+         */
+        default void onUpcomingStateChanged(int upcomingState) {}
+
+        /**
          * Callback to be notified when Dozing changes. Dozing is stored separately from state.
          */
         default void onDozingChanged(boolean isDozing) {}
diff --git a/packages/SystemUI/res/layout/clipboard_overlay.xml b/packages/SystemUI/res/layout/clipboard_overlay.xml
index c0436b2..ccfd3a3 100644
--- a/packages/SystemUI/res/layout/clipboard_overlay.xml
+++ b/packages/SystemUI/res/layout/clipboard_overlay.xml
@@ -136,6 +136,7 @@
             android:layout_height="@dimen/overlay_dismiss_button_tappable_size"
             android:elevation="@dimen/overlay_dismiss_button_elevation"
             android:visibility="gone"
+            android:alpha="0"
             app:layout_constraintStart_toEndOf="@id/clipboard_preview"
             app:layout_constraintEnd_toEndOf="@id/clipboard_preview"
             app:layout_constraintTop_toTopOf="@id/clipboard_preview"
diff --git a/packages/SystemUI/res/layout/udfps_view.xml b/packages/SystemUI/res/layout/udfps_view.xml
index 0fcbfa1..257d238 100644
--- a/packages/SystemUI/res/layout/udfps_view.xml
+++ b/packages/SystemUI/res/layout/udfps_view.xml
@@ -28,10 +28,4 @@
         android:layout_width="match_parent"
         android:layout_height="match_parent"/>
 
-    <com.android.systemui.biometrics.UdfpsSurfaceView
-        android:id="@+id/hbm_view"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:visibility="invisible"/>
-
 </com.android.systemui.biometrics.UdfpsView>
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index 50fdc7b..24118d2 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -137,9 +137,11 @@
     <!-- UDFPS colors -->
     <color name="udfps_enroll_icon">#7DA7F1</color>
     <color name="udfps_moving_target_fill">#475670</color>
+    <!-- 50% of udfps_moving_target_fill-->
+    <color name="udfps_moving_target_fill_error">#80475670</color>
     <color name="udfps_enroll_progress">#7DA7F1</color>
     <color name="udfps_enroll_progress_help">#607DA7F1</color>
-    <color name="udfps_enroll_progress_help_with_talkback">#ffEE675C</color>
+    <color name="udfps_enroll_progress_help_with_talkback">#FFEE675C</color>
 
     <!-- Floating overlay actions -->
     <color name="overlay_button_ripple">#1f000000</color>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 847aefd..ee77d21 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -958,6 +958,16 @@
         <item name="android:textAlignment">center</item>
     </style>
 
+    <!-- We explicitly overload this because we don't have control over the style or layout for
+         the cast dialog items, as it's in `@android:layout/media_route_list_item. -->
+    <style name="TextAppearance.CastItem" parent="@android:style/TextAppearance.DeviceDefault.Medium">
+        <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
+    </style>
+
+    <style name="Theme.SystemUI.Dialog.Cast">
+        <item name="android:textAppearanceMedium">@style/TextAppearance.CastItem</item>
+    </style>
+    <!-- ************************************************************************************* -->
 
     <style name="Widget" />
     <style name="Widget.Dialog" />
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
index 6b6af4c..b2673e9 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
@@ -126,7 +126,7 @@
         int[] mSensorIds;
         boolean mSkipIntro;
         long mOperationId;
-        long mRequestId;
+        long mRequestId = -1;
         boolean mSkipAnimation = false;
         @BiometricMultiSensorMode int mMultiSensorConfig = BIOMETRIC_MULTI_SENSOR_DEFAULT;
     }
@@ -599,6 +599,11 @@
     }
 
     @Override
+    public long getRequestId() {
+        return mConfig.mRequestId;
+    }
+
+    @Override
     public void animateToCredentialUI() {
         mBiometricView.startTransitionToCredentialUI();
     }
@@ -678,7 +683,9 @@
             return;
         }
         mContainerState = STATE_GONE;
-        mWindowManager.removeView(this);
+        if (isAttachedToWindow()) {
+            mWindowManager.removeView(this);
+        }
     }
 
     private void onDialogAnimatedIn() {
@@ -687,6 +694,11 @@
             animateAway(AuthDialogCallback.DISMISSED_USER_CANCELED);
             return;
         }
+        if (mContainerState == STATE_ANIMATING_OUT || mContainerState == STATE_GONE) {
+            Log.d(TAG, "onDialogAnimatedIn(): ignore, already animating out or gone - state: "
+                    + mContainerState);
+            return;
+        }
         mContainerState = STATE_SHOWING;
         if (mBiometricView != null) {
             mConfig.mCallback.onDialogAnimatedIn();
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index c100a07..aaf18b3 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -768,7 +768,7 @@
     }
 
     @Override
-    public void hideAuthenticationDialog() {
+    public void hideAuthenticationDialog(long requestId) {
         if (DEBUG) Log.d(TAG, "hideAuthenticationDialog: " + mCurrentDialog);
 
         if (mCurrentDialog == null) {
@@ -777,6 +777,11 @@
             if (DEBUG) Log.d(TAG, "dialog already gone");
             return;
         }
+        if (requestId != mCurrentDialog.getRequestId()) {
+            Log.w(TAG, "ignore - ids do not match: " + requestId + " current: "
+                    + mCurrentDialog.getRequestId());
+            return;
+        }
 
         mCurrentDialog.dismissFromSystemServer();
 
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialog.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialog.java
index 59ed156..4ff19f6 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialog.java
@@ -150,6 +150,9 @@
      */
     String getOpPackageName();
 
+    /** The requestId of the underlying operation within the framework. */
+    long getRequestId();
+
     /**
      * Animate to credential UI. Typically called after biometric is locked out.
      */
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarDrawable.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarDrawable.java
index f3a603f..59c658f 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarDrawable.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarDrawable.java
@@ -23,6 +23,7 @@
 import android.graphics.Paint;
 import android.graphics.drawable.Drawable;
 import android.view.accessibility.AccessibilityManager;
+import android.view.animation.DecelerateInterpolator;
 import android.view.animation.Interpolator;
 import android.view.animation.OvershootInterpolator;
 
@@ -40,13 +41,15 @@
 
     private static final long CHECKMARK_ANIMATION_DELAY_MS = 200L;
     private static final long CHECKMARK_ANIMATION_DURATION_MS = 300L;
-    private static final long FILL_COLOR_ANIMATION_DURATION_MS = 200L;
+    private static final long FILL_COLOR_ANIMATION_DURATION_MS = 350L;
     private static final long PROGRESS_ANIMATION_DURATION_MS = 400L;
     private static final float STROKE_WIDTH_DP = 12f;
+    private static final Interpolator DEACCEL = new DecelerateInterpolator();
 
     private final float mStrokeWidthPx;
     @ColorInt private final int mProgressColor;
     @ColorInt private final int mHelpColor;
+    @ColorInt private final int mOnFirstBucketFailedColor;
     @NonNull private final Drawable mCheckmarkDrawable;
     @NonNull private final Interpolator mCheckmarkInterpolator;
     @NonNull private final Paint mBackgroundPaint;
@@ -64,6 +67,9 @@
     @Nullable private ValueAnimator mFillColorAnimator;
     @NonNull private final ValueAnimator.AnimatorUpdateListener mFillColorUpdateListener;
 
+    @Nullable private ValueAnimator mBackgroundColorAnimator;
+    @NonNull private final ValueAnimator.AnimatorUpdateListener mBackgroundColorUpdateListener;
+
     private boolean mComplete = false;
     private float mCheckmarkScale = 0f;
     @Nullable private ValueAnimator mCheckmarkAnimator;
@@ -76,8 +82,10 @@
         final boolean isAccessbilityEnabled = am.isTouchExplorationEnabled();
         if (!isAccessbilityEnabled) {
             mHelpColor = context.getColor(R.color.udfps_enroll_progress_help);
+            mOnFirstBucketFailedColor = context.getColor(R.color.udfps_moving_target_fill_error);
         } else {
             mHelpColor = context.getColor(R.color.udfps_enroll_progress_help_with_talkback);
+            mOnFirstBucketFailedColor = mHelpColor;
         }
         mCheckmarkDrawable = context.getDrawable(R.drawable.udfps_enroll_checkmark);
         mCheckmarkDrawable.mutate();
@@ -112,6 +120,11 @@
             mCheckmarkScale = (float) animation.getAnimatedValue();
             invalidateSelf();
         };
+
+        mBackgroundColorUpdateListener = animation -> {
+            mBackgroundPaint.setColor((int) animation.getAnimatedValue());
+            invalidateSelf();
+        };
     }
 
     void onEnrollmentProgress(int remaining, int totalSteps) {
@@ -163,19 +176,38 @@
         }
     }
 
+    private void animateBackgroundColor() {
+        if (mBackgroundColorAnimator != null && mBackgroundColorAnimator.isRunning()) {
+            mBackgroundColorAnimator.end();
+        }
+        mBackgroundColorAnimator = ValueAnimator.ofArgb(mBackgroundPaint.getColor(),
+                mOnFirstBucketFailedColor);
+        mBackgroundColorAnimator.setDuration(FILL_COLOR_ANIMATION_DURATION_MS);
+        mBackgroundColorAnimator.setRepeatCount(1);
+        mBackgroundColorAnimator.setRepeatMode(ValueAnimator.REVERSE);
+        mBackgroundColorAnimator.setInterpolator(DEACCEL);
+        mBackgroundColorAnimator.addUpdateListener(mBackgroundColorUpdateListener);
+        mBackgroundColorAnimator.start();
+    }
+
     private void updateFillColor(boolean showingHelp) {
-        if (mShowingHelp == showingHelp) {
+        if (!mAfterFirstTouch && showingHelp) {
+            // If we are on the first touch, animate the background color
+            // instead of the progress color.
+            animateBackgroundColor();
             return;
         }
-        mShowingHelp = showingHelp;
 
         if (mFillColorAnimator != null && mFillColorAnimator.isRunning()) {
-            mFillColorAnimator.cancel();
+            mFillColorAnimator.end();
         }
 
         @ColorInt final int targetColor = showingHelp ? mHelpColor : mProgressColor;
         mFillColorAnimator = ValueAnimator.ofArgb(mFillPaint.getColor(), targetColor);
         mFillColorAnimator.setDuration(FILL_COLOR_ANIMATION_DURATION_MS);
+        mFillColorAnimator.setRepeatCount(1);
+        mFillColorAnimator.setRepeatMode(ValueAnimator.REVERSE);
+        mFillColorAnimator.setInterpolator(DEACCEL);
         mFillColorAnimator.addUpdateListener(mFillColorUpdateListener);
         mFillColorAnimator.start();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsHbmProvider.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsHbmProvider.java
index da24a8f..38c937f 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsHbmProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsHbmProvider.java
@@ -17,9 +17,6 @@
 package com.android.systemui.biometrics;
 
 import android.annotation.Nullable;
-import android.view.Surface;
-
-import com.android.systemui.biometrics.UdfpsHbmTypes.HbmType;
 
 /**
  * Interface for controlling the high-brightness mode (HBM). UdfpsView can use this callback to
@@ -36,24 +33,21 @@
      * This method must be called from the UI thread. The callback, if provided, will also be
      * invoked from the UI thread.
      *
-     * @param hbmType The type of HBM that should be enabled. See {@link UdfpsHbmTypes}.
-     * @param surface The surface for which the HBM is requested, in case the HBM implementation
-     *                needs to set special surface flags to enable the HBM. Can be null.
      * @param onHbmEnabled A runnable that will be executed once HBM is enabled.
      */
-    void enableHbm(@HbmType int hbmType, @Nullable Surface surface,
-            @Nullable Runnable onHbmEnabled);
+    void enableHbm(@Nullable Runnable onHbmEnabled);
 
     /**
-     * UdfpsView will call this to disable the HBM when the illumination is not longer needed.
+     * UdfpsView will call this to disable HBM when illumination is no longer needed.
      *
-     * This method is a no-op when HBM is already disabled. If HBM is enabled, this method will
-     * disable HBM for the {@code hbmType} and {@code surface} that were provided to the
-     * corresponding {@link #enableHbm(int, Surface, Runnable)}.
+     * This method will disable HBM if HBM is enabled. Otherwise, if HBM is already disabled,
+     * this method is a no-op.
      *
      * The call must be made from the UI thread. The callback, if provided, will also be invoked
      * from the UI thread.
      *
+     *
+     *
      * @param onHbmDisabled A runnable that will be executed once HBM is disabled.
      */
     void disableHbm(@Nullable Runnable onHbmDisabled);
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsHbmTypes.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsHbmTypes.java
deleted file mode 100644
index 3ab0bd6..0000000
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsHbmTypes.java
+++ /dev/null
@@ -1,39 +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.biometrics;
-
-import android.annotation.IntDef;
-import android.hardware.fingerprint.IUdfpsHbmListener;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
-/**
- * Different high-brightness mode (HBM) types that are relevant to this package.
- */
-public final class UdfpsHbmTypes {
-    /** HBM that applies to the whole screen. */
-    public static final int GLOBAL_HBM = IUdfpsHbmListener.GLOBAL_HBM;
-
-    /** HBM that only applies to a portion of the screen. */
-    public static final int LOCAL_HBM = IUdfpsHbmListener.LOCAL_HBM;
-
-    @Retention(RetentionPolicy.SOURCE)
-    @IntDef({GLOBAL_HBM, LOCAL_HBM})
-    public @interface HbmType {
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsSurfaceView.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsSurfaceView.java
deleted file mode 100644
index 77fad35..0000000
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsSurfaceView.java
+++ /dev/null
@@ -1,145 +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.biometrics;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.content.Context;
-import android.graphics.Canvas;
-import android.graphics.Paint;
-import android.graphics.PixelFormat;
-import android.graphics.RectF;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.view.Surface;
-import android.view.SurfaceHolder;
-import android.view.SurfaceView;
-
-/**
- * Surface View for providing the Global High-Brightness Mode (GHBM) illumination for UDFPS.
- */
-public class UdfpsSurfaceView extends SurfaceView implements SurfaceHolder.Callback {
-    private static final String TAG = "UdfpsSurfaceView";
-
-    /**
-     * Notifies {@link UdfpsView} when to enable GHBM illumination.
-     */
-    interface GhbmIlluminationListener {
-        /**
-         * @param surface the surface for which GHBM should be enabled.
-         * @param onIlluminatedRunnable a runnable that should be run after GHBM is enabled.
-         */
-        void enableGhbm(@NonNull Surface surface, @Nullable Runnable onIlluminatedRunnable);
-    }
-
-    @NonNull private final SurfaceHolder mHolder;
-    @NonNull private final Paint mSensorPaint;
-
-    @Nullable private GhbmIlluminationListener mGhbmIlluminationListener;
-    @Nullable private Runnable mOnIlluminatedRunnable;
-    boolean mAwaitingSurfaceToStartIllumination;
-    boolean mHasValidSurface;
-
-    public UdfpsSurfaceView(Context context, AttributeSet attrs) {
-        super(context, attrs);
-
-        // Make this SurfaceView draw on top of everything else in this window. This allows us to
-        // 1) Always show the HBM circle on top of everything else, and
-        // 2) Properly composite this view with any other animations in the same window no matter
-        //    what contents are added in which order to this view hierarchy.
-        setZOrderOnTop(true);
-
-        mHolder = getHolder();
-        mHolder.addCallback(this);
-        mHolder.setFormat(PixelFormat.RGBA_8888);
-
-        mSensorPaint = new Paint(0 /* flags */);
-        mSensorPaint.setAntiAlias(true);
-        mSensorPaint.setARGB(255, 255, 255, 255);
-        mSensorPaint.setStyle(Paint.Style.FILL);
-    }
-
-    @Override public void surfaceCreated(SurfaceHolder holder) {
-        mHasValidSurface = true;
-        if (mAwaitingSurfaceToStartIllumination) {
-            doIlluminate(mOnIlluminatedRunnable);
-            mOnIlluminatedRunnable = null;
-            mAwaitingSurfaceToStartIllumination = false;
-        }
-    }
-
-    @Override
-    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
-        // Unused.
-    }
-
-    @Override public void surfaceDestroyed(SurfaceHolder holder) {
-        mHasValidSurface = false;
-    }
-
-    void setGhbmIlluminationListener(@Nullable GhbmIlluminationListener listener) {
-        mGhbmIlluminationListener = listener;
-    }
-
-    /**
-     * Note: there is no corresponding method to stop GHBM illumination. It is expected that
-     * {@link UdfpsView} will hide this view, which would destroy the surface and remove the
-     * illumination dot.
-     */
-    void startGhbmIllumination(@Nullable Runnable onIlluminatedRunnable) {
-        if (mGhbmIlluminationListener == null) {
-            Log.e(TAG, "startIllumination | mGhbmIlluminationListener is null");
-            return;
-        }
-
-        if (mHasValidSurface) {
-            doIlluminate(onIlluminatedRunnable);
-        } else {
-            mAwaitingSurfaceToStartIllumination = true;
-            mOnIlluminatedRunnable = onIlluminatedRunnable;
-        }
-    }
-
-    private void doIlluminate(@Nullable Runnable onIlluminatedRunnable) {
-        if (mGhbmIlluminationListener == null) {
-            Log.e(TAG, "doIlluminate | mGhbmIlluminationListener is null");
-            return;
-        }
-
-        mGhbmIlluminationListener.enableGhbm(mHolder.getSurface(), onIlluminatedRunnable);
-    }
-
-    /**
-     * Immediately draws the illumination dot on this SurfaceView's surface.
-     */
-    void drawIlluminationDot(@NonNull RectF sensorRect) {
-        if (!mHasValidSurface) {
-            Log.e(TAG, "drawIlluminationDot | the surface is destroyed or was never created.");
-            return;
-        }
-        Canvas canvas = null;
-        try {
-            canvas = mHolder.lockCanvas();
-            canvas.drawOval(sensorRect, mSensorPaint);
-        } finally {
-            // Make sure the surface is never left in a bad state.
-            if (canvas != null) {
-                mHolder.unlockCanvasAndPost(canvas);
-            }
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.kt
index 9fbc458..75e6aa0 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.kt
@@ -22,26 +22,17 @@
 import android.graphics.PointF
 import android.graphics.RectF
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
-import android.os.Build
-import android.os.UserHandle
-import android.provider.Settings
 import android.util.AttributeSet
 import android.util.Log
 import android.view.MotionEvent
-import android.view.Surface
 import android.widget.FrameLayout
 import com.android.systemui.R
 import com.android.systemui.doze.DozeReceiver
-import com.android.systemui.biometrics.UdfpsHbmTypes.HbmType
 
 private const val TAG = "UdfpsView"
-private const val SETTING_HBM_TYPE = "com.android.systemui.biometrics.UdfpsSurfaceView.hbmType"
-@HbmType
-private const val DEFAULT_HBM_TYPE = UdfpsHbmTypes.LOCAL_HBM
 
 /**
- * A view containing 1) A SurfaceView for HBM, and 2) A normal drawable view for all other
- * animations.
+ * The main view group containing all UDFPS animations.
  */
 class UdfpsView(
     context: Context,
@@ -68,21 +59,6 @@
         com.android.internal.R.integer.config_udfps_illumination_transition_ms
     ).toLong()
 
-    @HbmType
-    private val hbmType = if (Build.IS_ENG || Build.IS_USERDEBUG) {
-        Settings.Secure.getIntForUser(
-            context.contentResolver,
-            SETTING_HBM_TYPE,
-            DEFAULT_HBM_TYPE,
-            UserHandle.USER_CURRENT
-        )
-    } else {
-        DEFAULT_HBM_TYPE
-    }
-
-    // Only used for UdfpsHbmTypes.GLOBAL_HBM.
-    private var ghbmView: UdfpsSurfaceView? = null
-
     /** View controller (can be different for enrollment, BiometricPrompt, Keyguard, etc.). */
     var animationViewController: UdfpsAnimationViewController<*>? = null
 
@@ -109,12 +85,6 @@
         return (animationViewController == null || !animationViewController!!.shouldPauseAuth())
     }
 
-    override fun onFinishInflate() {
-        if (hbmType == UdfpsHbmTypes.GLOBAL_HBM) {
-            ghbmView = findViewById(R.id.hbm_view)
-        }
-    }
-
     override fun dozeTimeTick() {
         animationViewController?.dozeTimeTick()
     }
@@ -180,24 +150,11 @@
     override fun startIllumination(onIlluminatedRunnable: Runnable?) {
         isIlluminationRequested = true
         animationViewController?.onIlluminationStarting()
-
-        val gView = ghbmView
-        if (gView != null) {
-            gView.setGhbmIlluminationListener(this::doIlluminate)
-            gView.visibility = VISIBLE
-            gView.startGhbmIllumination(onIlluminatedRunnable)
-        } else {
-            doIlluminate(null /* surface */, onIlluminatedRunnable)
-        }
+        doIlluminate(onIlluminatedRunnable)
     }
 
-    private fun doIlluminate(surface: Surface?, onIlluminatedRunnable: Runnable?) {
-        if (ghbmView != null && surface == null) {
-            Log.e(TAG, "doIlluminate | surface must be non-null for GHBM")
-        }
-
-        hbmProvider?.enableHbm(hbmType, surface) {
-            ghbmView?.drawIlluminationDot(sensorRect)
+    private fun doIlluminate(onIlluminatedRunnable: Runnable?) {
+        hbmProvider?.enableHbm() {
             if (onIlluminatedRunnable != null) {
                 // No framework API can reliably tell when a frame reaches the panel. A timeout
                 // is the safest solution.
@@ -211,10 +168,6 @@
     override fun stopIllumination() {
         isIlluminationRequested = false
         animationViewController?.onIlluminationStopped()
-        ghbmView?.let { view ->
-            view.setGhbmIlluminationListener(null)
-            view.visibility = INVISIBLE
-        }
         hbmProvider?.disableHbm(null /* onHbmDisabled */)
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
index 3ad5336..b7c4009 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
@@ -31,6 +31,8 @@
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.TimeInterpolator;
 import android.animation.ValueAnimator;
 import android.annotation.MainThread;
 import android.app.RemoteAction;
@@ -55,6 +57,7 @@
 import android.text.TextUtils;
 import android.util.DisplayMetrics;
 import android.util.Log;
+import android.util.MathUtils;
 import android.util.Size;
 import android.view.Display;
 import android.view.DisplayCutout;
@@ -68,6 +71,8 @@
 import android.view.WindowInsets;
 import android.view.WindowManager;
 import android.view.accessibility.AccessibilityManager;
+import android.view.animation.LinearInterpolator;
+import android.view.animation.PathInterpolator;
 import android.view.textclassifier.TextClassification;
 import android.view.textclassifier.TextClassificationManager;
 import android.view.textclassifier.TextClassifier;
@@ -119,6 +124,7 @@
 
     private final FrameLayout mContainer;
     private final DraggableConstraintLayout mView;
+    private final View mClipboardPreview;
     private final ImageView mImagePreview;
     private final TextView mTextPreview;
     private final View mPreviewBorder;
@@ -177,6 +183,7 @@
         mActionContainerBackground =
                 requireNonNull(mView.findViewById(R.id.actions_container_background));
         mActionContainer = requireNonNull(mView.findViewById(R.id.actions));
+        mClipboardPreview = requireNonNull(mView.findViewById(R.id.clipboard_preview));
         mImagePreview = requireNonNull(mView.findViewById(R.id.image_preview));
         mTextPreview = requireNonNull(mView.findViewById(R.id.text_preview));
         mPreviewBorder = requireNonNull(mView.findViewById(R.id.preview_border));
@@ -197,7 +204,7 @@
                     @Override
                     public void onAnimationStart(Animator animation) {
                         super.onAnimationStart(animation);
-                        mContainer.animate().alpha(0).start();
+                        mContainer.animate().alpha(0).setDuration(animation.getDuration()).start();
                     }
                 });
             }
@@ -294,6 +301,7 @@
                 animateOut();
             });
             mRemoteCopyChip.setAlpha(1f);
+            mActionContainerBackground.setVisibility(View.VISIBLE);
         } else {
             mRemoteCopyChip.setVisibility(View.GONE);
         }
@@ -410,6 +418,7 @@
     private void showEditableText(CharSequence text) {
         showTextPreview(text);
         mEditChip.setVisibility(View.VISIBLE);
+        mActionContainerBackground.setVisibility(View.VISIBLE);
         mEditChip.setAlpha(1f);
         mEditChip.setContentDescription(
                 mContext.getString(R.string.clipboard_edit_text_description));
@@ -422,6 +431,7 @@
         mTextPreview.setVisibility(View.GONE);
         mImagePreview.setVisibility(View.VISIBLE);
         mEditChip.setAlpha(1f);
+        mActionContainerBackground.setVisibility(View.VISIBLE);
         ContentResolver resolver = mContext.getContentResolver();
         try {
             int size = mContext.getResources().getDimensionPixelSize(R.dimen.overlay_x_scale);
@@ -453,36 +463,75 @@
     }
 
     private void animateIn() {
+        if (mAccessibilityManager.isEnabled()) {
+            mDismissButton.setVisibility(View.VISIBLE);
+        }
         getEnterAnimation().start();
     }
 
     private void animateOut() {
-        mView.dismiss();
+        Animator anim = getExitAnimation();
+        anim.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                super.onAnimationEnd(animation);
+                hideImmediate();
+            }
+        });
+        anim.start();
     }
 
-    private ValueAnimator getEnterAnimation() {
-        ValueAnimator anim = ValueAnimator.ofFloat(0, 1);
+    private Animator getEnterAnimation() {
+        TimeInterpolator linearInterpolator = new LinearInterpolator();
+        TimeInterpolator scaleInterpolator = new PathInterpolator(0, 0, 0, 1f);
+        AnimatorSet enterAnim = new AnimatorSet();
 
-        mContainer.setAlpha(0);
-        mDismissButton.setVisibility(View.GONE);
-        final View previewBorder = requireNonNull(mView.findViewById(R.id.preview_border));
-        final View actionBackground = requireNonNull(
-                mView.findViewById(R.id.actions_container_background));
-        mImagePreview.setVisibility(View.VISIBLE);
-        mActionContainerBackground.setVisibility(View.VISIBLE);
-        if (mAccessibilityManager.isEnabled()) {
-            mDismissButton.setVisibility(View.VISIBLE);
-        }
-
-        anim.addUpdateListener(animation -> {
+        ValueAnimator rootAnim = ValueAnimator.ofFloat(0, 1);
+        rootAnim.setInterpolator(linearInterpolator);
+        rootAnim.setDuration(66);
+        rootAnim.addUpdateListener(animation -> {
             mContainer.setAlpha(animation.getAnimatedFraction());
-            float scale = 0.6f + 0.4f * animation.getAnimatedFraction();
-            mView.setPivotY(mView.getHeight() - previewBorder.getHeight() / 2f);
-            mView.setPivotX(actionBackground.getWidth() / 2f);
-            mView.setScaleX(scale);
-            mView.setScaleY(scale);
         });
-        anim.addListener(new AnimatorListenerAdapter() {
+
+        ValueAnimator scaleAnim = ValueAnimator.ofFloat(0, 1);
+        scaleAnim.setInterpolator(scaleInterpolator);
+        scaleAnim.setDuration(333);
+        scaleAnim.addUpdateListener(animation -> {
+            float previewScale = MathUtils.lerp(.9f, 1f, animation.getAnimatedFraction());
+            mClipboardPreview.setScaleX(previewScale);
+            mClipboardPreview.setScaleY(previewScale);
+            mPreviewBorder.setScaleX(previewScale);
+            mPreviewBorder.setScaleY(previewScale);
+
+            float pivotX = mClipboardPreview.getWidth() / 2f + mClipboardPreview.getX();
+            mActionContainerBackground.setPivotX(pivotX - mActionContainerBackground.getX());
+            mActionContainer.setPivotX(pivotX - ((View) mActionContainer.getParent()).getX());
+            float actionsScaleX = MathUtils.lerp(.7f, 1f, animation.getAnimatedFraction());
+            float actionsScaleY = MathUtils.lerp(.9f, 1f, animation.getAnimatedFraction());
+            mActionContainer.setScaleX(actionsScaleX);
+            mActionContainer.setScaleY(actionsScaleY);
+            mActionContainerBackground.setScaleX(actionsScaleX);
+            mActionContainerBackground.setScaleY(actionsScaleY);
+        });
+
+        ValueAnimator alphaAnim = ValueAnimator.ofFloat(0, 1);
+        alphaAnim.setInterpolator(linearInterpolator);
+        alphaAnim.setDuration(283);
+        alphaAnim.addUpdateListener(animation -> {
+            float alpha = animation.getAnimatedFraction();
+            mClipboardPreview.setAlpha(alpha);
+            mPreviewBorder.setAlpha(alpha);
+            mDismissButton.setAlpha(alpha);
+            mActionContainer.setAlpha(alpha);
+        });
+
+        mActionContainer.setAlpha(0);
+        mPreviewBorder.setAlpha(0);
+        mClipboardPreview.setAlpha(0);
+        enterAnim.play(rootAnim).with(scaleAnim);
+        enterAnim.play(alphaAnim).after(50).after(rootAnim);
+
+        enterAnim.addListener(new AnimatorListenerAdapter() {
             @Override
             public void onAnimationEnd(Animator animation) {
                 super.onAnimationEnd(animation);
@@ -490,7 +539,56 @@
                 mTimeoutHandler.resetTimeout();
             }
         });
-        return anim;
+        return enterAnim;
+    }
+
+    private Animator getExitAnimation() {
+        TimeInterpolator linearInterpolator = new LinearInterpolator();
+        TimeInterpolator scaleInterpolator = new PathInterpolator(.3f, 0, 1f, 1f);
+        AnimatorSet exitAnim = new AnimatorSet();
+
+        ValueAnimator rootAnim = ValueAnimator.ofFloat(0, 1);
+        rootAnim.setInterpolator(linearInterpolator);
+        rootAnim.setDuration(100);
+        rootAnim.addUpdateListener(animation -> {
+            mContainer.setAlpha(1 - animation.getAnimatedFraction());
+        });
+
+        ValueAnimator scaleAnim = ValueAnimator.ofFloat(0, 1);
+        scaleAnim.setInterpolator(scaleInterpolator);
+        scaleAnim.setDuration(250);
+        scaleAnim.addUpdateListener(animation -> {
+            float previewScale = MathUtils.lerp(1f, .9f, animation.getAnimatedFraction());
+            mClipboardPreview.setScaleX(previewScale);
+            mClipboardPreview.setScaleY(previewScale);
+            mPreviewBorder.setScaleX(previewScale);
+            mPreviewBorder.setScaleY(previewScale);
+
+            float pivotX = mClipboardPreview.getWidth() / 2f + mClipboardPreview.getX();
+            mActionContainerBackground.setPivotX(pivotX - mActionContainerBackground.getX());
+            mActionContainer.setPivotX(pivotX - ((View) mActionContainer.getParent()).getX());
+            float actionScaleX = MathUtils.lerp(1f, .8f, animation.getAnimatedFraction());
+            float actionScaleY = MathUtils.lerp(1f, .9f, animation.getAnimatedFraction());
+            mActionContainer.setScaleX(actionScaleX);
+            mActionContainer.setScaleY(actionScaleY);
+            mActionContainerBackground.setScaleX(actionScaleX);
+            mActionContainerBackground.setScaleY(actionScaleY);
+        });
+
+        ValueAnimator alphaAnim = ValueAnimator.ofFloat(0, 1);
+        alphaAnim.setInterpolator(linearInterpolator);
+        alphaAnim.setDuration(166);
+        alphaAnim.addUpdateListener(animation -> {
+            float alpha = 1 - animation.getAnimatedFraction();
+            mClipboardPreview.setAlpha(alpha);
+            mPreviewBorder.setAlpha(alpha);
+            mDismissButton.setAlpha(alpha);
+            mActionContainer.setAlpha(alpha);
+        });
+
+        exitAnim.play(alphaAnim).with(scaleAnim);
+        exitAnim.play(rootAnim).after(150).after(alphaAnim);
+        return exitAnim;
     }
 
     private void hideImmediate() {
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java
index 5c99cd1..990f04b 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java
@@ -117,15 +117,23 @@
                         return false;
                     }
 
+                    // Don't set expansion for downward scroll when the bouncer is hidden.
+                    if (!mBouncerInitiallyShowing && (e1.getY() < e2.getY())) {
+                        return true;
+                    }
+
+                    // Don't set expansion for upward scroll when the bouncer is shown.
+                    if (mBouncerInitiallyShowing && (e1.getY() > e2.getY())) {
+                        return true;
+                    }
+
                     // For consistency, we adopt the expansion definition found in the
                     // PanelViewController. In this case, expansion refers to the view above the
                     // bouncer. As that view's expansion shrinks, the bouncer appears. The bouncer
                     // is fully hidden at full expansion (1) and fully visible when fully collapsed
                     // (0).
-                    final float dy = mBouncerInitiallyShowing ? e2.getY() - e1.getY()
-                            : e1.getY() - e2.getY();
-                    final float screenTravelPercentage = Math.max(0,
-                            dy / mCentralSurfaces.getDisplayHeight());
+                    final float screenTravelPercentage = Math.abs(e1.getY() - e2.getY())
+                            / mCentralSurfaces.getDisplayHeight();
                     setPanelExpansion(mBouncerInitiallyShowing
                             ? screenTravelPercentage : 1 - screenTravelPercentage);
                     return true;
diff --git a/packages/SystemUI/src/com/android/systemui/dump/DumpsysTableLogger.kt b/packages/SystemUI/src/com/android/systemui/dump/DumpsysTableLogger.kt
new file mode 100644
index 0000000..f7e6b98
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dump/DumpsysTableLogger.kt
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.dump
+
+import java.io.PrintWriter
+
+/**
+ * Utility for logging nice table data to be parsed (and pretty printed) in bugreports. The general
+ * idea here is to feed your nice, table-like data to this class, which embeds the schema and rows
+ * into the dumpsys, wrapped in a known start and stop tags. Later, one can build a simple parser
+ * and pretty-print this data in a table
+ *
+ * Note: Something should be said here about silently eating errors by filtering out malformed
+ * lines. Because this class is expected to be utilized only during a dumpsys, it doesn't feel
+ * most correct to throw an exception here (since an exception can often be the reason that this
+ * class is created). Because of this, [DumpsysTableLogger] will simply filter out invalid lines
+ * based solely on line length. This behavior might need to be revisited in the future.
+ *
+ * USAGE:
+ * Assuming we have some data that would be logged to dumpsys like so:
+ *
+ * ```
+ *      1: field1=val1, field2=val2..., fieldN=valN
+ *      //...
+ *      M: field1M=val1M, ..., fieldNM
+ * ```
+ *
+ * You can break the `field<n>` values out into a columns spec:
+ * ```
+ *      val cols = [field1, field2,...,fieldN]
+ * ```
+ * And then take all of the historical data lines (1 through M), and break them out into their own
+ * lists:
+ * ```
+ *      val rows = [
+ *          [field10, field20,..., fieldN0],
+ *          //...
+ *          [field1M, field2M,..., fieldNM]
+ *      ]
+ * ```
+ *
+ * Lastly, create a bugreport-unique section name, and use the table logger to write the data to
+ * dumpsys:
+ * ```
+ *      val logger = DumpsysTableLogger(uniqueName, cols, rows)
+ *      logger.printTableData(pw)
+ * ```
+ *
+ * The expected output in the dumpsys would be:
+ * ```
+ *      SystemUI TableSection START: <SectionName>
+ *      version 1
+ *      col1|col2|...|colN
+ *      field10|field20|...|fieldN0
+ *      //...
+ *      field1M|field2M|...|fieldNM
+ *      SystemUI TableSection END: <SectionName>
+ * ```
+ *
+ *  @param sectionName A name for the table data section. Should be unique in the bugreport
+ *  @param columns Definition for the columns of the table. This should be the same length as all
+ *      data rows
+ *  @param rows List of rows to be displayed in the table
+ */
+class DumpsysTableLogger(
+    private val sectionName: String,
+    private val columns: List<String>,
+    private val rows: List<Row>
+) {
+
+    fun printTableData(pw: PrintWriter) {
+        printSectionStart(pw)
+        printSchema(pw)
+        printData(pw)
+        printSectionEnd(pw)
+    }
+
+    private fun printSectionStart(pw: PrintWriter) {
+        pw.println(HEADER_PREFIX + sectionName)
+        pw.println("version $VERSION")
+    }
+
+    private fun printSectionEnd(pw: PrintWriter) {
+        pw.println(FOOTER_PREFIX + sectionName)
+    }
+
+    private fun printSchema(pw: PrintWriter) {
+        pw.println(columns.joinToString(separator = SEPARATOR))
+    }
+
+    private fun printData(pw: PrintWriter) {
+        val count = columns.size
+        rows
+            .filter { it.size == count }
+            .forEach { dataLine ->
+                pw.println(dataLine.joinToString(separator = SEPARATOR))
+        }
+    }
+}
+
+typealias Row = List<String>
+
+/**
+ * DO NOT CHANGE! (but if you must...)
+ *  1. Update the version number
+ *  2. Update any consumers to parse the new version
+ */
+private const val HEADER_PREFIX = "SystemUI TableSection START: "
+private const val FOOTER_PREFIX = "SystemUI TableSection END: "
+private const val SEPARATOR = "|" // TBD
+private const val VERSION = "1"
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java
index 0365241..e5dc0ec 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java
@@ -360,9 +360,7 @@
                 .getDimension(R.dimen.navigation_edge_action_drag_threshold);
         mSwipeProgressThreshold = context.getResources()
                 .getDimension(R.dimen.navigation_edge_action_progress_threshold);
-        if (mBackAnimation != null) {
-            mBackAnimation.setSwipeThresholds(mSwipeTriggerThreshold, mSwipeProgressThreshold);
-        }
+        initializeBackAnimation();
 
         setVisibility(GONE);
         Executor backgroundExecutor = Dependency.get(Dependency.BACKGROUND_EXECUTOR);
@@ -391,6 +389,13 @@
 
     public void setBackAnimation(BackAnimation backAnimation) {
         mBackAnimation = backAnimation;
+        initializeBackAnimation();
+    }
+
+    private void initializeBackAnimation() {
+        if (mBackAnimation != null) {
+            mBackAnimation.setSwipeThresholds(mSwipeTriggerThreshold, mSwipeProgressThreshold);
+        }
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
index 5d6bbae..4afd39e3 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
@@ -202,7 +202,7 @@
                         mActivityStarter
                                 .postStartActivityDismissingKeyguard(getLongClickIntent(), 0,
                                         controller);
-                    }, R.style.Theme_SystemUI_Dialog, false /* showProgressBarWhenEmpty */);
+                    }, R.style.Theme_SystemUI_Dialog_Cast, false /* showProgressBarWhenEmpty */);
             holder.init(dialog);
             SystemUIDialog.setShowForAllUsers(dialog, true);
             SystemUIDialog.registerDismissListener(dialog);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index d9a98b1..5585cde 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -323,7 +323,7 @@
         default void onBiometricError(@Modality int modality, int error, int vendorCode) {
         }
 
-        default void hideAuthenticationDialog() {
+        default void hideAuthenticationDialog(long requestId) {
         }
 
         /**
@@ -999,9 +999,11 @@
     }
 
     @Override
-    public void hideAuthenticationDialog() {
+    public void hideAuthenticationDialog(long requestId) {
         synchronized (mLock) {
-            mHandler.obtainMessage(MSG_BIOMETRIC_HIDE).sendToTarget();
+            final SomeArgs args = SomeArgs.obtain();
+            args.argl1 = requestId;
+            mHandler.obtainMessage(MSG_BIOMETRIC_HIDE, args).sendToTarget();
         }
     }
 
@@ -1508,11 +1510,14 @@
                     someArgs.recycle();
                     break;
                 }
-                case MSG_BIOMETRIC_HIDE:
+                case MSG_BIOMETRIC_HIDE: {
+                    final SomeArgs someArgs = (SomeArgs) msg.obj;
                     for (int i = 0; i < mCallbacks.size(); i++) {
-                        mCallbacks.get(i).hideAuthenticationDialog();
+                        mCallbacks.get(i).hideAuthenticationDialog(someArgs.argl1 /* requestId */);
                     }
+                    someArgs.recycle();
                     break;
+                }
                 case MSG_SET_BIOMETRICS_LISTENER:
                     for (int i = 0; i < mCallbacks.size(); i++) {
                         mCallbacks.get(i).setBiometicContextListener(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
index 93103e2..e026c19 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
@@ -205,7 +205,7 @@
             }
             mLastState = mState;
             mState = state;
-            mUpcomingState = state;
+            updateUpcomingState(mState);
             mUiEventLogger.log(StatusBarStateEvent.fromState(mState));
             Trace.instantForTrack(Trace.TRACE_TAG_APP, "UI Events", "StatusBarState " + tag);
             for (RankedListener rl : new ArrayList<>(mListeners)) {
@@ -223,8 +223,18 @@
 
     @Override
     public void setUpcomingState(int nextState) {
-        mUpcomingState = nextState;
-        recordHistoricalState(mUpcomingState /* newState */, mState /* lastState */, true);
+        recordHistoricalState(nextState /* newState */, mState /* lastState */, true);
+        updateUpcomingState(nextState);
+
+    }
+
+    private void updateUpcomingState(int upcomingState) {
+        if (mUpcomingState != upcomingState) {
+            mUpcomingState = upcomingState;
+            for (RankedListener rl : new ArrayList<>(mListeners)) {
+                rl.mListener.onUpcomingStateChanged(mUpcomingState);
+            }
+        }
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/ConnectivityState.kt b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/ConnectivityState.kt
index 9c3c10c..b66e175 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/ConnectivityState.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/ConnectivityState.kt
@@ -46,6 +46,34 @@
         }
     }
 
+    protected open fun tableColumns(): List<String> {
+        return listOf(
+            "connected",
+            "enabled",
+            "activityIn",
+            "activityOut",
+            "level",
+            "iconGroup",
+            "inetCondition",
+            "rssi",
+            "time")
+    }
+
+    protected open fun tableData(): List<String> {
+        return listOf(
+            connected,
+            enabled,
+            activityIn,
+            activityOut,
+            level,
+            iconGroup,
+            inetCondition,
+            rssi,
+            sSDF.format(time)).map {
+                it.toString()
+        }
+    }
+
     protected open fun copyFrom(other: ConnectivityState) {
         connected = other.connected
         enabled = other.enabled
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileSignalController.java
index 41d2b65..9d8667a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileSignalController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileSignalController.java
@@ -828,6 +828,8 @@
                     + (mMobileStatusHistoryIndex + STATUS_HISTORY_SIZE - i) + "): "
                     + mMobileStatusHistory[i & (STATUS_HISTORY_SIZE - 1)]);
         }
+
+        dumpTableData(pw);
     }
 
     /** Box for QS icon info */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileState.kt b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileState.kt
index 8a3b006..f20d206 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileState.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileState.kt
@@ -162,6 +162,52 @@
         builder.append("displayInfo=$telephonyDisplayInfo")
     }
 
+    override fun tableColumns(): List<String> {
+        val columns = listOf("dataSim",
+            "networkName",
+            "networkNameData",
+            "dataConnected",
+            "roaming",
+            "isDefault",
+            "isEmergency",
+            "airplaneMode",
+            "carrierNetworkChangeMode",
+            "userSetup",
+            "dataState",
+            "defaultDataOff",
+            "showQuickSettingsRatIcon",
+            "voiceServiceState",
+            "isInService",
+            "serviceState",
+            "signalStrength",
+            "displayInfo")
+
+        return super.tableColumns() + columns
+    }
+
+    override fun tableData(): List<String> {
+        val columns = listOf(dataSim,
+                networkName,
+                networkNameData,
+                dataConnected,
+                roaming,
+                isDefault,
+                isEmergency,
+                airplaneMode,
+                carrierNetworkChangeMode,
+                userSetup,
+                dataState,
+                defaultDataOff,
+                showQuickSettingsRatIcon(),
+                getVoiceServiceState(),
+                isInService(),
+                serviceState?.minLog() ?: "(null)",
+                signalStrength?.minLog() ?: "(null)",
+                telephonyDisplayInfo).map { it.toString() }
+
+        return super.tableData() + columns
+    }
+
     override fun equals(other: Any?): Boolean {
         if (this === other) return true
         if (javaClass != other?.javaClass) return false
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/SignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/SignalController.java
index cd20068..e2806a3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/SignalController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/SignalController.java
@@ -22,9 +22,12 @@
 import android.util.Log;
 
 import com.android.settingslib.SignalIcon.IconGroup;
+import com.android.systemui.dump.DumpsysTableLogger;
 
 import java.io.PrintWriter;
+import java.util.ArrayList;
 import java.util.BitSet;
+import java.util.List;
 
 
 /**
@@ -193,20 +196,68 @@
         pw.println("  - " + mTag + " -----");
         pw.println("  Current State: " + mCurrentState);
         if (RECORD_HISTORY) {
-            // Count up the states that actually contain time stamps, and only display those.
-            int size = 0;
-            for (int i = 0; i < HISTORY_SIZE; i++) {
-                if (mHistory[i].time != 0) size++;
-            }
-            // Print out the previous states in ordered number.
-            for (int i = mHistoryIndex + HISTORY_SIZE - 1;
-                    i >= mHistoryIndex + HISTORY_SIZE - size; i--) {
-                pw.println("  Previous State(" + (mHistoryIndex + HISTORY_SIZE - i) + "): "
-                        + mHistory[i & (HISTORY_SIZE - 1)]);
+            List<ConnectivityState> history = getOrderedHistoryExcludingCurrentState();
+            for (int i = 0; i < history.size(); i++) {
+                pw.println("  Previous State(" + (i + 1) + "): " + mHistory[i]);
             }
         }
     }
 
+    /**
+     * mHistory is a ring, so use this method to get the time-ordered (from youngest to oldest)
+     * list of historical states. Filters out any state whose `time` is `0`.
+     *
+     * For ease of compatibility, this list returns JUST the historical states, not the current
+     * state which has yet to be copied into the history
+     *
+     * @see #getOrderedHistory()
+     * @return historical states, ordered from newest to oldest
+     */
+    List<ConnectivityState> getOrderedHistoryExcludingCurrentState() {
+        ArrayList<ConnectivityState> history = new ArrayList<>();
+
+        // Count up the states that actually contain time stamps, and only display those.
+        int size = 0;
+        for (int i = 0; i < HISTORY_SIZE; i++) {
+            if (mHistory[i].time != 0) size++;
+        }
+        // Print out the previous states in ordered number.
+        for (int i = mHistoryIndex + HISTORY_SIZE - 1;
+                i >= mHistoryIndex + HISTORY_SIZE - size; i--) {
+            history.add(mHistory[i & (HISTORY_SIZE - 1)]);
+        }
+
+        return history;
+    }
+
+    /**
+     * Get the ordered history states, including the current yet-to-be-copied state. Useful for
+     * logging
+     *
+     * @see #getOrderedHistoryExcludingCurrentState()
+     * @return [currentState, historicalState...] array
+     */
+    List<ConnectivityState> getOrderedHistory() {
+        ArrayList<ConnectivityState> history = new ArrayList<>();
+        // Start with the current state
+        history.add(mCurrentState);
+        history.addAll(getOrderedHistoryExcludingCurrentState());
+        return history;
+    }
+
+    void dumpTableData(PrintWriter pw) {
+        List<List<String>> tableData = new ArrayList<List<String>>();
+        List<ConnectivityState> history = getOrderedHistory();
+        for (int i = 0; i < history.size(); i++) {
+            tableData.add(history.get(i).tableData());
+        }
+
+        DumpsysTableLogger logger =
+                new DumpsysTableLogger(mTag, mCurrentState.tableColumns(), tableData);
+
+        logger.printTableData(pw);
+    }
+
     final void notifyListeners() {
         notifyListeners(mCallbackHandler);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiSignalController.java
index b80df4a..a4589c8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiSignalController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiSignalController.java
@@ -241,6 +241,7 @@
     public void dump(PrintWriter pw) {
         super.dump(pw);
         mWifiTracker.dump(pw);
+        dumpTableData(pw);
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiState.kt b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiState.kt
index ac15f78..d32e349 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiState.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiState.kt
@@ -48,6 +48,30 @@
                 .append(",subId=").append(subId)
     }
 
+    override fun tableColumns(): List<String> {
+        val columns = listOf("ssid",
+                "isTransient",
+                "isDefault",
+                "statusLabel",
+                "isCarrierMerged",
+                "subId")
+
+        return super.tableColumns() + columns
+    }
+
+    override fun tableData(): List<String> {
+        val data = listOf(ssid,
+        isTransient,
+        isDefault,
+        statusLabel,
+        isCarrierMerged,
+        subId).map {
+            it.toString()
+        }
+
+        return super.tableData() + data
+    }
+
     override fun equals(other: Any?): Boolean {
         if (this === other) return true
         if (javaClass != other?.javaClass) 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 61e0fda..8271a88 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
@@ -427,6 +427,7 @@
     private boolean mInHeadsUpPinnedMode;
     private boolean mHeadsUpAnimatingAway;
     private int mStatusBarState;
+    private int mUpcomingStatusBarState;
     private int mCachedBackgroundColor;
     private boolean mHeadsUpGoingAwayAnimationsAllowed = true;
     private Runnable mReflingAndAnimateScroll = () -> {
@@ -690,7 +691,8 @@
                 mController.hasActiveClearableNotifications(ROWS_ALL);
         boolean showFooterView = (showDismissView || mController.getVisibleNotificationCount() > 0)
                 && mIsCurrentUserSetup  // see: b/193149550
-                && mStatusBarState != StatusBarState.KEYGUARD
+                && !onKeyguard()
+                && mUpcomingStatusBarState != StatusBarState.KEYGUARD
                 // quick settings don't affect notifications when not in full screen
                 && (mQsExpansionFraction != 1 || !mQsFullScreen)
                 && !mScreenOffAnimationController.shouldHideNotificationsFooter()
@@ -4960,6 +4962,13 @@
         updateDismissBehavior();
     }
 
+    void setUpcomingStatusBarState(int upcomingStatusBarState) {
+        mUpcomingStatusBarState = upcomingStatusBarState;
+        if (mUpcomingStatusBarState != mStatusBarState) {
+            updateFooter();
+        }
+    }
+
     void onStatePostChange(boolean fromShadeLocked) {
         boolean onKeyguard = onKeyguard();
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index 3e630cd..6d7c95f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -310,6 +310,11 @@
                 }
 
                 @Override
+                public void onUpcomingStateChanged(int newState) {
+                    mView.setUpcomingStatusBarState(newState);
+                }
+
+                @Override
                 public void onStatePostChange() {
                     mView.updateSensitiveness(mStatusBarStateController.goingToFullShade(),
                             mLockscreenUserManager.isAnyProfilePublicMode());
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
index fe08fb0..217a613 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
@@ -1215,7 +1215,8 @@
                 }
                 // Use broadcast instead of ShadeController, as this dialog may have started in
                 // another process and normal dagger bindings are not available
-                mBroadcastSender.closeSystemDialogs();
+                mBroadcastSender.sendBroadcastAsUser(
+                        new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS), UserHandle.CURRENT);
                 getContext().startActivityAsUser(
                         CreateUserActivity.createIntentForStart(getContext()),
                         mUserTracker.getUserHandle());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
index 483dbf5..666c9e4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
@@ -48,6 +48,7 @@
 import org.mockito.Mock
 import org.mockito.Mockito.anyInt
 import org.mockito.Mockito.eq
+import org.mockito.Mockito.never
 import org.mockito.Mockito.verify
 import org.mockito.junit.MockitoJUnit
 import org.mockito.Mockito.`when` as whenever
@@ -81,8 +82,29 @@
     }
 
     @Test
+    fun testNotifiesAnimatedIn() {
+        initializeContainer()
+        verify(callback).onDialogAnimatedIn()
+    }
+
+    @Test
+    fun testIgnoresAnimatedInWhenDismissed() {
+        val container = initializeContainer(addToView = false)
+        container.dismissFromSystemServer()
+        waitForIdleSync()
+
+        verify(callback, never()).onDialogAnimatedIn()
+
+        container.addToView()
+        waitForIdleSync()
+
+        // attaching the view resets the state and allows this to happen again
+        verify(callback).onDialogAnimatedIn()
+    }
+
+    @Test
     fun testActionAuthenticated_sendsDismissedAuthenticated() {
-        val container = initializeContainer(BiometricManager.Authenticators.BIOMETRIC_WEAK)
+        val container = initializeContainer()
         container.mBiometricCallback.onAction(
             AuthBiometricView.Callback.ACTION_AUTHENTICATED
         )
@@ -97,7 +119,7 @@
 
     @Test
     fun testActionUserCanceled_sendsDismissedUserCanceled() {
-        val container = initializeContainer(BiometricManager.Authenticators.BIOMETRIC_WEAK)
+        val container = initializeContainer()
         container.mBiometricCallback.onAction(
             AuthBiometricView.Callback.ACTION_USER_CANCELED
         )
@@ -115,7 +137,7 @@
 
     @Test
     fun testActionButtonNegative_sendsDismissedButtonNegative() {
-        val container = initializeContainer(BiometricManager.Authenticators.BIOMETRIC_WEAK)
+        val container = initializeContainer()
         container.mBiometricCallback.onAction(
             AuthBiometricView.Callback.ACTION_BUTTON_NEGATIVE
         )
@@ -141,7 +163,7 @@
 
     @Test
     fun testActionError_sendsDismissedError() {
-        val container = initializeContainer(BiometricManager.Authenticators.BIOMETRIC_WEAK)
+        val container = initializeContainer()
         authContainer!!.mBiometricCallback.onAction(
             AuthBiometricView.Callback.ACTION_ERROR
         )
@@ -183,7 +205,7 @@
 
     @Test
     fun testShowBiometricUI() {
-        val container = initializeContainer(BiometricManager.Authenticators.BIOMETRIC_WEAK)
+        val container = initializeContainer()
 
         waitForIdleSync()
 
@@ -252,7 +274,10 @@
         assertThat((layoutParams.fitInsetsTypes and WindowInsets.Type.ime()) == 0).isTrue()
     }
 
-    private fun initializeContainer(authenticators: Int): TestAuthContainerView {
+    private fun initializeContainer(
+        authenticators: Int = BiometricManager.Authenticators.BIOMETRIC_WEAK,
+        addToView: Boolean = true
+    ): TestAuthContainerView {
         val config = AuthContainerView.Config()
         config.mContext = mContext
         config.mCallback = callback
@@ -291,7 +316,11 @@
             lockPatternUtils,
             Handler(TestableLooper.get(this).looper)
         )
-        ViewUtils.attachView(authContainer)
+
+        if (addToView) {
+            authContainer!!.addToView()
+        }
+
         return authContainer!!
     }
 
@@ -316,6 +345,12 @@
         TestableLooper.get(this).processAllMessages()
         super.waitForIdleSync()
     }
+
+    private fun AuthContainerView.addToView() {
+        ViewUtils.attachView(this)
+        waitForIdleSync()
+        assertThat(isAttachedToWindow).isTrue()
+    }
 }
 
 private fun AuthContainerView.hasBiometricPrompt() =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
index 42c3c7f..190228d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
@@ -20,6 +20,8 @@
 import static android.hardware.biometrics.BiometricManager.Authenticators;
 import static android.hardware.biometrics.BiometricManager.BIOMETRIC_MULTI_SENSOR_FINGERPRINT_AND_FACE;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertNull;
 
@@ -104,6 +106,8 @@
 @SmallTest
 public class AuthControllerTest extends SysuiTestCase {
 
+    private static final long REQUEST_ID = 22;
+
     @Rule
     public final MockitoRule mMockitoRule = MockitoJUnit.rule();
 
@@ -173,6 +177,9 @@
         when(mDialog1.isAllowDeviceCredentials()).thenReturn(false);
         when(mDialog2.isAllowDeviceCredentials()).thenReturn(false);
 
+        when(mDialog1.getRequestId()).thenReturn(REQUEST_ID);
+        when(mDialog2.getRequestId()).thenReturn(REQUEST_ID);
+
         when(mFingerprintManager.isHardwareDetected()).thenReturn(true);
 
         final List<ComponentInfoInternal> componentInfo = new ArrayList<>();
@@ -482,7 +489,12 @@
     @Test
     public void testHideAuthenticationDialog_invokesDismissFromSystemServer() {
         showDialog(new int[] {1} /* sensorIds */, false /* credentialAllowed */);
-        mAuthController.hideAuthenticationDialog();
+
+        mAuthController.hideAuthenticationDialog(REQUEST_ID + 1);
+        verify(mDialog1, never()).dismissFromSystemServer();
+        assertThat(mAuthController.mCurrentDialog).isSameInstanceAs(mDialog1);
+
+        mAuthController.hideAuthenticationDialog(REQUEST_ID);
         verify(mDialog1).dismissFromSystemServer();
 
         // In this case, BiometricService sends the error to the client immediately, without
@@ -512,7 +524,7 @@
                 eq(BiometricPrompt.DISMISSED_REASON_CREDENTIAL_CONFIRMED),
                 AdditionalMatchers.aryEq(credentialAttestation));
 
-        mAuthController.hideAuthenticationDialog();
+        mAuthController.hideAuthenticationDialog(REQUEST_ID);
     }
 
     @Test
@@ -648,7 +660,7 @@
 
         verify(mDisplayManager).registerDisplayListener(any(), eq(mHandler));
 
-        mAuthController.hideAuthenticationDialog();
+        mAuthController.hideAuthenticationDialog(REQUEST_ID);
         verify(mDisplayManager).unregisterDisplayListener(any());
     }
 
@@ -704,7 +716,7 @@
                 0 /* userId */,
                 0 /* operationId */,
                 "testPackage",
-                1 /* requestId */,
+                REQUEST_ID,
                 BIOMETRIC_MULTI_SENSOR_FINGERPRINT_AND_FACE);
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsViewTest.kt
index 3d8d128..6d4cc4c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsViewTest.kt
@@ -23,7 +23,6 @@
 import android.testing.TestableLooper
 import android.testing.ViewUtils
 import android.view.LayoutInflater
-import android.view.Surface
 import androidx.test.filters.SmallTest
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
@@ -37,7 +36,6 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.Mock
-import org.mockito.Mockito.anyInt
 import org.mockito.Mockito.never
 import org.mockito.Mockito.nullable
 import org.mockito.Mockito.verify
@@ -148,7 +146,7 @@
         view.startIllumination(onDone)
 
         val illuminator = withArgCaptor<Runnable> {
-            verify(hbmProvider).enableHbm(anyInt(), nullable(Surface::class.java), capture())
+            verify(hbmProvider).enableHbm(capture())
         }
 
         assertThat(view.isIlluminationRequested).isTrue()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandlerTest.java
index 74cf497..a016a1d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandlerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandlerTest.java
@@ -169,7 +169,7 @@
      * Makes sure swiping up when bouncer initially showing doesn't change the expansion amount.
      */
     @Test
-    public void testSwipeUp_whenBouncerInitiallyShowing_keepsExpansionAtZero() {
+    public void testSwipeUp_whenBouncerInitiallyShowing_doesNotSetExpansion() {
         when(mCentralSurfaces.isBouncerShowing()).thenReturn(true);
 
         mTouchHandler.onSessionStart(mTouchSession);
@@ -191,21 +191,15 @@
         assertThat(gestureListener.onScroll(event1, event2, 0, distanceY))
                 .isTrue();
 
-        // Ensure only called once
-        verify(mStatusBarKeyguardViewManager)
+        verify(mStatusBarKeyguardViewManager, never())
                 .onPanelExpansionChanged(anyFloat(), anyBoolean(), anyBoolean());
-
-        // TODO(b/227348372): update the logic and also this test.
-        // Ensure the expansion is kept at 0.
-        verify(mStatusBarKeyguardViewManager).onPanelExpansionChanged(eq(0f), eq(false),
-                eq(true));
     }
 
     /**
      * Makes sure swiping down when bouncer initially hidden doesn't change the expansion amount.
      */
     @Test
-    public void testSwipeDown_whenBouncerInitiallyHidden_keepsExpansionAtOne() {
+    public void testSwipeDown_whenBouncerInitiallyHidden_doesNotSetExpansion() {
         mTouchHandler.onSessionStart(mTouchSession);
         ArgumentCaptor<GestureDetector.OnGestureListener> gestureListenerCaptor =
                 ArgumentCaptor.forClass(GestureDetector.OnGestureListener.class);
@@ -225,14 +219,8 @@
         assertThat(gestureListener.onScroll(event1, event2, 0, distanceY))
                 .isTrue();
 
-        // Ensure only called once
-        verify(mStatusBarKeyguardViewManager)
+        verify(mStatusBarKeyguardViewManager, never())
                 .onPanelExpansionChanged(anyFloat(), anyBoolean(), anyBoolean());
-
-        // TODO(b/227348372): update the logic and also this test.
-        // Ensure the expansion is kept at 1.
-        verify(mStatusBarKeyguardViewManager).onPanelExpansionChanged(eq(1f), eq(false),
-                eq(true));
     }
 
     /**
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dump/DumpsysTableLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/dump/DumpsysTableLoggerTest.kt
new file mode 100644
index 0000000..1d2afe4
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/dump/DumpsysTableLoggerTest.kt
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.dump
+
+import androidx.test.filters.SmallTest
+
+import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Truth.assertThat
+
+import org.junit.Assert.assertEquals
+import org.junit.Before
+import org.junit.Test
+
+import java.io.PrintWriter
+import java.io.StringWriter
+
+@SmallTest
+class DumpsysTableLoggerTest : SysuiTestCase() {
+    private val logger = DumpsysTableLogger(
+            TEST_SECTION_NAME,
+            TEST_COLUMNS,
+            TEST_DATA_VALID)
+
+    private val stringWriter = StringWriter()
+    private val printWriter = PrintWriter(stringWriter)
+
+    @Before
+    fun setup() {
+    }
+
+    @Test
+    fun testTableLogger_header() {
+        logger.printTableData(printWriter)
+        val lines = logLines(stringWriter)
+
+        val line1 = lines[0]
+
+        assertEquals("table logger header is incorrect",
+                HEADER_PREFIX + TEST_SECTION_NAME, line1)
+    }
+
+    @Test
+    fun testTableLogger_version() {
+        logger.printTableData(printWriter)
+        val lines = logLines(stringWriter)
+
+        val line2 = lines[1]
+
+        assertEquals("version probably shouldn't have changed",
+        "version $VERSION", line2)
+    }
+
+    @Test
+    fun testTableLogger_footer() {
+        logger.printTableData(printWriter)
+        val lines = logLines(stringWriter)
+
+        val footer = lines.last()
+        android.util.Log.d("evanevan", footer)
+        android.util.Log.d("evanevan", lines.toString())
+
+        assertEquals("table logger footer is incorrect",
+                FOOTER_PREFIX + TEST_SECTION_NAME, footer)
+    }
+
+    @Test
+    fun testTableLogger_data_length() {
+        logger.printTableData(printWriter)
+        val lines = logLines(stringWriter)
+
+        // Header is 2 lines long, plus a line for the column defs so data is lines[3..last()-1]
+        val data = lines.subList(3, lines.size - 1)
+        assertEquals(TEST_DATA_LENGTH, data.size)
+    }
+
+    @Test
+    fun testTableLogger_data_columns() {
+        logger.printTableData(printWriter)
+        val lines = logLines(stringWriter)
+
+        // Header is always 2 lines long so data is lines[2..last()-1]
+        val data = lines.subList(3, lines.size - 1)
+
+        data.forEach { dataLine ->
+            assertEquals(TEST_COLUMNS.size, dataLine.split(SEPARATOR).size)
+        }
+    }
+
+    @Test
+    fun testInvalidLinesAreFiltered() {
+        // GIVEN an invalid data row, by virtue of having an extra field
+        val invalidLine = List(TEST_COLUMNS.size) { col ->
+            "data${col}X"
+        } + "INVALID COLUMN"
+        val invalidData = TEST_DATA_VALID.toMutableList().also {
+            it.add(invalidLine)
+        }
+
+        // WHEN the table logger is created and asked to print the table
+        val tableLogger = DumpsysTableLogger(
+                TEST_SECTION_NAME,
+                TEST_COLUMNS,
+                invalidData)
+
+        tableLogger.printTableData(printWriter)
+
+        // THEN the invalid line is filtered out
+        val invalidString = invalidLine.joinToString(separator = SEPARATOR)
+        val logString = stringWriter.toString()
+
+        assertThat(logString).doesNotContain(invalidString)
+    }
+
+    private fun logLines(sw: StringWriter): List<String> {
+        return sw.toString().split("\n").filter { it.isNotBlank() }
+    }
+}
+
+// Copying these here from [DumpsysTableLogger] so that we catch any accidental versioning change
+private const val HEADER_PREFIX = "SystemUI TableSection START: "
+private const val FOOTER_PREFIX = "SystemUI TableSection END: "
+private const val SEPARATOR = "|" // TBD
+private const val VERSION = "1"
+
+const val TEST_SECTION_NAME = "TestTableSection"
+const val TEST_DATA_LENGTH = 5
+val TEST_COLUMNS = arrayListOf("col1", "col2", "col3")
+val TEST_DATA_VALID = List(TEST_DATA_LENGTH) { row ->
+    List(TEST_COLUMNS.size) { col ->
+        "data$col$row"
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
index 11f76a3..fc4d9c4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
@@ -475,9 +475,10 @@
 
     @Test
     public void testHideAuthenticationDialog() {
-        mCommandQueue.hideAuthenticationDialog();
+        final long id = 4;
+        mCommandQueue.hideAuthenticationDialog(id);
         waitForIdleSync();
-        verify(mCallbacks).hideAuthenticationDialog();
+        verify(mCallbacks).hideAuthenticationDialog(eq(id));
     }
 
     @Test
diff --git a/services/core/java/com/android/server/UiModeManagerService.java b/services/core/java/com/android/server/UiModeManagerService.java
index 2f84ec5..e68a0a6 100644
--- a/services/core/java/com/android/server/UiModeManagerService.java
+++ b/services/core/java/com/android/server/UiModeManagerService.java
@@ -38,6 +38,7 @@
 import android.app.AlarmManager;
 import android.app.IOnProjectionStateChangedListener;
 import android.app.IUiModeManager;
+import android.app.KeyguardManager;
 import android.app.Notification;
 import android.app.NotificationManager;
 import android.app.PendingIntent;
@@ -149,6 +150,8 @@
     private int mCarModeEnableFlags;
     private boolean mSetupWizardComplete;
 
+    // flag set by resource, whether to start dream immediately upon docking even if unlocked.
+    private boolean mStartDreamImmediatelyOnDock = true;
     // flag set by resource, whether to enable Car dock launch when starting car mode.
     private boolean mEnableCarDockLaunch = true;
     // flag set by resource, whether to lock UI mode to the default one or not.
@@ -173,6 +176,7 @@
     private ActivityTaskManagerInternal mActivityTaskManager;
     private AlarmManager mAlarmManager;
     private PowerManager mPowerManager;
+    private KeyguardManager mKeyguardManager;
 
     // In automatic scheduling, the user is able
     // to override the computed night mode until the two match
@@ -374,6 +378,7 @@
             synchronized (mLock) {
                 final Context context = getContext();
                 mSystemReady = true;
+                mKeyguardManager = context.getSystemService(KeyguardManager.class);
                 mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
                 mWakeLock = mPowerManager.newWakeLock(PowerManager.FULL_WAKE_LOCK, TAG);
                 mWindowManager = LocalServices.getService(WindowManagerInternal.class);
@@ -412,6 +417,8 @@
         verifySetupWizardCompleted();
 
         final Resources res = context.getResources();
+        mStartDreamImmediatelyOnDock = res.getBoolean(
+                com.android.internal.R.bool.config_startDreamImmediatelyOnDock);
         mNightMode = res.getInteger(
                 com.android.internal.R.integer.config_defaultNightMode);
         mDefaultUiModeType = res.getInteger(
@@ -1294,6 +1301,8 @@
             pw.print("  mDockState="); pw.print(mDockState);
             pw.print(" mLastBroadcastState="); pw.println(mLastBroadcastState);
 
+            pw.print(" mStartDreamImmediatelyOnDock="); pw.print(mStartDreamImmediatelyOnDock);
+
             pw.print("  mNightMode="); pw.print(mNightMode); pw.print(" (");
             pw.print(Shell.nightModeToStr(mNightMode, mNightModeCustomType)); pw.print(") ");
             pw.print(" mOverrideOn/Off="); pw.print(mOverrideNightModeOn);
@@ -1803,8 +1812,9 @@
         // Send the new configuration.
         applyConfigurationExternallyLocked();
 
-        // If we did not start a dock app, then start dreaming if supported.
-        if (category != null && !dockAppStarted) {
+        // If we did not start a dock app, then start dreaming if appropriate.
+        if (category != null && !dockAppStarted && (mStartDreamImmediatelyOnDock
+                || mKeyguardManager.isKeyguardLocked())) {
             Sandman.startDreamWhenDockedIfAppropriate(getContext());
         }
     }
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 2904b74..07c2818 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -208,7 +208,7 @@
 import android.appwidget.AppWidgetManager;
 import android.appwidget.AppWidgetManagerInternal;
 import android.compat.annotation.ChangeId;
-import android.compat.annotation.EnabledSince;
+import android.compat.annotation.Disabled;
 import android.content.AttributionSource;
 import android.content.AutofillOptions;
 import android.content.BroadcastReceiver;
@@ -461,14 +461,6 @@
     private static final String SYSTEM_PROPERTY_DEVICE_PROVISIONED =
             "persist.sys.device_provisioned";
 
-    /**
-     * Enabling this flag enforces the requirement for context registered receivers to use one of
-     * {@link Context#RECEIVER_EXPORTED} or {@link Context#RECEIVER_NOT_EXPORTED} for unprotected
-     * broadcasts
-     */
-    private static final boolean ENFORCE_DYNAMIC_RECEIVER_EXPLICIT_EXPORT =
-            SystemProperties.getBoolean("fw.enforce_dynamic_receiver_explicit_export", false);
-
     static final String TAG = TAG_WITH_CLASS_NAME ? "ActivityManagerService" : TAG_AM;
     static final String TAG_BACKUP = TAG + POSTFIX_BACKUP;
     private static final String TAG_BROADCAST = TAG + POSTFIX_BROADCAST;
@@ -585,7 +577,7 @@
      * unprotected broadcast in code.
      */
     @ChangeId
-    @EnabledSince(targetSdkVersion = Build.VERSION_CODES.TIRAMISU)
+    @Disabled
     private static final long DYNAMIC_RECEIVER_EXPLICIT_EXPORT_REQUIRED = 161145287L;
 
     /**
@@ -13070,25 +13062,14 @@
                     // sticky broadcast, no flag specified (flag isn't required)
                     flags |= Context.RECEIVER_EXPORTED;
                 } else if (requireExplicitFlagForDynamicReceivers && !explicitExportStateDefined) {
-                    if (ENFORCE_DYNAMIC_RECEIVER_EXPLICIT_EXPORT) {
-                        throw new SecurityException(
-                                callerPackage + ": Targeting T+ (version "
-                                        + Build.VERSION_CODES.TIRAMISU
-                                        + " and above) requires that one of RECEIVER_EXPORTED or "
-                                        + "RECEIVER_NOT_EXPORTED be specified when registering a "
-                                        + "receiver");
-                    } else {
-                        Slog.wtf(TAG,
-                                callerPackage + ": Targeting T+ (version "
-                                        + Build.VERSION_CODES.TIRAMISU
-                                        + " and above) requires that one of RECEIVER_EXPORTED or "
-                                        + "RECEIVER_NOT_EXPORTED be specified when registering a "
-                                        + "receiver");
-                        // Assume default behavior-- flag check is not enforced
-                        flags |= Context.RECEIVER_EXPORTED;
-                    }
-                } else if (!requireExplicitFlagForDynamicReceivers) {
-                    // Change is not enabled, thus not targeting T+. Assume exported.
+                    throw new SecurityException(
+                            callerPackage + ": One of RECEIVER_EXPORTED or "
+                                    + "RECEIVER_NOT_EXPORTED should be specified when a receiver "
+                                    + "isn't being registered exclusively for system broadcasts");
+                    // Assume default behavior-- flag check is not enforced
+                } else if (!requireExplicitFlagForDynamicReceivers && (
+                        (flags & Context.RECEIVER_NOT_EXPORTED) == 0)) {
+                    // Change is not enabled, assume exported unless otherwise specified.
                     flags |= Context.RECEIVER_EXPORTED;
                 }
             } else if ((flags & Context.RECEIVER_NOT_EXPORTED) == 0) {
@@ -17234,17 +17215,17 @@
         }
 
         @Override
-        public boolean isUidCurrentlyInstrumented(int uid) {
+        public int getInstrumentationSourceUid(int uid) {
             synchronized (mProcLock) {
                 for (int i = mActiveInstrumentation.size() - 1; i >= 0; i--) {
                     ActiveInstrumentation activeInst = mActiveInstrumentation.get(i);
                     if (!activeInst.mFinished && activeInst.mTargetInfo != null
                             && activeInst.mTargetInfo.uid == uid) {
-                        return true;
+                        return activeInst.mSourceUid;
                     }
                 }
             }
-            return false;
+            return INVALID_UID;
         }
 
         @Override
diff --git a/services/core/java/com/android/server/am/AppFGSTracker.java b/services/core/java/com/android/server/am/AppFGSTracker.java
index f8378c3..ddd2764 100644
--- a/services/core/java/com/android/server/am/AppFGSTracker.java
+++ b/services/core/java/com/android/server/am/AppFGSTracker.java
@@ -52,6 +52,7 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.os.SomeArgs;
 import com.android.server.am.AppFGSTracker.AppFGSPolicy;
 import com.android.server.am.AppFGSTracker.PackageDurations;
 import com.android.server.am.AppRestrictionController.TrackerType;
@@ -112,9 +113,14 @@
 
     @Override
     public void onForegroundServiceNotificationUpdated(String packageName, int uid,
-            int foregroundId) {
-        mHandler.obtainMessage(MyHandler.MSG_FOREGROUND_SERVICES_NOTIFICATION_UPDATED,
-                uid, foregroundId, packageName).sendToTarget();
+            int foregroundId, boolean canceling) {
+        final SomeArgs args = SomeArgs.obtain();
+        args.argi1 = uid;
+        args.argi2 = foregroundId;
+        args.arg1 = packageName;
+        args.arg2 = canceling ? Boolean.TRUE : Boolean.FALSE;
+        mHandler.obtainMessage(MyHandler.MSG_FOREGROUND_SERVICES_NOTIFICATION_UPDATED, args)
+                .sendToTarget();
     }
 
     private static class MyHandler extends Handler {
@@ -149,8 +155,10 @@
                             (String) msg.obj, msg.arg1, msg.arg2);
                     break;
                 case MSG_FOREGROUND_SERVICES_NOTIFICATION_UPDATED:
+                    final SomeArgs args = (SomeArgs) msg.obj;
                     mTracker.handleForegroundServiceNotificationUpdated(
-                            (String) msg.obj, msg.arg1, msg.arg2);
+                            (String) args.arg1, args.argi1, args.argi2, (Boolean) args.arg2);
+                    args.recycle();
                     break;
                 case MSG_CHECK_LONG_RUNNING_FGS:
                     mTracker.checkLongRunningFgs();
@@ -241,18 +249,18 @@
     }
 
     private void handleForegroundServiceNotificationUpdated(String packageName, int uid,
-            int notificationId) {
+            int notificationId, boolean canceling) {
         synchronized (mLock) {
             SparseBooleanArray notificationIDs = mFGSNotificationIDs.get(uid, packageName);
-            if (notificationId > 0) {
+            if (!canceling) {
                 if (notificationIDs == null) {
                     notificationIDs = new SparseBooleanArray();
                     mFGSNotificationIDs.put(uid, packageName, notificationIDs);
                 }
                 notificationIDs.put(notificationId, false);
-            } else if (notificationId < 0) {
+            } else {
                 if (notificationIDs != null) {
-                    final int indexOfKey = notificationIDs.indexOfKey(-notificationId);
+                    final int indexOfKey = notificationIDs.indexOfKey(notificationId);
                     if (indexOfKey >= 0) {
                         final boolean wasVisible = notificationIDs.valueAt(indexOfKey);
                         notificationIDs.removeAt(indexOfKey);
diff --git a/services/core/java/com/android/server/am/AppRestrictionController.java b/services/core/java/com/android/server/am/AppRestrictionController.java
index 708bd16..6f74359 100644
--- a/services/core/java/com/android/server/am/AppRestrictionController.java
+++ b/services/core/java/com/android/server/am/AppRestrictionController.java
@@ -1975,6 +1975,9 @@
         }
         try {
             final PackageInfo pkg = pm.getPackageInfo(packageName, 0 /* flags */);
+            if (pkg == null || pkg.applicationInfo == null) {
+                return FrameworkStatsLog.APP_BACKGROUND_RESTRICTIONS_INFO__TARGET_SDK__SDK_UNKNOWN;
+            }
             final int targetSdk = pkg.applicationInfo.targetSdkVersion;
             if (targetSdk < Build.VERSION_CODES.S) {
                 return FrameworkStatsLog.APP_BACKGROUND_RESTRICTIONS_INFO__TARGET_SDK__SDK_PRE_S;
diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java
index 639f56c..5a55b8b 100644
--- a/services/core/java/com/android/server/am/ServiceRecord.java
+++ b/services/core/java/com/android/server/am/ServiceRecord.java
@@ -1112,7 +1112,7 @@
                         foregroundNoti = localForegroundNoti; // save it for amending next time
 
                         signalForegroundServiceNotification(packageName, appInfo.uid,
-                                localForegroundId);
+                                localForegroundId, false /* canceling */);
 
                     } catch (RuntimeException e) {
                         Slog.w(TAG, "Error showing notification for service", e);
@@ -1147,17 +1147,18 @@
                 } catch (RuntimeException e) {
                     Slog.w(TAG, "Error canceling notification for service", e);
                 }
-                signalForegroundServiceNotification(packageName, appInfo.uid, -localForegroundId);
+                signalForegroundServiceNotification(packageName, appInfo.uid, localForegroundId,
+                        true /* canceling */);
             }
         });
     }
 
     private void signalForegroundServiceNotification(String packageName, int uid,
-            int foregroundId) {
+            int foregroundId, boolean canceling) {
         synchronized (ams) {
             for (int i = ams.mForegroundServiceStateListeners.size() - 1; i >= 0; i--) {
                 ams.mForegroundServiceStateListeners.get(i).onForegroundServiceNotificationUpdated(
-                        packageName, appInfo.uid, foregroundId);
+                        packageName, appInfo.uid, foregroundId, canceling);
             }
         }
     }
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index 361629b..752e17e 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -2367,7 +2367,8 @@
         ActivityManagerInternal ami = LocalServices.getService(ActivityManagerInternal.class);
         boolean isSelfRequest = (filter & FILTER_BY_UID) != 0 && uid == Binder.getCallingUid();
         if (!isSelfRequest) {
-            boolean isCallerInstrumented = ami.isUidCurrentlyInstrumented(Binder.getCallingUid());
+            boolean isCallerInstrumented =
+                    ami.getInstrumentationSourceUid(Binder.getCallingUid()) != Process.INVALID_UID;
             boolean isCallerSystem = Binder.getCallingPid() == Process.myPid();
             boolean isCallerPermissionController;
             try {
@@ -6894,7 +6895,8 @@
     @Override
     public @Nullable RuntimeAppOpAccessMessage collectRuntimeAppOpAccessMessage() {
         ActivityManagerInternal ami = LocalServices.getService(ActivityManagerInternal.class);
-        boolean isCallerInstrumented = ami.isUidCurrentlyInstrumented(Binder.getCallingUid());
+        boolean isCallerInstrumented =
+                ami.getInstrumentationSourceUid(Binder.getCallingUid()) != Process.INVALID_UID;
         boolean isCallerSystem = Binder.getCallingPid() == Process.myPid();
         if (!isCallerSystem && !isCallerInstrumented) {
             return null;
diff --git a/services/core/java/com/android/server/biometrics/AuthSession.java b/services/core/java/com/android/server/biometrics/AuthSession.java
index bf69284..cc49f07 100644
--- a/services/core/java/com/android/server/biometrics/AuthSession.java
+++ b/services/core/java/com/android/server/biometrics/AuthSession.java
@@ -462,7 +462,7 @@
                     mState = STATE_SHOWING_DEVICE_CREDENTIAL;
                     mStatusBarService.onBiometricError(modality, error, vendorCode);
                 } else if (error == BiometricConstants.BIOMETRIC_ERROR_CANCELED) {
-                    mStatusBarService.hideAuthenticationDialog();
+                    mStatusBarService.hideAuthenticationDialog(mRequestId);
                     // TODO: If multiple authenticators are simultaneously running, this will
                     // need to be modified. Send the error to the client here, instead of doing
                     // a round trip to SystemUI.
@@ -480,7 +480,7 @@
                 // the client and clean up. The only error we should get here is
                 // ERROR_CANCELED due to another client kicking us out.
                 mClientReceiver.onError(modality, error, vendorCode);
-                mStatusBarService.hideAuthenticationDialog();
+                mStatusBarService.hideAuthenticationDialog(mRequestId);
                 return true;
             }
 
@@ -489,7 +489,7 @@
                 break;
 
             case STATE_CLIENT_DIED_CANCELLING:
-                mStatusBarService.hideAuthenticationDialog();
+                mStatusBarService.hideAuthenticationDialog(mRequestId);
                 return true;
 
             default:
@@ -665,7 +665,7 @@
                     cancelAllSensors();
                     return false;
                 default:
-                    mStatusBarService.hideAuthenticationDialog();
+                    mStatusBarService.hideAuthenticationDialog(mRequestId);
                     return true;
             }
         } catch (RemoteException e) {
@@ -832,7 +832,7 @@
                         BiometricConstants.BIOMETRIC_ERROR_CANCELED,
                         0 /* vendorCode */
                 );
-                mStatusBarService.hideAuthenticationDialog();
+                mStatusBarService.hideAuthenticationDialog(mRequestId);
                 return true;
             } catch (RemoteException e) {
                 Slog.e(TAG, "Remote exception", e);
diff --git a/services/core/java/com/android/server/display/DisplayModeDirector.java b/services/core/java/com/android/server/display/DisplayModeDirector.java
index efd2f6f..ac72b17 100644
--- a/services/core/java/com/android/server/display/DisplayModeDirector.java
+++ b/services/core/java/com/android/server/display/DisplayModeDirector.java
@@ -2098,7 +2098,6 @@
 
     private class UdfpsObserver extends IUdfpsHbmListener.Stub {
         private final SparseBooleanArray mLocalHbmEnabled = new SparseBooleanArray();
-        private final SparseBooleanArray mGlobalHbmEnabled = new SparseBooleanArray();
 
         public void observe() {
             StatusBarManagerInternal statusBar =
@@ -2109,39 +2108,27 @@
         }
 
         @Override
-        public void onHbmEnabled(int hbmType, int displayId) {
+        public void onHbmEnabled(int displayId) {
             synchronized (mLock) {
-                updateHbmStateLocked(hbmType, displayId, true /*enabled*/);
+                updateHbmStateLocked(displayId, true /*enabled*/);
             }
         }
 
         @Override
-        public void onHbmDisabled(int hbmType, int displayId) {
+        public void onHbmDisabled(int displayId) {
             synchronized (mLock) {
-                updateHbmStateLocked(hbmType, displayId, false /*enabled*/);
+                updateHbmStateLocked(displayId, false /*enabled*/);
             }
         }
 
-        private void updateHbmStateLocked(int hbmType, int displayId, boolean enabled) {
-            switch (hbmType) {
-                case UdfpsObserver.LOCAL_HBM:
-                    mLocalHbmEnabled.put(displayId, enabled);
-                    break;
-                case UdfpsObserver.GLOBAL_HBM:
-                    mGlobalHbmEnabled.put(displayId, enabled);
-                    break;
-                default:
-                    Slog.w(TAG, "Unknown HBM type reported. Ignoring.");
-                    return;
-            }
+        private void updateHbmStateLocked(int displayId, boolean enabled) {
+            mLocalHbmEnabled.put(displayId, enabled);
             updateVoteLocked(displayId);
         }
 
         private void updateVoteLocked(int displayId) {
             final Vote vote;
-            if (mGlobalHbmEnabled.get(displayId)) {
-                vote = Vote.forRefreshRates(60f, 60f);
-            } else if (mLocalHbmEnabled.get(displayId)) {
+            if (mLocalHbmEnabled.get(displayId)) {
                 Display.Mode[] modes = mSupportedModesByDisplay.get(displayId);
                 float maxRefreshRate = 0f;
                 for (Display.Mode mode : modes) {
@@ -2165,13 +2152,6 @@
                 final String enabled = mLocalHbmEnabled.valueAt(i) ? "enabled" : "disabled";
                 pw.println("      Display " + displayId + ": " + enabled);
             }
-            pw.println("    mGlobalHbmEnabled: ");
-            for (int i = 0; i < mGlobalHbmEnabled.size(); i++) {
-                final int displayId = mGlobalHbmEnabled.keyAt(i);
-                final String enabled = mGlobalHbmEnabled.valueAt(i) ? "enabled" : "disabled";
-                pw.println("      Display " + displayId + ": " + enabled);
-            }
-
         }
     }
 
diff --git a/services/core/java/com/android/server/hdmi/AbsoluteVolumeAudioStatusAction.java b/services/core/java/com/android/server/hdmi/AbsoluteVolumeAudioStatusAction.java
new file mode 100644
index 0000000..d7563e0
--- /dev/null
+++ b/services/core/java/com/android/server/hdmi/AbsoluteVolumeAudioStatusAction.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.hdmi;
+
+/**
+ * Action to query and track the audio status of the System Audio device when enabling or using
+ * Absolute Volume Control. Must be removed when AVC is disabled. Performs two main functions:
+ * 1. When enabling AVC: queries the starting audio status of the System Audio device and
+ *    enables the feature upon receiving a response.
+ * 2. While AVC is enabled: monitors <Report Audio Status> messages from the System Audio device and
+ *    notifies AudioService if the audio status changes.
+ */
+final class AbsoluteVolumeAudioStatusAction extends HdmiCecFeatureAction {
+    private static final String TAG = "AbsoluteVolumeAudioStatusAction";
+
+    private int mInitialAudioStatusRetriesLeft = 2;
+
+    private static final int STATE_WAIT_FOR_INITIAL_AUDIO_STATUS = 1;
+    private static final int STATE_MONITOR_AUDIO_STATUS = 2;
+
+    private final int mTargetAddress;
+
+    private AudioStatus mLastAudioStatus;
+
+    AbsoluteVolumeAudioStatusAction(HdmiCecLocalDevice source, int targetAddress) {
+        super(source);
+        mTargetAddress = targetAddress;
+    }
+
+    @Override
+    boolean start() {
+        mState = STATE_WAIT_FOR_INITIAL_AUDIO_STATUS;
+        sendGiveAudioStatus();
+        return true;
+    }
+
+    void updateVolume(int volumeIndex) {
+        mLastAudioStatus = new AudioStatus(volumeIndex, mLastAudioStatus.getMute());
+    }
+
+    private void sendGiveAudioStatus() {
+        addTimer(mState, HdmiConfig.TIMEOUT_MS);
+        sendCommand(HdmiCecMessageBuilder.buildGiveAudioStatus(getSourceAddress(), mTargetAddress));
+    }
+
+    @Override
+    boolean processCommand(HdmiCecMessage cmd) {
+        switch (cmd.getOpcode()) {
+            case Constants.MESSAGE_REPORT_AUDIO_STATUS:
+                return handleReportAudioStatus(cmd);
+        }
+
+        return false;
+    }
+
+    private boolean handleReportAudioStatus(HdmiCecMessage cmd) {
+        if (mTargetAddress != cmd.getSource() || cmd.getParams().length == 0) {
+            return false;
+        }
+
+        boolean mute = HdmiUtils.isAudioStatusMute(cmd);
+        int volume = HdmiUtils.getAudioStatusVolume(cmd);
+        AudioStatus audioStatus = new AudioStatus(volume, mute);
+        if (mState == STATE_WAIT_FOR_INITIAL_AUDIO_STATUS) {
+            localDevice().getService().enableAbsoluteVolumeControl(audioStatus);
+            mState = STATE_MONITOR_AUDIO_STATUS;
+        } else if (mState == STATE_MONITOR_AUDIO_STATUS) {
+            if (audioStatus.getVolume() != mLastAudioStatus.getVolume()) {
+                localDevice().getService().notifyAvcVolumeChange(audioStatus.getVolume());
+            }
+            if (audioStatus.getMute() != mLastAudioStatus.getMute()) {
+                localDevice().getService().notifyAvcMuteChange(audioStatus.getMute());
+            }
+        }
+        mLastAudioStatus = audioStatus;
+
+        return true;
+    }
+
+    @Override
+    void handleTimerEvent(int state) {
+        if (mState != state) {
+            return;
+        } else if (mInitialAudioStatusRetriesLeft > 0) {
+            mInitialAudioStatusRetriesLeft--;
+            sendGiveAudioStatus();
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/hdmi/AudioDeviceVolumeManagerWrapper.java b/services/core/java/com/android/server/hdmi/AudioDeviceVolumeManagerWrapper.java
new file mode 100644
index 0000000..438c1ea
--- /dev/null
+++ b/services/core/java/com/android/server/hdmi/AudioDeviceVolumeManagerWrapper.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.hdmi;
+
+import android.annotation.CallbackExecutor;
+import android.annotation.NonNull;
+import android.content.Context;
+import android.media.AudioDeviceAttributes;
+import android.media.AudioDeviceVolumeManager;
+import android.media.VolumeInfo;
+
+import java.util.concurrent.Executor;
+
+/**
+ * Wrapper for {@link AudioDeviceVolumeManager}. Creates an instance of the class and directly
+ * passes method calls to that instance.
+ */
+public class AudioDeviceVolumeManagerWrapper
+        implements AudioDeviceVolumeManagerWrapperInterface {
+
+    private static final String TAG = "AudioDeviceVolumeManagerWrapper";
+
+    private final AudioDeviceVolumeManager mAudioDeviceVolumeManager;
+
+    public AudioDeviceVolumeManagerWrapper(Context context) {
+        mAudioDeviceVolumeManager = new AudioDeviceVolumeManager(context);
+    }
+
+    @Override
+    public void addOnDeviceVolumeBehaviorChangedListener(
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull AudioDeviceVolumeManager.OnDeviceVolumeBehaviorChangedListener listener)
+            throws SecurityException {
+        mAudioDeviceVolumeManager.addOnDeviceVolumeBehaviorChangedListener(executor, listener);
+    }
+
+    @Override
+    public void removeOnDeviceVolumeBehaviorChangedListener(
+            @NonNull AudioDeviceVolumeManager.OnDeviceVolumeBehaviorChangedListener listener) {
+        mAudioDeviceVolumeManager.removeOnDeviceVolumeBehaviorChangedListener(listener);
+    }
+
+    @Override
+    public void setDeviceAbsoluteVolumeBehavior(
+            @NonNull AudioDeviceAttributes device,
+            @NonNull VolumeInfo volume,
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull AudioDeviceVolumeManager.OnAudioDeviceVolumeChangedListener vclistener,
+            boolean handlesVolumeAdjustment) {
+        mAudioDeviceVolumeManager.setDeviceAbsoluteVolumeBehavior(device, volume, executor,
+                vclistener, handlesVolumeAdjustment);
+    }
+}
diff --git a/services/core/java/com/android/server/hdmi/AudioDeviceVolumeManagerWrapperInterface.java b/services/core/java/com/android/server/hdmi/AudioDeviceVolumeManagerWrapperInterface.java
new file mode 100644
index 0000000..1a1d4c1
--- /dev/null
+++ b/services/core/java/com/android/server/hdmi/AudioDeviceVolumeManagerWrapperInterface.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.hdmi;
+
+import static android.media.AudioDeviceVolumeManager.OnAudioDeviceVolumeChangedListener;
+import static android.media.AudioDeviceVolumeManager.OnDeviceVolumeBehaviorChangedListener;
+
+import android.annotation.CallbackExecutor;
+import android.annotation.NonNull;
+import android.media.AudioDeviceAttributes;
+import android.media.AudioDeviceVolumeManager;
+import android.media.VolumeInfo;
+
+import java.util.concurrent.Executor;
+
+/**
+ * Interface with the methods from {@link AudioDeviceVolumeManager} used by the HDMI framework.
+ * Allows the class to be faked for tests.
+ */
+public interface AudioDeviceVolumeManagerWrapperInterface {
+
+    /**
+     * Wrapper for {@link AudioDeviceVolumeManager#addOnDeviceVolumeBehaviorChangedListener(
+     * Executor, OnDeviceVolumeBehaviorChangedListener)}
+     */
+    void addOnDeviceVolumeBehaviorChangedListener(
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull AudioDeviceVolumeManager.OnDeviceVolumeBehaviorChangedListener listener);
+
+    /**
+     * Wrapper for {@link AudioDeviceVolumeManager#removeOnDeviceVolumeBehaviorChangedListener(
+     * OnDeviceVolumeBehaviorChangedListener)}
+     */
+    void removeOnDeviceVolumeBehaviorChangedListener(
+            @NonNull AudioDeviceVolumeManager.OnDeviceVolumeBehaviorChangedListener listener);
+
+    /**
+     * Wrapper for {@link AudioDeviceVolumeManager#setDeviceAbsoluteVolumeBehavior(
+     * AudioDeviceAttributes, VolumeInfo, Executor, OnAudioDeviceVolumeChangedListener, boolean)}
+     */
+    void setDeviceAbsoluteVolumeBehavior(
+            @NonNull AudioDeviceAttributes device,
+            @NonNull VolumeInfo volume,
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull AudioDeviceVolumeManager.OnAudioDeviceVolumeChangedListener vclistener,
+            boolean handlesVolumeAdjustment);
+}
diff --git a/services/core/java/com/android/server/hdmi/AudioStatus.java b/services/core/java/com/android/server/hdmi/AudioStatus.java
new file mode 100644
index 0000000..a884ffb
--- /dev/null
+++ b/services/core/java/com/android/server/hdmi/AudioStatus.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.hdmi;
+
+import android.annotation.Nullable;
+
+import java.util.Objects;
+
+/**
+ * Immutable representation of the information in the [Audio Status] operand:
+ * volume status (0 <= N <= 100) and mute status (muted or unmuted).
+ */
+public class AudioStatus {
+    public static final int MAX_VOLUME = 100;
+    public static final int MIN_VOLUME = 0;
+
+    int mVolume;
+    boolean mMute;
+
+    public AudioStatus(int volume, boolean mute) {
+        mVolume = volume;
+        mMute = mute;
+    }
+
+    public int getVolume() {
+        return mVolume;
+    }
+
+    public boolean getMute() {
+        return mMute;
+    }
+
+    @Override
+    public boolean equals(@Nullable Object obj) {
+        if (!(obj instanceof AudioStatus)) {
+            return false;
+        }
+
+        AudioStatus other = (AudioStatus) obj;
+        return mVolume == other.mVolume
+                && mMute == other.mMute;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mVolume, mMute);
+    }
+
+    @Override
+    public String toString() {
+        return "AudioStatus mVolume:" + mVolume + " mMute:" + mMute;
+    }
+}
diff --git a/services/core/java/com/android/server/hdmi/Constants.java b/services/core/java/com/android/server/hdmi/Constants.java
index 751f2db..157057d 100644
--- a/services/core/java/com/android/server/hdmi/Constants.java
+++ b/services/core/java/com/android/server/hdmi/Constants.java
@@ -118,6 +118,7 @@
             MESSAGE_SYSTEM_AUDIO_MODE_REQUEST,
             MESSAGE_GIVE_AUDIO_STATUS,
             MESSAGE_SET_SYSTEM_AUDIO_MODE,
+            MESSAGE_SET_AUDIO_VOLUME_LEVEL,
             MESSAGE_REPORT_AUDIO_STATUS,
             MESSAGE_GIVE_SYSTEM_AUDIO_MODE_STATUS,
             MESSAGE_SYSTEM_AUDIO_MODE_STATUS,
@@ -197,9 +198,9 @@
     static final int MESSAGE_SYSTEM_AUDIO_MODE_REQUEST = 0x70;
     static final int MESSAGE_GIVE_AUDIO_STATUS = 0x71;
     static final int MESSAGE_SET_SYSTEM_AUDIO_MODE = 0x72;
+    static final int MESSAGE_SET_AUDIO_VOLUME_LEVEL = 0x73;
     static final int MESSAGE_REPORT_AUDIO_STATUS = 0x7A;
     static final int MESSAGE_GIVE_SYSTEM_AUDIO_MODE_STATUS = 0x7D;
-    static final int MESSAGE_SET_AUDIO_VOLUME_LEVEL = 0x73;
     static final int MESSAGE_SYSTEM_AUDIO_MODE_STATUS = 0x7E;
     static final int MESSAGE_ROUTING_CHANGE = 0x80;
     static final int MESSAGE_ROUTING_INFORMATION = 0x81;
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
index 26a1613..fb2d2ee 100755
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
@@ -303,6 +303,13 @@
         if (dispatchMessageToAction(message)) {
             return Constants.HANDLED;
         }
+
+        // If a message type has its own class, all valid messages of that type
+        // will be represented by an instance of that class.
+        if (message instanceof SetAudioVolumeLevelMessage) {
+            return handleSetAudioVolumeLevel((SetAudioVolumeLevelMessage) message);
+        }
+
         switch (message.getOpcode()) {
             case Constants.MESSAGE_ACTIVE_SOURCE:
                 return handleActiveSource(message);
@@ -637,6 +644,11 @@
         return Constants.NOT_HANDLED;
     }
 
+    @Constants.HandleMessageResult
+    protected int handleSetAudioVolumeLevel(SetAudioVolumeLevelMessage message) {
+        return Constants.NOT_HANDLED;
+    }
+
     @Constants.RcProfile
     protected abstract int getRcProfile();
 
@@ -1002,6 +1014,57 @@
         action.start();
     }
 
+    void addAvcAudioStatusAction(int targetAddress) {
+        if (!hasAction(AbsoluteVolumeAudioStatusAction.class)) {
+            addAndStartAction(new AbsoluteVolumeAudioStatusAction(this, targetAddress));
+        }
+    }
+
+    void removeAvcAudioStatusAction() {
+        removeAction(AbsoluteVolumeAudioStatusAction.class);
+    }
+
+    void updateAvcVolume(int volumeIndex) {
+        for (AbsoluteVolumeAudioStatusAction action :
+                getActions(AbsoluteVolumeAudioStatusAction.class)) {
+            action.updateVolume(volumeIndex);
+        }
+    }
+
+    /**
+     * Determines whether {@code targetAddress} supports <Set Audio Volume Level>. Does two things
+     * in parallel: send <Give Features> (to get <Report Features> in response),
+     * and send <Set Audio Volume Level> (to see if it gets a <Feature Abort> in response).
+     */
+    @ServiceThreadOnly
+    void queryAvcSupport(int targetAddress) {
+        assertRunOnServiceThread();
+
+        // Send <Give Features> if using CEC 2.0 or above.
+        if (mService.getCecVersion() >= HdmiControlManager.HDMI_CEC_VERSION_2_0) {
+            synchronized (mLock) {
+                mService.sendCecCommand(HdmiCecMessageBuilder.buildGiveFeatures(
+                        getDeviceInfo().getLogicalAddress(), targetAddress));
+            }
+        }
+
+        // If we don't already have a {@link SetAudioVolumeLevelDiscoveryAction} for the target
+        // device, start one.
+        List<SetAudioVolumeLevelDiscoveryAction> savlDiscoveryActions =
+                getActions(SetAudioVolumeLevelDiscoveryAction.class);
+        if (savlDiscoveryActions.stream().noneMatch(a -> a.getTargetAddress() == targetAddress)) {
+            addAndStartAction(new SetAudioVolumeLevelDiscoveryAction(this, targetAddress,
+                    new IHdmiControlCallback.Stub() {
+                            @Override
+                            public void onComplete(int result) {
+                                if (result == HdmiControlManager.RESULT_SUCCESS) {
+                                    getService().checkAndUpdateAbsoluteVolumeControlState();
+                                }
+                            }
+                        }));
+        }
+    }
+
     @ServiceThreadOnly
     void startQueuedActions() {
         assertRunOnServiceThread();
@@ -1205,6 +1268,9 @@
      */
     protected void disableDevice(
             boolean initiatedByCec, final PendingActionClearedCallback originalCallback) {
+        removeAction(AbsoluteVolumeAudioStatusAction.class);
+        removeAction(SetAudioVolumeLevelDiscoveryAction.class);
+
         mPendingActionClearedCallback =
                 new PendingActionClearedCallback() {
                     @Override
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java
index 90b4f76..c0c0202 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java
@@ -307,6 +307,7 @@
     protected void disableDevice(boolean initiatedByCec, PendingActionClearedCallback callback) {
         removeAction(OneTouchPlayAction.class);
         removeAction(DevicePowerStatusAction.class);
+        removeAction(AbsoluteVolumeAudioStatusAction.class);
 
         super.disableDevice(initiatedByCec, callback);
     }
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
index 9212fb6..1ea1457 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
@@ -1166,6 +1166,19 @@
         return Constants.HANDLED;
     }
 
+    @Override
+    @Constants.HandleMessageResult
+    protected int handleSetAudioVolumeLevel(SetAudioVolumeLevelMessage message) {
+        // <Set Audio Volume Level> should only be sent to the System Audio device, so we don't
+        // handle it when System Audio Mode is enabled.
+        if (mService.isSystemAudioActivated()) {
+            return Constants.ABORT_NOT_IN_CORRECT_MODE;
+        } else {
+            mService.setStreamMusicVolume(message.getAudioVolumeLevel(), 0);
+            return Constants.HANDLED;
+        }
+    }
+
     void announceOneTouchRecordResult(int recorderAddress, int result) {
         mService.invokeOneTouchRecordResult(recorderAddress, result);
     }
@@ -1202,6 +1215,13 @@
         return mService.getHdmiCecNetwork().getSafeCecDeviceInfo(Constants.ADDR_AUDIO_SYSTEM);
     }
 
+    /**
+     * Returns the audio output device used for System Audio Mode.
+     */
+    AudioDeviceAttributes getSystemAudioOutputDevice() {
+        return HdmiControlService.AUDIO_OUTPUT_DEVICE_HDMI_ARC;
+    }
+
 
     @ServiceThreadOnly
     void handleRemoveActiveRoutingPath(int path) {
@@ -1296,6 +1316,7 @@
         removeAction(OneTouchRecordAction.class);
         removeAction(TimerRecordingAction.class);
         removeAction(NewDeviceAction.class);
+        removeAction(AbsoluteVolumeAudioStatusAction.class);
 
         disableSystemAudioIfExist();
         disableArcIfExist();
@@ -1318,7 +1339,6 @@
         removeAction(SystemAudioActionFromAvr.class);
         removeAction(SystemAudioActionFromTv.class);
         removeAction(SystemAudioAutoInitiationAction.class);
-        removeAction(SystemAudioStatusAction.class);
         removeAction(VolumeControlAction.class);
 
         if (!mService.isControlEnabled()) {
@@ -1589,6 +1609,7 @@
         return DeviceFeatures.NO_FEATURES_SUPPORTED.toBuilder()
                 .setRecordTvScreenSupport(FEATURE_SUPPORTED)
                 .setArcTxSupport(hasArcPort ? FEATURE_SUPPORTED : FEATURE_NOT_SUPPORTED)
+                .setSetAudioVolumeLevelSupport(FEATURE_SUPPORTED)
                 .build();
     }
 
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecMessage.java b/services/core/java/com/android/server/hdmi/HdmiCecMessage.java
index 290cae5..2b84225 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecMessage.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecMessage.java
@@ -262,6 +262,8 @@
                 return "Give Audio Status";
             case Constants.MESSAGE_SET_SYSTEM_AUDIO_MODE:
                 return "Set System Audio Mode";
+            case Constants.MESSAGE_SET_AUDIO_VOLUME_LEVEL:
+                return "Set Audio Volume Level";
             case Constants.MESSAGE_REPORT_AUDIO_STATUS:
                 return "Report Audio Status";
             case Constants.MESSAGE_GIVE_SYSTEM_AUDIO_MODE_STATUS:
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecNetwork.java b/services/core/java/com/android/server/hdmi/HdmiCecNetwork.java
index 8b6d16a..caaf800 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecNetwork.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecNetwork.java
@@ -259,6 +259,7 @@
             // The addition of a local device should not notify listeners
             return;
         }
+        mHdmiControlService.checkAndUpdateAbsoluteVolumeControlState();
         if (info.getPhysicalAddress() == HdmiDeviceInfo.PATH_INVALID) {
             // Don't notify listeners of devices that haven't reported their physical address yet
             return;
@@ -383,7 +384,7 @@
     final void removeCecDevice(HdmiCecLocalDevice localDevice, int address) {
         assertRunOnServiceThread();
         HdmiDeviceInfo info = removeDeviceInfo(HdmiDeviceInfo.idForCecDevice(address));
-
+        mHdmiControlService.checkAndUpdateAbsoluteVolumeControlState();
         localDevice.mCecMessageCache.flushMessagesFrom(address);
         if (info.getPhysicalAddress() == HdmiDeviceInfo.PATH_INVALID) {
             // Don't notify listeners of devices that haven't reported their physical address yet
@@ -586,6 +587,8 @@
                 .build();
 
         updateCecDevice(newDeviceInfo);
+
+        mHdmiControlService.checkAndUpdateAbsoluteVolumeControlState();
     }
 
     @ServiceThreadOnly
@@ -617,6 +620,8 @@
                     )
                     .build();
             updateCecDevice(newDeviceInfo);
+
+            mHdmiControlService.checkAndUpdateAbsoluteVolumeControlState();
         }
     }
 
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index 12380ab..9824b4e 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -30,6 +30,7 @@
 import static com.android.server.power.ShutdownThread.SHUTDOWN_ACTION_PROPERTY;
 
 import android.annotation.IntDef;
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.BroadcastReceiver;
 import android.content.ContentResolver;
@@ -38,6 +39,7 @@
 import android.content.IntentFilter;
 import android.database.ContentObserver;
 import android.hardware.display.DisplayManager;
+import android.hardware.hdmi.DeviceFeatures;
 import android.hardware.hdmi.HdmiControlManager;
 import android.hardware.hdmi.HdmiDeviceInfo;
 import android.hardware.hdmi.HdmiHotplugEvent;
@@ -56,7 +58,12 @@
 import android.hardware.hdmi.IHdmiVendorCommandListener;
 import android.hardware.tv.cec.V1_0.OptionKey;
 import android.hardware.tv.cec.V1_0.SendMessageResult;
+import android.media.AudioAttributes;
+import android.media.AudioDeviceAttributes;
+import android.media.AudioDeviceInfo;
+import android.media.AudioDeviceVolumeManager;
 import android.media.AudioManager;
+import android.media.VolumeInfo;
 import android.media.session.MediaController;
 import android.media.session.MediaSessionManager;
 import android.media.tv.TvInputManager;
@@ -83,6 +90,7 @@
 import android.util.Slog;
 import android.util.SparseArray;
 import android.view.Display;
+import android.view.KeyEvent;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
@@ -101,6 +109,7 @@
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
@@ -202,6 +211,27 @@
     public @interface WakeReason {
     }
 
+    @VisibleForTesting
+    static final AudioDeviceAttributes AUDIO_OUTPUT_DEVICE_HDMI = new AudioDeviceAttributes(
+            AudioDeviceAttributes.ROLE_OUTPUT, AudioDeviceInfo.TYPE_HDMI, "");
+    @VisibleForTesting
+    static final AudioDeviceAttributes AUDIO_OUTPUT_DEVICE_HDMI_ARC =
+            new AudioDeviceAttributes(AudioDeviceAttributes.ROLE_OUTPUT,
+                    AudioDeviceInfo.TYPE_HDMI_ARC, "");
+    static final AudioDeviceAttributes AUDIO_OUTPUT_DEVICE_HDMI_EARC =
+            new AudioDeviceAttributes(AudioDeviceAttributes.ROLE_OUTPUT,
+                    AudioDeviceInfo.TYPE_HDMI_EARC, "");
+
+    // Audio output devices used for Absolute Volume Control
+    private static final List<AudioDeviceAttributes> AVC_AUDIO_OUTPUT_DEVICES =
+            Collections.unmodifiableList(Arrays.asList(AUDIO_OUTPUT_DEVICE_HDMI,
+                    AUDIO_OUTPUT_DEVICE_HDMI_ARC, AUDIO_OUTPUT_DEVICE_HDMI_EARC));
+
+    // AudioAttributes for STREAM_MUSIC
+    @VisibleForTesting
+    static final AudioAttributes STREAM_MUSIC_ATTRIBUTES =
+            new AudioAttributes.Builder().setLegacyStreamType(AudioManager.STREAM_MUSIC).build();
+
     private final Executor mServiceThreadExecutor = new Executor() {
         @Override
         public void execute(Runnable r) {
@@ -209,6 +239,10 @@
         }
     };
 
+    Executor getServiceThreadExecutor() {
+        return mServiceThreadExecutor;
+    }
+
     // Logical address of the active source.
     @GuardedBy("mLock")
     protected final ActiveSource mActiveSource = new ActiveSource();
@@ -222,6 +256,13 @@
     @HdmiControlManager.VolumeControl
     private int mHdmiCecVolumeControl;
 
+    // Caches the volume behaviors of all audio output devices in AVC_AUDIO_OUTPUT_DEVICES.
+    @GuardedBy("mLock")
+    private Map<AudioDeviceAttributes, Integer> mAudioDeviceVolumeBehaviors = new HashMap<>();
+
+    // Maximum volume of AudioManager.STREAM_MUSIC. Set upon gaining access to system services.
+    private int mStreamMusicMaxVolume;
+
     // Make sure HdmiCecConfig is instantiated and the XMLs are read.
     private HdmiCecConfig mHdmiCecConfig;
 
@@ -411,6 +452,12 @@
     private PowerManagerInternalWrapper mPowerManagerInternal;
 
     @Nullable
+    private AudioManager mAudioManager;
+
+    @Nullable
+    private AudioDeviceVolumeManagerWrapperInterface mAudioDeviceVolumeManager;
+
+    @Nullable
     private Looper mIoLooper;
 
     @Nullable
@@ -439,11 +486,21 @@
 
     private final SelectRequestBuffer mSelectRequestBuffer = new SelectRequestBuffer();
 
-    @VisibleForTesting HdmiControlService(Context context, List<Integer> deviceTypes) {
+    /**
+     * Constructor for testing.
+     *
+     * It's critical to use a fake AudioDeviceVolumeManager because a normally instantiated
+     * AudioDeviceVolumeManager can access the "real" AudioService on the DUT.
+     *
+     * @see FakeAudioDeviceVolumeManagerWrapper
+     */
+    @VisibleForTesting HdmiControlService(Context context, List<Integer> deviceTypes,
+            AudioDeviceVolumeManagerWrapperInterface audioDeviceVolumeManager) {
         super(context);
         mLocalDevices = deviceTypes;
         mSettingsObserver = new SettingsObserver(mHandler);
         mHdmiCecConfig = new HdmiCecConfig(context);
+        mAudioDeviceVolumeManager = audioDeviceVolumeManager;
     }
 
     public HdmiControlService(Context context) {
@@ -744,6 +801,14 @@
                     Context.TV_INPUT_SERVICE);
             mPowerManager = new PowerManagerWrapper(getContext());
             mPowerManagerInternal = new PowerManagerInternalWrapper();
+            mAudioManager = (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE);
+            mStreamMusicMaxVolume = getAudioManager().getStreamMaxVolume(AudioManager.STREAM_MUSIC);
+            if (mAudioDeviceVolumeManager == null) {
+                mAudioDeviceVolumeManager =
+                        new AudioDeviceVolumeManagerWrapper(getContext());
+            }
+            getAudioDeviceVolumeManager().addOnDeviceVolumeBehaviorChangedListener(
+                    mServiceThreadExecutor, this::onDeviceVolumeBehaviorChanged);
         } else if (phase == SystemService.PHASE_BOOT_COMPLETED) {
             runOnServiceThread(this::bootCompleted);
         }
@@ -2419,6 +2484,7 @@
             pw.println("mPowerStatus: " + mPowerStatusController.getPowerStatus());
             pw.println("mIsCecAvailable: " + mIsCecAvailable);
             pw.println("mCecVersion: " + mCecVersion);
+            pw.println("mIsAbsoluteVolumeControlEnabled: " + isAbsoluteVolumeControlEnabled());
 
             // System settings
             pw.println("System_settings:");
@@ -2578,6 +2644,7 @@
             @HdmiControlManager.VolumeControl int hdmiCecVolumeControl) {
         mHdmiCecVolumeControl = hdmiCecVolumeControl;
         announceHdmiCecVolumeControlFeatureChange(hdmiCecVolumeControl);
+        runOnServiceThread(this::checkAndUpdateAbsoluteVolumeControlState);
     }
 
     // Get the source address to send out commands to devices connected to the current device
@@ -3086,15 +3153,17 @@
     private void announceHdmiCecVolumeControlFeatureChange(
             @HdmiControlManager.VolumeControl int hdmiCecVolumeControl) {
         assertRunOnServiceThread();
-        mHdmiCecVolumeControlFeatureListenerRecords.broadcast(listener -> {
-            try {
-                listener.onHdmiCecVolumeControlFeature(hdmiCecVolumeControl);
-            } catch (RemoteException e) {
-                Slog.e(TAG,
-                        "Failed to report HdmiControlVolumeControlStatusChange: "
-                                + hdmiCecVolumeControl);
-            }
-        });
+        synchronized (mLock) {
+            mHdmiCecVolumeControlFeatureListenerRecords.broadcast(listener -> {
+                try {
+                    listener.onHdmiCecVolumeControlFeature(hdmiCecVolumeControl);
+                } catch (RemoteException e) {
+                    Slog.e(TAG,
+                            "Failed to report HdmiControlVolumeControlStatusChange: "
+                                    + hdmiCecVolumeControl);
+                }
+            });
+        }
     }
 
     public HdmiCecLocalDeviceTv tv() {
@@ -3131,8 +3200,20 @@
                 HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
     }
 
+    /**
+     * Returns null before the boot phase {@link SystemService#PHASE_SYSTEM_SERVICES_READY}.
+     */
+    @Nullable
     AudioManager getAudioManager() {
-        return (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE);
+        return mAudioManager;
+    }
+
+    /**
+     * Returns null before the boot phase {@link SystemService#PHASE_SYSTEM_SERVICES_READY}.
+     */
+    @Nullable
+    private AudioDeviceVolumeManagerWrapperInterface getAudioDeviceVolumeManager() {
+        return mAudioDeviceVolumeManager;
     }
 
     boolean isControlEnabled() {
@@ -3486,6 +3567,7 @@
         synchronized (mLock) {
             mSystemAudioActivated = on;
         }
+        runOnServiceThread(this::checkAndUpdateAbsoluteVolumeControlState);
     }
 
     @ServiceThreadOnly
@@ -3599,6 +3681,8 @@
             device.addActiveSourceHistoryItem(new ActiveSource(logicalAddress, physicalAddress),
                     deviceIsActiveSource, caller);
         }
+
+        runOnServiceThread(this::checkAndUpdateAbsoluteVolumeControlState);
     }
 
     // This method should only be called when the device can be the active source
@@ -3791,4 +3875,332 @@
             Slog.e(TAG, "Failed to report setting change", e);
         }
     }
+
+    /**
+     * Listener for changes to the volume behavior of an audio output device. Caches the
+     * volume behavior of devices used for Absolute Volume Control.
+     */
+    @VisibleForTesting
+    @ServiceThreadOnly
+    void onDeviceVolumeBehaviorChanged(AudioDeviceAttributes device, int volumeBehavior) {
+        assertRunOnServiceThread();
+        if (AVC_AUDIO_OUTPUT_DEVICES.contains(device)) {
+            synchronized (mLock) {
+                mAudioDeviceVolumeBehaviors.put(device, volumeBehavior);
+            }
+            checkAndUpdateAbsoluteVolumeControlState();
+        }
+    }
+
+    /**
+     * Wrapper for {@link AudioManager#getDeviceVolumeBehavior} that takes advantage of cached
+     * results for the volume behaviors of HDMI audio devices.
+     */
+    @AudioManager.DeviceVolumeBehavior
+    private int getDeviceVolumeBehavior(AudioDeviceAttributes device) {
+        if (AVC_AUDIO_OUTPUT_DEVICES.contains(device)) {
+            synchronized (mLock) {
+                if (mAudioDeviceVolumeBehaviors.containsKey(device)) {
+                    return mAudioDeviceVolumeBehaviors.get(device);
+                }
+            }
+        }
+        return getAudioManager().getDeviceVolumeBehavior(device);
+    }
+
+    /**
+     * Returns whether Absolute Volume Control is enabled or not. This is determined by the
+     * volume behavior of the relevant HDMI audio output device(s) for this device's type.
+     */
+    public boolean isAbsoluteVolumeControlEnabled() {
+        if (!isTvDevice() && !isPlaybackDevice()) {
+            return false;
+        }
+        AudioDeviceAttributes avcAudioOutputDevice = getAvcAudioOutputDevice();
+        if (avcAudioOutputDevice == null) {
+            return false;
+        }
+        return getDeviceVolumeBehavior(avcAudioOutputDevice)
+                    == AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE;
+    }
+
+    private AudioDeviceAttributes getAvcAudioOutputDevice() {
+        if (isTvDevice()) {
+            return tv().getSystemAudioOutputDevice();
+        } else if (isPlaybackDevice()) {
+            return AUDIO_OUTPUT_DEVICE_HDMI;
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * Checks the conditions for Absolute Volume Control (AVC), and enables or disables the feature
+     * if necessary. AVC is enabled precisely when a specific audio output device
+     * (HDMI for playback devices, and HDMI_ARC or HDMI_EARC for TVs) is using absolute volume
+     * behavior.
+     *
+     * AVC must be enabled on a Playback device or TV precisely when it is playing
+     * audio on an external device (the System Audio device) that supports the feature.
+     * This reduces to these conditions:
+     *
+     * 1. If the System Audio Device is an Audio System: System Audio Mode is active
+     * 2. Our HDMI audio output device is using full volume behavior
+     * 3. CEC volume is enabled
+     * 4. The System Audio device supports AVC (i.e. it supports <Set Audio Volume Level>)
+     *
+     * If not all of these conditions are met, this method disables AVC if necessary.
+     *
+     * If all of these conditions are met, this method starts an action to query the System Audio
+     * device's audio status, which enables AVC upon obtaining the audio status.
+     */
+    @ServiceThreadOnly
+    void checkAndUpdateAbsoluteVolumeControlState() {
+        assertRunOnServiceThread();
+
+        // Can't enable or disable AVC before we have access to system services
+        if (getAudioManager() == null) {
+            return;
+        }
+
+        HdmiCecLocalDevice localCecDevice;
+        if (isTvDevice() && tv() != null) {
+            localCecDevice = tv();
+            // Condition 1: TVs need System Audio Mode to be active
+            // (Doesn't apply to Playback Devices, where if SAM isn't active, we assume the
+            // TV is the System Audio Device instead.)
+            if (!isSystemAudioActivated()) {
+                disableAbsoluteVolumeControl();
+                return;
+            }
+        } else if (isPlaybackDevice() && playback() != null) {
+            localCecDevice = playback();
+        } else {
+            // Either this device type doesn't support AVC, or it hasn't fully initialized yet
+            return;
+        }
+
+        HdmiDeviceInfo systemAudioDeviceInfo = getHdmiCecNetwork().getSafeCecDeviceInfo(
+                localCecDevice.findAudioReceiverAddress());
+        @AudioManager.DeviceVolumeBehavior int currentVolumeBehavior =
+                        getDeviceVolumeBehavior(getAvcAudioOutputDevice());
+
+        // Condition 2: Already using full or absolute volume behavior
+        boolean alreadyUsingFullOrAbsoluteVolume =
+                currentVolumeBehavior == AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL
+                        || currentVolumeBehavior == AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE;
+        // Condition 3: CEC volume is enabled
+        boolean cecVolumeEnabled =
+                getHdmiCecVolumeControl() == HdmiControlManager.VOLUME_CONTROL_ENABLED;
+
+        if (!cecVolumeEnabled || !alreadyUsingFullOrAbsoluteVolume) {
+            disableAbsoluteVolumeControl();
+            return;
+        }
+
+        // Check for safety: if the System Audio device is a candidate for AVC, we should already
+        // have received messages from it to trigger the other conditions.
+        if (systemAudioDeviceInfo == null) {
+            disableAbsoluteVolumeControl();
+            return;
+        }
+        // Condition 4: The System Audio device supports AVC (i.e. <Set Audio Volume Level>).
+        switch (systemAudioDeviceInfo.getDeviceFeatures().getSetAudioVolumeLevelSupport()) {
+            case DeviceFeatures.FEATURE_SUPPORTED:
+                if (!isAbsoluteVolumeControlEnabled()) {
+                    // Start an action that will call {@link #enableAbsoluteVolumeControl}
+                    // once the System Audio device sends <Report Audio Status>
+                    localCecDevice.addAvcAudioStatusAction(
+                            systemAudioDeviceInfo.getLogicalAddress());
+                }
+                return;
+            case DeviceFeatures.FEATURE_NOT_SUPPORTED:
+                disableAbsoluteVolumeControl();
+                return;
+            case DeviceFeatures.FEATURE_SUPPORT_UNKNOWN:
+                disableAbsoluteVolumeControl();
+                localCecDevice.queryAvcSupport(systemAudioDeviceInfo.getLogicalAddress());
+                return;
+            default:
+                return;
+        }
+    }
+
+    private void disableAbsoluteVolumeControl() {
+        if (isPlaybackDevice()) {
+            playback().removeAvcAudioStatusAction();
+        } else if (isTvDevice()) {
+            tv().removeAvcAudioStatusAction();
+        }
+        AudioDeviceAttributes device = getAvcAudioOutputDevice();
+        if (getDeviceVolumeBehavior(device) == AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE) {
+            getAudioManager().setDeviceVolumeBehavior(device,
+                    AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL);
+        }
+    }
+
+    /**
+     * Enables Absolute Volume Control. Should only be called when all the conditions for
+     * AVC are met (see {@link #checkAndUpdateAbsoluteVolumeControlState}).
+     * @param audioStatus The initial audio status to set the audio output device to
+     */
+    void enableAbsoluteVolumeControl(AudioStatus audioStatus) {
+        HdmiCecLocalDevice localDevice = isPlaybackDevice() ? playback() : tv();
+        HdmiDeviceInfo systemAudioDevice = getHdmiCecNetwork().getDeviceInfo(
+                localDevice.findAudioReceiverAddress());
+        VolumeInfo volumeInfo = new VolumeInfo.Builder(AudioManager.STREAM_MUSIC)
+                .setMuted(audioStatus.getMute())
+                .setVolumeIndex(audioStatus.getVolume())
+                .setMaxVolumeIndex(AudioStatus.MAX_VOLUME)
+                .setMinVolumeIndex(AudioStatus.MIN_VOLUME)
+                .build();
+        mAbsoluteVolumeChangedListener = new AbsoluteVolumeChangedListener(
+                localDevice, systemAudioDevice);
+
+        // AudioService sets the volume of the stream and device based on the input VolumeInfo
+        // when enabling absolute volume behavior, but not the mute state
+        notifyAvcMuteChange(audioStatus.getMute());
+        getAudioDeviceVolumeManager().setDeviceAbsoluteVolumeBehavior(
+                getAvcAudioOutputDevice(), volumeInfo, mServiceThreadExecutor,
+                mAbsoluteVolumeChangedListener, true);
+    }
+
+    private AbsoluteVolumeChangedListener mAbsoluteVolumeChangedListener;
+
+    @VisibleForTesting
+    AbsoluteVolumeChangedListener getAbsoluteVolumeChangedListener() {
+        return mAbsoluteVolumeChangedListener;
+    }
+
+    /**
+     * Listeners for changes reported by AudioService to the state of an audio output device using
+     * absolute volume behavior.
+     */
+    @VisibleForTesting
+    class AbsoluteVolumeChangedListener implements
+            AudioDeviceVolumeManager.OnAudioDeviceVolumeChangedListener {
+        private HdmiCecLocalDevice mLocalDevice;
+        private HdmiDeviceInfo mSystemAudioDevice;
+
+        private AbsoluteVolumeChangedListener(HdmiCecLocalDevice localDevice,
+                HdmiDeviceInfo systemAudioDevice) {
+            mLocalDevice = localDevice;
+            mSystemAudioDevice = systemAudioDevice;
+        }
+
+        /**
+         * Called when AudioService sets the volume level of an absolute volume audio output device
+         * to a numeric value.
+         */
+        @Override
+        public void onAudioDeviceVolumeChanged(
+                @NonNull AudioDeviceAttributes audioDevice,
+                @NonNull VolumeInfo volumeInfo) {
+            int localDeviceAddress;
+            synchronized (mLocalDevice.mLock) {
+                localDeviceAddress = mLocalDevice.getDeviceInfo().getLogicalAddress();
+            }
+            sendCecCommand(SetAudioVolumeLevelMessage.build(
+                            localDeviceAddress,
+                            mSystemAudioDevice.getLogicalAddress(),
+                            volumeInfo.getVolumeIndex()),
+                    // If sending the message fails, ask the System Audio device for its
+                    // audio status so that we can update AudioService
+                    (int errorCode) -> {
+                        if (errorCode == SendMessageResult.SUCCESS) {
+                            // Update the volume tracked in our AbsoluteVolumeAudioStatusAction
+                            // so it correctly processes incoming <Report Audio Status> messages
+                            HdmiCecLocalDevice avcDevice = isTvDevice() ? tv() : playback();
+                            avcDevice.updateAvcVolume(volumeInfo.getVolumeIndex());
+                        } else {
+                            sendCecCommand(HdmiCecMessageBuilder.buildGiveAudioStatus(
+                                    localDeviceAddress,
+                                    mSystemAudioDevice.getLogicalAddress()
+                            ));
+                        }
+                    });
+        }
+
+        /**
+         * Called when AudioService adjusts the volume or mute state of an absolute volume
+         * audio output device
+         */
+        @Override
+        public void onAudioDeviceVolumeAdjusted(
+                @NonNull AudioDeviceAttributes audioDevice,
+                @NonNull VolumeInfo volumeInfo,
+                @AudioManager.VolumeAdjustment int direction,
+                @AudioDeviceVolumeManager.VolumeAdjustmentMode int mode
+        ) {
+            int keyCode;
+            switch (direction) {
+                case AudioManager.ADJUST_RAISE:
+                    keyCode = KeyEvent.KEYCODE_VOLUME_UP;
+                    break;
+                case AudioManager.ADJUST_LOWER:
+                    keyCode = KeyEvent.KEYCODE_VOLUME_DOWN;
+                    break;
+                case AudioManager.ADJUST_TOGGLE_MUTE:
+                case AudioManager.ADJUST_MUTE:
+                case AudioManager.ADJUST_UNMUTE:
+                    // Many CEC devices only support toggle mute. Therefore, we send the
+                    // same keycode for all three mute options.
+                    keyCode = KeyEvent.KEYCODE_VOLUME_MUTE;
+                    break;
+                default:
+                    return;
+            }
+            switch (mode) {
+                case AudioDeviceVolumeManager.ADJUST_MODE_NORMAL:
+                    mLocalDevice.sendVolumeKeyEvent(keyCode, true);
+                    mLocalDevice.sendVolumeKeyEvent(keyCode, false);
+                    break;
+                case AudioDeviceVolumeManager.ADJUST_MODE_START:
+                    mLocalDevice.sendVolumeKeyEvent(keyCode, true);
+                    break;
+                case AudioDeviceVolumeManager.ADJUST_MODE_END:
+                    mLocalDevice.sendVolumeKeyEvent(keyCode, false);
+                    break;
+                default:
+                    return;
+            }
+        }
+    }
+
+    /**
+     * Notifies AudioService of a change in the volume of the System Audio device. Has no effect if
+     * AVC is disabled, or the audio output device for AVC is not playing for STREAM_MUSIC
+     */
+    void notifyAvcVolumeChange(int volume) {
+        if (!isAbsoluteVolumeControlEnabled()) return;
+        List<AudioDeviceAttributes> streamMusicDevices =
+                getAudioManager().getDevicesForAttributes(STREAM_MUSIC_ATTRIBUTES);
+        if (streamMusicDevices.contains(getAvcAudioOutputDevice())) {
+            setStreamMusicVolume(volume, AudioManager.FLAG_ABSOLUTE_VOLUME);
+        }
+    }
+
+    /**
+     * Notifies AudioService of a change in the mute status of the System Audio device. Has no
+     * effect if AVC is disabled, or the audio output device for AVC is not playing for STREAM_MUSIC
+     */
+    void notifyAvcMuteChange(boolean mute) {
+        if (!isAbsoluteVolumeControlEnabled()) return;
+        List<AudioDeviceAttributes> streamMusicDevices =
+                getAudioManager().getDevicesForAttributes(STREAM_MUSIC_ATTRIBUTES);
+        if (streamMusicDevices.contains(getAvcAudioOutputDevice())) {
+            int direction = mute ? AudioManager.ADJUST_MUTE : AudioManager.ADJUST_UNMUTE;
+            getAudioManager().adjustStreamVolume(AudioManager.STREAM_MUSIC, direction,
+                    AudioManager.FLAG_ABSOLUTE_VOLUME);
+        }
+    }
+
+    /**
+     * Sets the volume index of {@link AudioManager#STREAM_MUSIC}. Rescales the input volume index
+     * from HDMI-CEC volume range to STREAM_MUSIC's.
+     */
+    void setStreamMusicVolume(int volume, int flags) {
+        getAudioManager().setStreamVolume(AudioManager.STREAM_MUSIC,
+                volume * mStreamMusicMaxVolume / AudioStatus.MAX_VOLUME, flags);
+    }
 }
diff --git a/services/core/java/com/android/server/hdmi/SendKeyAction.java b/services/core/java/com/android/server/hdmi/SendKeyAction.java
index adcef66..7daeaf1 100644
--- a/services/core/java/com/android/server/hdmi/SendKeyAction.java
+++ b/services/core/java/com/android/server/hdmi/SendKeyAction.java
@@ -172,8 +172,19 @@
     }
 
     private void sendKeyUp() {
-        sendCommand(HdmiCecMessageBuilder.buildUserControlReleased(getSourceAddress(),
-                mTargetAddress));
+        // When using Absolute Volume Control, query audio status after a volume key is released.
+        // This allows us to notify AudioService of the resulting volume or mute status changes.
+        if (HdmiCecKeycode.isVolumeKeycode(mLastKeycode)
+                && localDevice().getService().isAbsoluteVolumeControlEnabled()) {
+            sendCommand(HdmiCecMessageBuilder.buildUserControlReleased(getSourceAddress(),
+                    mTargetAddress),
+                    __ -> sendCommand(HdmiCecMessageBuilder.buildGiveAudioStatus(
+                            getSourceAddress(),
+                            localDevice().findAudioReceiverAddress())));
+        } else {
+            sendCommand(HdmiCecMessageBuilder.buildUserControlReleased(getSourceAddress(),
+                    mTargetAddress));
+        }
     }
 
     @Override
diff --git a/services/core/java/com/android/server/hdmi/SetAudioVolumeLevelDiscoveryAction.java b/services/core/java/com/android/server/hdmi/SetAudioVolumeLevelDiscoveryAction.java
index 96fd003..eb3b33d 100644
--- a/services/core/java/com/android/server/hdmi/SetAudioVolumeLevelDiscoveryAction.java
+++ b/services/core/java/com/android/server/hdmi/SetAudioVolumeLevelDiscoveryAction.java
@@ -122,4 +122,11 @@
             return true;
         }
     }
+
+    /**
+     * Returns the logical address of this action's target device.
+     */
+    public int getTargetAddress() {
+        return mTargetAddress;
+    }
 }
diff --git a/services/core/java/com/android/server/hdmi/SystemAudioAction.java b/services/core/java/com/android/server/hdmi/SystemAudioAction.java
index 978c25d..e7a3db7 100644
--- a/services/core/java/com/android/server/hdmi/SystemAudioAction.java
+++ b/services/core/java/com/android/server/hdmi/SystemAudioAction.java
@@ -153,7 +153,7 @@
                 boolean receivedStatus = HdmiUtils.parseCommandParamSystemAudioStatus(cmd);
                 if (receivedStatus == mTargetAudioStatus) {
                     setSystemAudioMode(receivedStatus);
-                    startAudioStatusAction();
+                    finish();
                     return true;
                 } else {
                     HdmiLogger.debug("Unexpected system audio mode request:" + receivedStatus);
@@ -168,11 +168,6 @@
         }
     }
 
-    protected void startAudioStatusAction() {
-        addAndStartAction(new SystemAudioStatusAction(tv(), mAvrLogicalAddress, mCallbacks));
-        finish();
-    }
-
     protected void removeSystemAudioActionInProgress() {
         removeActionExcept(SystemAudioActionFromTv.class, this);
         removeActionExcept(SystemAudioActionFromAvr.class, this);
diff --git a/services/core/java/com/android/server/hdmi/SystemAudioActionFromAvr.java b/services/core/java/com/android/server/hdmi/SystemAudioActionFromAvr.java
index 6ddff91..99148c4 100644
--- a/services/core/java/com/android/server/hdmi/SystemAudioActionFromAvr.java
+++ b/services/core/java/com/android/server/hdmi/SystemAudioActionFromAvr.java
@@ -16,8 +16,8 @@
 
 package com.android.server.hdmi;
 
-import android.hardware.hdmi.HdmiDeviceInfo;
 import android.hardware.hdmi.HdmiControlManager;
+import android.hardware.hdmi.HdmiDeviceInfo;
 import android.hardware.hdmi.IHdmiControlCallback;
 
 /**
@@ -65,7 +65,7 @@
 
         if (mTargetAudioStatus) {
             setSystemAudioMode(true);
-            startAudioStatusAction();
+            finish();
         } else {
             setSystemAudioMode(false);
             finishWithCallback(HdmiControlManager.RESULT_SUCCESS);
diff --git a/services/core/java/com/android/server/hdmi/SystemAudioStatusAction.java b/services/core/java/com/android/server/hdmi/SystemAudioStatusAction.java
deleted file mode 100644
index b4af540..0000000
--- a/services/core/java/com/android/server/hdmi/SystemAudioStatusAction.java
+++ /dev/null
@@ -1,112 +0,0 @@
-/*
- * Copyright (C) 2014 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.hdmi;
-
-import android.hardware.hdmi.HdmiControlManager;
-import android.hardware.hdmi.IHdmiControlCallback;
-import android.hardware.tv.cec.V1_0.SendMessageResult;
-
-import com.android.server.hdmi.HdmiControlService.SendMessageCallback;
-
-import java.util.List;
-
-/**
- * Action to update audio status (volume or mute) of audio amplifier
- */
-final class SystemAudioStatusAction extends HdmiCecFeatureAction {
-    private static final String TAG = "SystemAudioStatusAction";
-
-    // State that waits for <ReportAudioStatus>.
-    private static final int STATE_WAIT_FOR_REPORT_AUDIO_STATUS = 1;
-
-    private final int mAvrAddress;
-
-    SystemAudioStatusAction(
-            HdmiCecLocalDevice source, int avrAddress, List<IHdmiControlCallback> callbacks) {
-        super(source, callbacks);
-        mAvrAddress = avrAddress;
-    }
-
-    SystemAudioStatusAction(HdmiCecLocalDevice source, int avrAddress,
-            IHdmiControlCallback callback) {
-        super(source, callback);
-        mAvrAddress = avrAddress;
-    }
-
-    @Override
-    boolean start() {
-        mState = STATE_WAIT_FOR_REPORT_AUDIO_STATUS;
-        addTimer(mState, HdmiConfig.TIMEOUT_MS);
-        sendGiveAudioStatus();
-        return true;
-    }
-
-    private void sendGiveAudioStatus() {
-        sendCommand(HdmiCecMessageBuilder.buildGiveAudioStatus(getSourceAddress(), mAvrAddress),
-                new SendMessageCallback() {
-            @Override
-            public void onSendCompleted(int error) {
-                if (error != SendMessageResult.SUCCESS) {
-                    handleSendGiveAudioStatusFailure();
-                }
-            }
-        });
-    }
-
-    private void handleSendGiveAudioStatusFailure() {
-
-        // Still return SUCCESS to callback.
-        finishWithCallback(HdmiControlManager.RESULT_SUCCESS);
-    }
-
-    @Override
-    boolean processCommand(HdmiCecMessage cmd) {
-        if (mState != STATE_WAIT_FOR_REPORT_AUDIO_STATUS || mAvrAddress != cmd.getSource()) {
-            return false;
-        }
-
-        switch (cmd.getOpcode()) {
-            case Constants.MESSAGE_REPORT_AUDIO_STATUS:
-                handleReportAudioStatus(cmd);
-                return true;
-        }
-
-        return false;
-    }
-
-    private void handleReportAudioStatus(HdmiCecMessage cmd) {
-        byte[] params = cmd.getParams();
-        boolean mute = HdmiUtils.isAudioStatusMute(cmd);
-        int volume = HdmiUtils.getAudioStatusVolume(cmd);
-        tv().setAudioStatus(mute, volume);
-
-        if (!(tv().isSystemAudioActivated() ^ mute)) {
-            // Toggle AVR's mute status to match with the system audio status.
-            sendUserControlPressedAndReleased(mAvrAddress, HdmiCecKeycode.CEC_KEYCODE_MUTE);
-        }
-        finishWithCallback(HdmiControlManager.RESULT_SUCCESS);
-    }
-
-    @Override
-    void handleTimerEvent(int state) {
-        if (mState != state) {
-            return;
-        }
-
-        handleSendGiveAudioStatusFailure();
-    }
-}
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index 7360bbc..385aa69 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -20,6 +20,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.app.ActivityManagerInternal;
 import android.app.Notification;
 import android.app.NotificationManager;
 import android.app.PendingIntent;
@@ -325,8 +326,7 @@
     private static native void nativeSetMaximumObscuringOpacityForTouch(long ptr, float opacity);
     private static native void nativeSetBlockUntrustedTouchesMode(long ptr, int mode);
     private static native int nativeInjectInputEvent(long ptr, InputEvent event,
-            int injectorPid, int injectorUid, int syncMode, int timeoutMillis,
-            int policyFlags);
+            boolean injectIntoUid, int uid, int syncMode, int timeoutMillis, int policyFlags);
     private static native VerifiedInputEvent nativeVerifyInputEvent(long ptr, InputEvent event);
     private static native void nativeToggleCapsLock(long ptr, int deviceId);
     private static native void nativeDisplayRemoved(long ptr, int displayId);
@@ -897,7 +897,15 @@
     }
 
     @Override // Binder call
-    public boolean injectInputEvent(InputEvent event, int mode) {
+    public boolean injectInputEvent(InputEvent event, int mode, int targetUid) {
+        if (!checkCallingPermission(android.Manifest.permission.INJECT_EVENTS,
+                "injectInputEvent()", true /*checkInstrumentationSource*/)) {
+            throw new SecurityException(
+                    "Injecting input events requires the caller (or the source of the "
+                            + "instrumentation, if any) to have the INJECT_EVENTS permission.");
+        }
+        // We are not checking if targetUid matches the callingUid, since having the permission
+        // already means you can inject into any window.
         Objects.requireNonNull(event, "event must not be null");
         if (mode != InputEventInjectionSync.NONE
                 && mode != InputEventInjectionSync.WAIT_FOR_FINISHED
@@ -906,22 +914,41 @@
         }
 
         final int pid = Binder.getCallingPid();
-        final int uid = Binder.getCallingUid();
         final long ident = Binder.clearCallingIdentity();
+        final boolean injectIntoUid = targetUid != Process.INVALID_UID;
         final int result;
         try {
-            result = nativeInjectInputEvent(mPtr, event, pid, uid, mode,
-                    INJECTION_TIMEOUT_MILLIS, WindowManagerPolicy.FLAG_DISABLE_KEY_REPEAT);
+            result = nativeInjectInputEvent(mPtr, event, injectIntoUid,
+                    targetUid, mode, INJECTION_TIMEOUT_MILLIS,
+                    WindowManagerPolicy.FLAG_DISABLE_KEY_REPEAT);
         } finally {
             Binder.restoreCallingIdentity(ident);
         }
         switch (result) {
-            case InputEventInjectionResult.PERMISSION_DENIED:
-                Slog.w(TAG, "Input event injection from pid " + pid + " permission denied.");
-                throw new SecurityException(
-                        "Injecting to another application requires INJECT_EVENTS permission");
             case InputEventInjectionResult.SUCCEEDED:
                 return true;
+            case InputEventInjectionResult.TARGET_MISMATCH:
+                if (!injectIntoUid) {
+                    throw new IllegalStateException("Injection should not result in TARGET_MISMATCH"
+                            + " when it is not targeted into to a specific uid.");
+                }
+                // Attempt to inject into a window owned by the instrumentation source of the caller
+                // because it is possible that tests adopt the identity of the shell when launching
+                // activities that they would like to inject into.
+                final ActivityManagerInternal ami =
+                        LocalServices.getService(ActivityManagerInternal.class);
+                Objects.requireNonNull(ami, "ActivityManagerInternal should not be null.");
+                final int instrUid = ami.getInstrumentationSourceUid(Binder.getCallingUid());
+                if (instrUid != Process.INVALID_UID && targetUid != instrUid) {
+                    Slog.w(TAG, "Targeted input event was not directed at a window owned by uid "
+                            + targetUid + ". Attempting to inject into window owned by "
+                            + "instrumentation source uid " + instrUid + ".");
+                    return injectInputEvent(event, mode, instrUid);
+                }
+                throw new IllegalArgumentException(
+                        "Targeted input event injection from pid " + pid
+                                + " was not directed at a window owned by uid "
+                                + targetUid + ".");
             case InputEventInjectionResult.TIMED_OUT:
                 Slog.w(TAG, "Input event injection from pid " + pid + " timed out.");
                 return false;
@@ -2757,8 +2784,12 @@
             }
         }
     }
-
     private boolean checkCallingPermission(String permission, String func) {
+        return checkCallingPermission(permission, func, false /*checkInstrumentationSource*/);
+    }
+
+    private boolean checkCallingPermission(String permission, String func,
+            boolean checkInstrumentationSource) {
         // Quick check: if the calling permission is me, it's all okay.
         if (Binder.getCallingPid() == Process.myPid()) {
             return true;
@@ -2767,6 +2798,18 @@
         if (mContext.checkCallingPermission(permission) == PackageManager.PERMISSION_GRANTED) {
             return true;
         }
+
+        if (checkInstrumentationSource) {
+            final ActivityManagerInternal ami =
+                    LocalServices.getService(ActivityManagerInternal.class);
+            Objects.requireNonNull(ami, "ActivityManagerInternal should not be null.");
+            final int instrumentationUid = ami.getInstrumentationSourceUid(Binder.getCallingUid());
+            if (instrumentationUid != Process.INVALID_UID && mContext.checkPermission(permission,
+                    -1 /*pid*/, instrumentationUid) == PackageManager.PERMISSION_GRANTED) {
+                return true;
+            }
+        }
+
         String msg = "Permission Denial: " + func + " from pid="
                 + Binder.getCallingPid()
                 + ", uid=" + Binder.getCallingUid()
@@ -3012,13 +3055,6 @@
 
     // Native callback.
     @SuppressWarnings("unused")
-    private boolean checkInjectEventsPermission(int injectorPid, int injectorUid) {
-        return mContext.checkPermission(android.Manifest.permission.INJECT_EVENTS,
-                injectorPid, injectorUid) == PackageManager.PERMISSION_GRANTED;
-    }
-
-    // Native callback.
-    @SuppressWarnings("unused")
     private void onPointerDownOutsideFocus(IBinder touchedToken) {
         mWindowManagerCallbacks.onPointerDownOutsideFocus(touchedToken);
     }
@@ -3459,12 +3495,17 @@
 
         @Override
         public void sendInputEvent(InputEvent event, int policyFlags) {
+            if (!checkCallingPermission(android.Manifest.permission.INJECT_EVENTS,
+                    "sendInputEvent()")) {
+                throw new SecurityException(
+                        "The INJECT_EVENTS permission is required for injecting input events.");
+            }
             Objects.requireNonNull(event, "event must not be null");
 
             synchronized (mInputFilterLock) {
                 if (!mDisconnected) {
-                    nativeInjectInputEvent(mPtr, event, 0, 0,
-                            InputManager.INJECT_INPUT_EVENT_MODE_ASYNC, 0,
+                    nativeInjectInputEvent(mPtr, event, false /* injectIntoUid */, -1 /* uid */,
+                            InputManager.INJECT_INPUT_EVENT_MODE_ASYNC, 0 /* timeout */,
                             policyFlags | WindowManagerPolicy.FLAG_FILTERED);
                 }
             }
diff --git a/services/core/java/com/android/server/locales/LocaleManagerBackupHelper.java b/services/core/java/com/android/server/locales/LocaleManagerBackupHelper.java
index db81393..37a4869 100644
--- a/services/core/java/com/android/server/locales/LocaleManagerBackupHelper.java
+++ b/services/core/java/com/android/server/locales/LocaleManagerBackupHelper.java
@@ -30,8 +30,6 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
-import android.content.pm.PackageManagerInternal;
-import android.os.Binder;
 import android.os.HandlerThread;
 import android.os.LocaleList;
 import android.os.RemoteException;
@@ -78,7 +76,7 @@
     private static final Duration STAGE_DATA_RETENTION_PERIOD = Duration.ofDays(3);
 
     private final LocaleManagerService mLocaleManagerService;
-    private final PackageManagerInternal mPackageManagerInternal;
+    private final PackageManager mPackageManager;
     private final Clock mClock;
     private final Context mContext;
     private final Object mStagedDataLock = new Object();
@@ -90,18 +88,18 @@
     private final BroadcastReceiver mUserMonitor;
 
     LocaleManagerBackupHelper(LocaleManagerService localeManagerService,
-            PackageManagerInternal pmInternal, HandlerThread broadcastHandlerThread) {
-        this(localeManagerService.mContext, localeManagerService, pmInternal, Clock.systemUTC(),
+            PackageManager packageManager, HandlerThread broadcastHandlerThread) {
+        this(localeManagerService.mContext, localeManagerService, packageManager, Clock.systemUTC(),
                 new SparseArray<>(), broadcastHandlerThread);
     }
 
     @VisibleForTesting LocaleManagerBackupHelper(Context context,
             LocaleManagerService localeManagerService,
-            PackageManagerInternal pmInternal, Clock clock, SparseArray<StagedData> stagedData,
+            PackageManager packageManager, Clock clock, SparseArray<StagedData> stagedData,
             HandlerThread broadcastHandlerThread) {
         mContext = context;
         mLocaleManagerService = localeManagerService;
-        mPackageManagerInternal = pmInternal;
+        mPackageManager = packageManager;
         mClock = clock;
         mStagedData = stagedData;
 
@@ -130,8 +128,8 @@
         }
 
         HashMap<String, String> pkgStates = new HashMap<>();
-        for (ApplicationInfo appInfo : mPackageManagerInternal.getInstalledApplications(/*flags*/0,
-                userId, Binder.getCallingUid())) {
+        for (ApplicationInfo appInfo : mPackageManager.getInstalledApplicationsAsUser(
+                PackageManager.ApplicationInfoFlags.of(0), userId)) {
             try {
                 LocaleList appLocales = mLocaleManagerService.getApplicationLocales(
                         appInfo.packageName,
diff --git a/services/core/java/com/android/server/locales/LocaleManagerService.java b/services/core/java/com/android/server/locales/LocaleManagerService.java
index 924db6a..9ef14cc 100644
--- a/services/core/java/com/android/server/locales/LocaleManagerService.java
+++ b/services/core/java/com/android/server/locales/LocaleManagerService.java
@@ -28,7 +28,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager;
-import android.content.pm.PackageManagerInternal;
+import android.content.pm.PackageManager.PackageInfoFlags;
 import android.content.res.Configuration;
 import android.os.Binder;
 import android.os.HandlerThread;
@@ -60,7 +60,7 @@
     private final LocaleManagerService.LocaleManagerBinderService mBinderService;
     private ActivityTaskManagerInternal mActivityTaskManagerInternal;
     private ActivityManagerInternal mActivityManagerInternal;
-    private PackageManagerInternal mPackageManagerInternal;
+    private PackageManager mPackageManager;
 
     private LocaleManagerBackupHelper mBackupHelper;
 
@@ -74,7 +74,7 @@
         mBinderService = new LocaleManagerBinderService();
         mActivityTaskManagerInternal = LocalServices.getService(ActivityTaskManagerInternal.class);
         mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
-        mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
+        mPackageManager = mContext.getPackageManager();
 
         HandlerThread broadcastHandlerThread = new HandlerThread(TAG,
                 Process.THREAD_PRIORITY_BACKGROUND);
@@ -90,7 +90,7 @@
         });
 
         mBackupHelper = new LocaleManagerBackupHelper(this,
-                mPackageManagerInternal, broadcastHandlerThread);
+                mPackageManager, broadcastHandlerThread);
 
         mPackageMonitor = new LocaleManagerServicePackageMonitor(mBackupHelper,
                 systemAppUpdateTracker);
@@ -102,7 +102,7 @@
     @VisibleForTesting
     LocaleManagerService(Context context, ActivityTaskManagerInternal activityTaskManagerInternal,
             ActivityManagerInternal activityManagerInternal,
-            PackageManagerInternal packageManagerInternal,
+            PackageManager packageManager,
             LocaleManagerBackupHelper localeManagerBackupHelper,
             PackageMonitor packageMonitor) {
         super(context);
@@ -110,7 +110,7 @@
         mBinderService = new LocaleManagerBinderService();
         mActivityTaskManagerInternal = activityTaskManagerInternal;
         mActivityManagerInternal = activityManagerInternal;
-        mPackageManagerInternal = packageManagerInternal;
+        mPackageManager = packageManager;
         mBackupHelper = localeManagerBackupHelper;
         mPackageMonitor = packageMonitor;
     }
@@ -419,8 +419,12 @@
     }
 
     private int getPackageUid(String appPackageName, int userId) {
-        return mPackageManagerInternal
-                .getPackageUid(appPackageName, /* flags */ 0, userId);
+        try {
+            return mPackageManager
+                    .getPackageUidAsUser(appPackageName, PackageInfoFlags.of(0), userId);
+        } catch (PackageManager.NameNotFoundException e) {
+            return Process.INVALID_UID;
+        }
     }
 
     @Nullable
diff --git a/services/core/java/com/android/server/location/LocationManagerService.java b/services/core/java/com/android/server/location/LocationManagerService.java
index fac5106..31d5136 100644
--- a/services/core/java/com/android/server/location/LocationManagerService.java
+++ b/services/core/java/com/android/server/location/LocationManagerService.java
@@ -94,7 +94,6 @@
 import android.util.Log;
 
 import com.android.internal.annotations.GuardedBy;
-import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.Preconditions;
 import com.android.server.FgThread;
@@ -279,6 +278,9 @@
                 this::onLocationUserSettingsChanged);
         mInjector.getSettingsHelper().addOnLocationEnabledChangedListener(
                 this::onLocationModeChanged);
+        mInjector.getSettingsHelper().addAdasAllowlistChangedListener(
+                () -> refreshAppOpsRestrictions(UserHandle.USER_ALL)
+        );
         mInjector.getSettingsHelper().addIgnoreSettingsAllowlistChangedListener(
                 () -> refreshAppOpsRestrictions(UserHandle.USER_ALL));
         mInjector.getUserInfoHelper().addListener((userId, change) -> {
@@ -823,12 +825,6 @@
                 throw new IllegalArgumentException(
                         "adas gnss bypass requests are only allowed on the \"gps\" provider");
             }
-            if (!ArrayUtils.contains(mContext.getResources().getStringArray(
-                    com.android.internal.R.array.config_locationDriverAssistancePackageNames),
-                    identity.getPackageName())) {
-                throw new SecurityException(
-                        "only verified adas packages may use adas gnss bypass requests");
-            }
             if (!isLocationProvider) {
                 LocationPermissions.enforceCallingOrSelfBypassPermission(mContext);
             }
@@ -923,12 +919,6 @@
                 throw new IllegalArgumentException(
                         "adas gnss bypass requests are only allowed on the \"gps\" provider");
             }
-            if (!ArrayUtils.contains(mContext.getResources().getStringArray(
-                    com.android.internal.R.array.config_locationDriverAssistancePackageNames),
-                    identity.getPackageName())) {
-                throw new SecurityException(
-                        "only verified adas packages may use adas gnss bypass requests");
-            }
             if (!isLocationProvider) {
                 LocationPermissions.enforceCallingOrSelfBypassPermission(mContext);
             }
@@ -1542,6 +1532,7 @@
                 }
             }
             builder.add(mInjector.getSettingsHelper().getIgnoreSettingsAllowlist());
+            builder.add(mInjector.getSettingsHelper().getAdasAllowlist());
             allowedPackages = builder.build();
         }
 
diff --git a/services/core/java/com/android/server/location/injector/SettingsHelper.java b/services/core/java/com/android/server/location/injector/SettingsHelper.java
index 148afa7..490bfe1 100644
--- a/services/core/java/com/android/server/location/injector/SettingsHelper.java
+++ b/services/core/java/com/android/server/location/injector/SettingsHelper.java
@@ -146,6 +146,20 @@
     public abstract void removeOnGnssMeasurementsFullTrackingEnabledChangedListener(
             GlobalSettingChangedListener listener);
 
+    /** Retrieve adas allowlist. */
+    public abstract PackageTagsList getAdasAllowlist();
+
+    /**
+     * Add a listener for changes to the ADAS settings package allowlist. Callbacks occur on an
+     * unspecified thread.
+     */
+    public abstract void addAdasAllowlistChangedListener(GlobalSettingChangedListener listener);
+
+    /**
+     * Remove a listener for changes to the ADAS package allowlist.
+     */
+    public abstract void removeAdasAllowlistChangedListener(GlobalSettingChangedListener listener);
+
     /**
      * Retrieve the ignore location settings package+tags allowlist setting.
      */
diff --git a/services/core/java/com/android/server/location/injector/SystemSettingsHelper.java b/services/core/java/com/android/server/location/injector/SystemSettingsHelper.java
index 3e8da7d..777683e 100644
--- a/services/core/java/com/android/server/location/injector/SystemSettingsHelper.java
+++ b/services/core/java/com/android/server/location/injector/SystemSettingsHelper.java
@@ -16,6 +16,7 @@
 
 package com.android.server.location.injector;
 
+import static android.location.LocationDeviceConfig.ADAS_SETTINGS_ALLOWLIST;
 import static android.location.LocationDeviceConfig.IGNORE_SETTINGS_ALLOWLIST;
 import static android.provider.Settings.Global.ENABLE_GNSS_RAW_MEAS_FULL_TRACKING;
 import static android.provider.Settings.Global.LOCATION_BACKGROUND_THROTTLE_INTERVAL_MS;
@@ -80,6 +81,7 @@
     private final StringListCachedSecureSetting mLocationPackageBlacklist;
     private final StringListCachedSecureSetting mLocationPackageWhitelist;
     private final StringSetCachedGlobalSetting mBackgroundThrottlePackageWhitelist;
+    private final PackageTagsListSetting mAdasPackageAllowlist;
     private final PackageTagsListSetting mIgnoreSettingsPackageAllowlist;
 
     public SystemSettingsHelper(Context context) {
@@ -98,6 +100,9 @@
                 LOCATION_BACKGROUND_THROTTLE_PACKAGE_WHITELIST,
                 () -> SystemConfig.getInstance().getAllowUnthrottledLocation(),
                 FgThread.getHandler());
+        mAdasPackageAllowlist = new PackageTagsListSetting(
+                ADAS_SETTINGS_ALLOWLIST,
+                () -> SystemConfig.getInstance().getAllowAdasLocationSettings());
         mIgnoreSettingsPackageAllowlist = new PackageTagsListSetting(
                 IGNORE_SETTINGS_ALLOWLIST,
                 () -> SystemConfig.getInstance().getAllowIgnoreLocationSettings());
@@ -233,6 +238,21 @@
     }
 
     @Override
+    public PackageTagsList getAdasAllowlist() {
+        return mAdasPackageAllowlist.getValue();
+    }
+
+    @Override
+    public void addAdasAllowlistChangedListener(GlobalSettingChangedListener listener) {
+        mAdasPackageAllowlist.addListener(listener);
+    }
+
+    @Override
+    public void removeAdasAllowlistChangedListener(GlobalSettingChangedListener listener) {
+        mAdasPackageAllowlist.removeListener(listener);
+    }
+
+    @Override
     public PackageTagsList getIgnoreSettingsAllowlist() {
         return mIgnoreSettingsPackageAllowlist.getValue();
     }
@@ -359,11 +379,19 @@
 
         PackageTagsList ignoreSettingsAllowlist = mIgnoreSettingsPackageAllowlist.getValue();
         if (!ignoreSettingsAllowlist.isEmpty()) {
-            ipw.println("Bypass Allow Packages:");
+            ipw.println("Emergency Bypass Allow Packages:");
             ipw.increaseIndent();
             ignoreSettingsAllowlist.dump(ipw);
             ipw.decreaseIndent();
         }
+
+        PackageTagsList adasPackageAllowlist = mAdasPackageAllowlist.getValue();
+        if (!adasPackageAllowlist.isEmpty()) {
+            ipw.println("ADAS Bypass Allow Packages:");
+            ipw.increaseIndent();
+            adasPackageAllowlist.dump(ipw);
+            ipw.decreaseIndent();
+        }
     }
 
     private abstract static class ObservingSetting extends ContentObserver {
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 721ef1e..1235352 100644
--- a/services/core/java/com/android/server/location/provider/LocationProviderManager.java
+++ b/services/core/java/com/android/server/location/provider/LocationProviderManager.java
@@ -699,6 +699,9 @@
                 } else if (!mLocationSettings.getUserSettings(
                         getIdentity().getUserId()).isAdasGnssLocationEnabled()) {
                     adasGnssBypass = false;
+                } else if (!mSettingsHelper.getAdasAllowlist().contains(
+                        getIdentity().getPackageName(), getIdentity().getAttributionTag())) {
+                    adasGnssBypass = false;
                 }
 
                 builder.setAdasGnssBypass(adasGnssBypass);
@@ -1406,6 +1409,8 @@
             this::onAppForegroundChanged;
     private final GlobalSettingChangedListener mBackgroundThrottleIntervalChangedListener =
             this::onBackgroundThrottleIntervalChanged;
+    private final GlobalSettingChangedListener mAdasPackageAllowlistChangedListener =
+            this::onAdasAllowlistChanged;
     private final GlobalSettingChangedListener mIgnoreSettingsPackageWhitelistChangedListener =
             this::onIgnoreSettingsWhitelistChanged;
     private final LocationPowerSaveModeChangedListener mLocationPowerSaveModeChangedListener =
@@ -1710,6 +1715,9 @@
             } else if (!mLocationSettings.getUserSettings(
                     identity.getUserId()).isAdasGnssLocationEnabled()) {
                 adasGnssBypass = false;
+            } else if (!mSettingsHelper.getAdasAllowlist().contains(
+                    identity.getPackageName(), identity.getAttributionTag())) {
+                adasGnssBypass = false;
             }
 
             builder.setAdasGnssBypass(adasGnssBypass);
@@ -1979,6 +1987,8 @@
                 mBackgroundThrottlePackageWhitelistChangedListener);
         mSettingsHelper.addOnLocationPackageBlacklistChangedListener(
                 mLocationPackageBlacklistChangedListener);
+        mSettingsHelper.addAdasAllowlistChangedListener(
+                mAdasPackageAllowlistChangedListener);
         mSettingsHelper.addIgnoreSettingsAllowlistChangedListener(
                 mIgnoreSettingsPackageWhitelistChangedListener);
         mLocationPermissionsHelper.addListener(mLocationPermissionsListener);
@@ -2000,6 +2010,7 @@
                 mBackgroundThrottlePackageWhitelistChangedListener);
         mSettingsHelper.removeOnLocationPackageBlacklistChangedListener(
                 mLocationPackageBlacklistChangedListener);
+        mSettingsHelper.removeAdasAllowlistChangedListener(mAdasPackageAllowlistChangedListener);
         mSettingsHelper.removeIgnoreSettingsAllowlistChangedListener(
                 mIgnoreSettingsPackageWhitelistChangedListener);
         mLocationPermissionsHelper.removeListener(mLocationPermissionsListener);
@@ -2422,6 +2433,12 @@
         }
     }
 
+    private void onAdasAllowlistChanged() {
+        synchronized (mLock) {
+            updateRegistrations(Registration::onProviderLocationRequestChanged);
+        }
+    }
+
     private void onIgnoreSettingsWhitelistChanged() {
         synchronized (mLock) {
             updateRegistrations(Registration::onProviderLocationRequestChanged);
diff --git a/services/core/java/com/android/server/logcat/LogcatManagerService.java b/services/core/java/com/android/server/logcat/LogcatManagerService.java
index 0aa384c..015bf3c5 100644
--- a/services/core/java/com/android/server/logcat/LogcatManagerService.java
+++ b/services/core/java/com/android/server/logcat/LogcatManagerService.java
@@ -183,7 +183,8 @@
 
                 ActivityManagerInternal ami = LocalServices.getService(
                         ActivityManagerInternal.class);
-                boolean isCallerInstrumented = ami.isUidCurrentlyInstrumented(mUid);
+                boolean isCallerInstrumented =
+                        ami.getInstrumentationSourceUid(mUid) != android.os.Process.INVALID_UID;
 
                 // The instrumented apks only run for testing, so we don't check user permission.
                 if (isCallerInstrumented) {
diff --git a/services/core/java/com/android/server/pm/AppsFilter.java b/services/core/java/com/android/server/pm/AppsFilter.java
index 152c745..29ee281 100644
--- a/services/core/java/com/android/server/pm/AppsFilter.java
+++ b/services/core/java/com/android/server/pm/AppsFilter.java
@@ -52,7 +52,6 @@
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.function.QuadFunction;
 import com.android.server.FgThread;
-import com.android.server.LocalServices;
 import com.android.server.compat.CompatChange;
 import com.android.server.om.OverlayReferenceMapper;
 import com.android.server.pm.parsing.pkg.AndroidPackage;
@@ -171,10 +170,13 @@
      * filtered to the second. It's essentially a cache of the
      * {@link #shouldFilterApplicationInternal(int, Object, PackageStateInternal, int)} call.
      * NOTE: It can only be relied upon after the system is ready to avoid unnecessary update on
-     * initial scam and is null until {@link #onSystemReady()} is called.
+     * initial scam and is empty until {@link #mSystemReady} is true.
      */
     @GuardedBy("mCacheLock")
-    private volatile WatchedSparseBooleanMatrix mShouldFilterCache;
+    @NonNull
+    private final WatchedSparseBooleanMatrix mShouldFilterCache;
+
+    private volatile boolean mSystemReady = false;
 
     /**
      * A cached snapshot.
@@ -187,7 +189,8 @@
             public AppsFilter createSnapshot() {
                 AppsFilter s = new AppsFilter(mSource);
                 return s;
-            }};
+            }
+        };
     }
 
     /**
@@ -219,6 +222,7 @@
 
     /**
      * Return true if the {@link Watcher) is a registered observer.
+     *
      * @param observer A {@link Watcher} that might be registered
      * @return true if the observer is registered with this {@link Watchable}.
      */
@@ -262,6 +266,7 @@
         mStateProvider = stateProvider;
         mPmInternal = pmInternal;
         mBackgroundExecutor = backgroundExecutor;
+        mShouldFilterCache = new WatchedSparseBooleanMatrix();
         mSnapshot = makeCache();
     }
 
@@ -285,16 +290,14 @@
         mStateProvider = orig.mStateProvider;
         mSystemSigningDetails = orig.mSystemSigningDetails;
         mProtectedBroadcasts = orig.mProtectedBroadcasts;
-        mShouldFilterCache = orig.mShouldFilterCache;
-        if (mShouldFilterCache != null) {
-            synchronized (orig.mCacheLock) {
-                mShouldFilterCache = mShouldFilterCache.snapshot();
-            }
+        synchronized (orig.mCacheLock) {
+            mShouldFilterCache = orig.mShouldFilterCache.snapshot();
         }
 
         mBackgroundExecutor = null;
         mPmInternal = null;
         mSnapshot = new SnapshotCache.Sealed<>();
+        mSystemReady = true;
     }
 
     /**
@@ -653,9 +656,9 @@
      * Grants access based on an interaction between a calling and target package, granting
      * visibility of the caller from the target.
      *
-     * @param recipientUid the uid gaining visibility of the {@code visibleUid}.
-     * @param visibleUid   the uid becoming visible to the {@recipientUid}
-     * @param retainOnUpdate  if the implicit access retained across package updates.
+     * @param recipientUid   the uid gaining visibility of the {@code visibleUid}.
+     * @param visibleUid     the uid becoming visible to the {@recipientUid}
+     * @param retainOnUpdate if the implicit access retained across package updates.
      * @return {@code true} if implicit access was not already granted.
      */
     public boolean grantImplicitAccess(int recipientUid, int visibleUid, boolean retainOnUpdate) {
@@ -669,8 +672,9 @@
             Slog.i(TAG, (retainOnUpdate ? "retained " : "") + "implicit access granted: "
                     + recipientUid + " -> " + visibleUid);
         }
-        synchronized (mCacheLock) {
-            if (mShouldFilterCache != null) {
+
+        if (mSystemReady) {
+            synchronized (mCacheLock) {
                 // update the cache in a one-off manner since we've got all the information we
                 // need.
                 mShouldFilterCache.put(recipientUid, visibleUid, false);
@@ -688,13 +692,14 @@
 
         updateEntireShouldFilterCacheAsync();
         onChanged();
+        mSystemReady = true;
     }
 
     /**
      * Adds a package that should be considered when filtering visibility between apps.
      *
      * @param newPkgSetting the new setting being added
-     * @param isReplace if the package is being replaced and may need extra cleanup.
+     * @param isReplace     if the package is being replaced and may need extra cleanup.
      */
     public void addPackage(PackageStateInternal newPkgSetting, boolean isReplace) {
         if (DEBUG_TRACING) {
@@ -708,29 +713,27 @@
             mStateProvider.runWithState((settings, users) -> {
                 ArraySet<String> additionalChangedPackages =
                         addPackageInternal(newPkgSetting, settings);
-                synchronized (mCacheLock) {
-                    if (mShouldFilterCache != null) {
-                        updateShouldFilterCacheForPackage(mShouldFilterCache, null, newPkgSetting,
-                                settings, users, USER_ALL, settings.size());
-                        if (additionalChangedPackages != null) {
-                            for (int index = 0; index < additionalChangedPackages.size(); index++) {
-                                String changedPackage = additionalChangedPackages.valueAt(index);
-                                PackageStateInternal changedPkgSetting =
-                                        settings.get(changedPackage);
-                                if (changedPkgSetting == null) {
-                                    // It's possible for the overlay mapper to know that an actor
-                                    // package changed via an explicit reference, even if the actor
-                                    // isn't installed, so skip if that's the case.
-                                    continue;
-                                }
-
-                                updateShouldFilterCacheForPackage(mShouldFilterCache, null,
-                                        changedPkgSetting, settings, users, USER_ALL,
-                                        settings.size());
+                if (mSystemReady) {
+                    updateShouldFilterCacheForPackage(null, newPkgSetting,
+                            settings, users, USER_ALL, settings.size());
+                    if (additionalChangedPackages != null) {
+                        for (int index = 0; index < additionalChangedPackages.size(); index++) {
+                            String changedPackage = additionalChangedPackages.valueAt(index);
+                            PackageStateInternal changedPkgSetting =
+                                    settings.get(changedPackage);
+                            if (changedPkgSetting == null) {
+                                // It's possible for the overlay mapper to know that an actor
+                                // package changed via an explicit reference, even if the actor
+                                // isn't installed, so skip if that's the case.
+                                continue;
                             }
+
+                            updateShouldFilterCacheForPackage(null,
+                                    changedPkgSetting, settings, users, USER_ALL,
+                                    settings.size());
                         }
-                    } // else, rebuild entire cache when system is ready
-                }
+                    }
+                } // else, rebuild entire cache when system is ready
             });
         } finally {
             onChanged();
@@ -845,19 +848,20 @@
         return changedPackages;
     }
 
-    @GuardedBy("mCacheLock")
     private void removeAppIdFromVisibilityCache(int appId) {
-        if (mShouldFilterCache == null) {
+        if (!mSystemReady) {
             return;
         }
-        for (int i = 0; i < mShouldFilterCache.size(); i++) {
-            if (UserHandle.getAppId(mShouldFilterCache.keyAt(i)) == appId) {
-                mShouldFilterCache.removeAt(i);
-                // The key was deleted so the list of keys has shifted left.  That means i
-                // is now pointing at the next key to be examined.  The decrement here and
-                // the loop increment together mean that i will be unchanged in the need
-                // iteration and will correctly point to the next key to be examined.
-                i--;
+        synchronized (mCacheLock) {
+            for (int i = 0; i < mShouldFilterCache.size(); i++) {
+                if (UserHandle.getAppId(mShouldFilterCache.keyAt(i)) == appId) {
+                    mShouldFilterCache.removeAt(i);
+                    // The key was deleted so the list of keys has shifted left.  That means i
+                    // is now pointing at the next key to be examined.  The decrement here and
+                    // the loop increment together mean that i will be unchanged in the need
+                    // iteration and will correctly point to the next key to be examined.
+                    i--;
+                }
             }
         }
     }
@@ -880,31 +884,23 @@
                         + "updating the whole cache");
                 userId = USER_ALL;
             }
-            WatchedSparseBooleanMatrix cache =
-                    updateEntireShouldFilterCacheInner(settings, users, userId);
-            synchronized (mCacheLock) {
-                mShouldFilterCache = cache;
-            }
+            updateEntireShouldFilterCacheInner(settings, users, userId);
         });
     }
 
-    private WatchedSparseBooleanMatrix updateEntireShouldFilterCacheInner(
+    private void updateEntireShouldFilterCacheInner(
             ArrayMap<String, ? extends PackageStateInternal> settings, UserInfo[] users,
             int subjectUserId) {
-        final WatchedSparseBooleanMatrix cache;
-        if (subjectUserId == USER_ALL) {
-            cache = new WatchedSparseBooleanMatrix(users.length * settings.size());
-        } else {
-            synchronized (mCacheLock) {
-                cache = mShouldFilterCache.snapshot();
+        synchronized (mCacheLock) {
+            if (subjectUserId == USER_ALL) {
+                mShouldFilterCache.clear();
             }
-            cache.setCapacity(users.length * settings.size());
+            mShouldFilterCache.setCapacity(users.length * settings.size());
         }
         for (int i = settings.size() - 1; i >= 0; i--) {
-            updateShouldFilterCacheForPackage(cache,
+            updateShouldFilterCacheForPackage(
                     null /*skipPackage*/, settings.valueAt(i), settings, users, subjectUserId, i);
         }
-        return cache;
     }
 
     private void updateEntireShouldFilterCacheAsync() {
@@ -923,8 +919,7 @@
                     packagesCache.put(settings.keyAt(i), pkg);
                 }
             });
-            WatchedSparseBooleanMatrix cache = updateEntireShouldFilterCacheInner(
-                    settingsCopy, usersRef[0], USER_ALL);
+
             boolean[] changed = new boolean[1];
             // We have a cache, let's make sure the world hasn't changed out from under us.
             mStateProvider.runWithState((settings, users) -> {
@@ -947,45 +942,39 @@
                     Slog.i(TAG, "Rebuilding cache with lock due to package change.");
                 }
             } else {
-                synchronized (mCacheLock) {
-                    mShouldFilterCache = cache;
-                }
+                updateEntireShouldFilterCacheInner(settingsCopy, usersRef[0], USER_ALL);
             }
         });
     }
 
     public void onUserCreated(int newUserId) {
-        synchronized (mCacheLock) {
-            if (mShouldFilterCache != null) {
-                updateEntireShouldFilterCache(newUserId);
-                onChanged();
-            }
+        if (!mSystemReady) {
+            return;
         }
+        updateEntireShouldFilterCache(newUserId);
+        onChanged();
     }
 
     public void onUserDeleted(@UserIdInt int userId) {
-        synchronized (mCacheLock) {
-            if (mShouldFilterCache != null) {
-                removeShouldFilterCacheForUser(userId);
-                onChanged();
-            }
+        if (!mSystemReady) {
+            return;
         }
+        removeShouldFilterCacheForUser(userId);
+        onChanged();
     }
 
     private void updateShouldFilterCacheForPackage(String packageName) {
         mStateProvider.runWithState((settings, users) -> {
-            synchronized (mCacheLock) {
-                if (mShouldFilterCache == null) {
-                    return;
-                }
-                updateShouldFilterCacheForPackage(mShouldFilterCache, null /* skipPackage */,
-                        settings.get(packageName), settings, users, USER_ALL,
-                        settings.size() /*maxIndex*/);
+            if (!mSystemReady) {
+                return;
             }
+            updateShouldFilterCacheForPackage(null /* skipPackage */,
+                    settings.get(packageName), settings, users, USER_ALL,
+                    settings.size() /*maxIndex*/);
         });
     }
 
-    private void updateShouldFilterCacheForPackage(WatchedSparseBooleanMatrix cache,
+    private void updateShouldFilterCacheForPackage(
             @Nullable String skipPackageName, PackageStateInternal subjectSetting, ArrayMap<String,
             ? extends PackageStateInternal> allSettings, UserInfo[] allUsers, int subjectUserId,
             int maxIndex) {
@@ -1001,53 +990,56 @@
             }
             if (subjectUserId == USER_ALL) {
                 for (int su = 0; su < allUsers.length; su++) {
-                    updateShouldFilterCacheForUser(cache, subjectSetting, allUsers, otherSetting,
+                    updateShouldFilterCacheForUser(subjectSetting, allUsers, otherSetting,
                             allUsers[su].id);
                 }
             } else {
-                updateShouldFilterCacheForUser(cache, subjectSetting, allUsers, otherSetting,
+                updateShouldFilterCacheForUser(subjectSetting, allUsers, otherSetting,
                         subjectUserId);
             }
         }
     }
 
-    private void updateShouldFilterCacheForUser(WatchedSparseBooleanMatrix cache,
+    private void updateShouldFilterCacheForUser(
             PackageStateInternal subjectSetting, UserInfo[] allUsers,
             PackageStateInternal otherSetting, int subjectUserId) {
         for (int ou = 0; ou < allUsers.length; ou++) {
             int otherUser = allUsers[ou].id;
             int subjectUid = UserHandle.getUid(subjectUserId, subjectSetting.getAppId());
             int otherUid = UserHandle.getUid(otherUser, otherSetting.getAppId());
-            cache.put(subjectUid, otherUid,
-                    shouldFilterApplicationInternal(
-                            subjectUid, subjectSetting, otherSetting, otherUser));
-            cache.put(otherUid, subjectUid,
-                    shouldFilterApplicationInternal(
-                            otherUid, otherSetting, subjectSetting, subjectUserId));
+            final boolean shouldFilterSubjectToOther = shouldFilterApplicationInternal(
+                    subjectUid, subjectSetting, otherSetting, otherUser);
+            final boolean shouldFilterOtherToSubject = shouldFilterApplicationInternal(
+                    otherUid, otherSetting, subjectSetting, subjectUserId);
+            synchronized (mCacheLock) {
+                mShouldFilterCache.put(subjectUid, otherUid, shouldFilterSubjectToOther);
+                mShouldFilterCache.put(otherUid, subjectUid, shouldFilterOtherToSubject);
+            }
         }
     }
 
-    @GuardedBy("mCacheLock")
     private void removeShouldFilterCacheForUser(int userId) {
-        // Sorted uids with the ascending order
-        final int[] cacheUids = mShouldFilterCache.keys();
-        final int size = cacheUids.length;
-        int pos = Arrays.binarySearch(cacheUids, UserHandle.getUid(userId, 0));
-        final int fromIndex = (pos >= 0 ? pos : ~pos);
-        if (fromIndex >= size || UserHandle.getUserId(cacheUids[fromIndex]) != userId) {
-            Slog.w(TAG, "Failed to remove should filter cache for user " + userId
-                    + ", fromIndex=" + fromIndex);
-            return;
+        synchronized (mCacheLock) {
+            // Sorted uids with the ascending order
+            final int[] cacheUids = mShouldFilterCache.keys();
+            final int size = cacheUids.length;
+            int pos = Arrays.binarySearch(cacheUids, UserHandle.getUid(userId, 0));
+            final int fromIndex = (pos >= 0 ? pos : ~pos);
+            if (fromIndex >= size || UserHandle.getUserId(cacheUids[fromIndex]) != userId) {
+                Slog.w(TAG, "Failed to remove should filter cache for user " + userId
+                        + ", fromIndex=" + fromIndex);
+                return;
+            }
+            pos = Arrays.binarySearch(cacheUids, UserHandle.getUid(userId + 1, 0) - 1);
+            final int toIndex = (pos >= 0 ? pos + 1 : ~pos);
+            if (fromIndex >= toIndex || UserHandle.getUserId(cacheUids[toIndex - 1]) != userId) {
+                Slog.w(TAG, "Failed to remove should filter cache for user " + userId
+                        + ", fromIndex=" + fromIndex + ", toIndex=" + toIndex);
+                return;
+            }
+            mShouldFilterCache.removeRange(fromIndex, toIndex);
+            mShouldFilterCache.compact();
         }
-        pos = Arrays.binarySearch(cacheUids, UserHandle.getUid(userId + 1, 0) - 1);
-        final int toIndex = (pos >= 0 ? pos + 1 : ~pos);
-        if (fromIndex >= toIndex || UserHandle.getUserId(cacheUids[toIndex - 1]) != userId) {
-            Slog.w(TAG, "Failed to remove should filter cache for user " + userId
-                    + ", fromIndex=" + fromIndex + ", toIndex=" + toIndex);
-            return;
-        }
-        mShouldFilterCache.removeRange(fromIndex, toIndex);
-        mShouldFilterCache.compact();
     }
 
     private static boolean isSystemSigned(@NonNull SigningDetails sysSigningDetails,
@@ -1171,6 +1163,7 @@
     /**
      * Equivalent to calling {@link #addPackage(PackageStateInternal, boolean)} with
      * {@code isReplace} equal to {@code false}.
+     *
      * @see AppsFilter#addPackage(PackageStateInternal, boolean)
      */
     public void addPackage(PackageStateInternal newPkgSetting) {
@@ -1180,7 +1173,7 @@
     /**
      * Removes a package for consideration when filtering visibility between apps.
      *
-     * @param setting the setting of the package being removed.
+     * @param setting   the setting of the package being removed.
      * @param isReplace if the package is being replaced.
      */
     public void removePackage(PackageStateInternal setting, boolean isReplace) {
@@ -1253,43 +1246,41 @@
                 }
             }
 
-            synchronized (mCacheLock) {
-                removeAppIdFromVisibilityCache(setting.getAppId());
-                if (mShouldFilterCache != null && setting.hasSharedUser()) {
-                    final ArraySet<PackageStateInternal> sharedUserPackages =
-                            mPmInternal.getSharedUserPackages(setting.getSharedUserAppId());
-                    for (int i = sharedUserPackages.size() - 1; i >= 0; i--) {
-                        PackageStateInternal siblingSetting =
-                                sharedUserPackages.valueAt(i);
-                        if (siblingSetting == setting) {
+            removeAppIdFromVisibilityCache(setting.getAppId());
+            if (mSystemReady && setting.hasSharedUser()) {
+                final ArraySet<PackageStateInternal> sharedUserPackages =
+                        mPmInternal.getSharedUserPackages(setting.getSharedUserAppId());
+                for (int i = sharedUserPackages.size() - 1; i >= 0; i--) {
+                    PackageStateInternal siblingSetting =
+                            sharedUserPackages.valueAt(i);
+                    if (siblingSetting == setting) {
+                        continue;
+                    }
+                    updateShouldFilterCacheForPackage(
+                            setting.getPackageName(), siblingSetting, settings, users,
+                            USER_ALL, settings.size());
+                }
+            }
+
+            if (mSystemReady) {
+                if (additionalChangedPackages != null) {
+                    for (int index = 0; index < additionalChangedPackages.size(); index++) {
+                        String changedPackage = additionalChangedPackages.valueAt(index);
+                        PackageStateInternal changedPkgSetting = settings.get(changedPackage);
+                        if (changedPkgSetting == null) {
+                            // It's possible for the overlay mapper to know that an actor
+                            // package changed via an explicit reference, even if the actor
+                            // isn't installed, so skip if that's the case.
                             continue;
                         }
-                        updateShouldFilterCacheForPackage(mShouldFilterCache,
-                                setting.getPackageName(), siblingSetting, settings, users,
-                                USER_ALL, settings.size());
+
+                        updateShouldFilterCacheForPackage(null,
+                                changedPkgSetting, settings, users, USER_ALL, settings.size());
                     }
                 }
-
-                if (mShouldFilterCache != null) {
-                    if (additionalChangedPackages != null) {
-                        for (int index = 0; index < additionalChangedPackages.size(); index++) {
-                            String changedPackage = additionalChangedPackages.valueAt(index);
-                            PackageStateInternal changedPkgSetting = settings.get(changedPackage);
-                            if (changedPkgSetting == null) {
-                                // It's possible for the overlay mapper to know that an actor
-                                // package changed via an explicit reference, even if the actor
-                                // isn't installed, so skip if that's the case.
-                                continue;
-                            }
-
-                            updateShouldFilterCacheForPackage(mShouldFilterCache, null,
-                                    changedPkgSetting, settings, users, USER_ALL, settings.size());
-                        }
-                    }
-                }
-
-                onChanged();
             }
+
+            onChanged();
         });
     }
 
@@ -1315,29 +1306,16 @@
                     || callingAppId == targetPkgSetting.getAppId()) {
                 return false;
             }
-            synchronized (mCacheLock) {
-                if (mShouldFilterCache != null) { // use cache
-                    final int callingIndex = mShouldFilterCache.indexOfKey(callingUid);
-                    if (callingIndex < 0) {
-                        Slog.wtf(TAG, "Encountered calling uid with no cached rules: "
-                                + callingUid);
-                        return true;
-                    }
-                    final int targetUid = UserHandle.getUid(userId, targetPkgSetting.getAppId());
-                    final int targetIndex = mShouldFilterCache.indexOfKey(targetUid);
-                    if (targetIndex < 0) {
-                        Slog.w(TAG, "Encountered calling -> target with no cached rules: "
-                                + callingUid + " -> " + targetUid);
-                        return true;
-                    }
-                    if (!mShouldFilterCache.valueAt(callingIndex, targetIndex)) {
-                        return false;
-                    }
-                } else {
-                    if (!shouldFilterApplicationInternal(
-                            callingUid, callingSetting, targetPkgSetting, userId)) {
-                        return false;
-                    }
+            if (mSystemReady) { // use cache
+                if (!shouldFilterApplicationUsingCache(callingUid,
+                        targetPkgSetting.getAppId(),
+                        userId)) {
+                    return false;
+                }
+            } else {
+                if (!shouldFilterApplicationInternal(
+                        callingUid, callingSetting, targetPkgSetting, userId)) {
+                    return false;
                 }
             }
             if (DEBUG_LOGGING || mFeatureConfig.isLoggingEnabled(callingAppId)) {
@@ -1351,6 +1329,25 @@
         }
     }
 
+    private boolean shouldFilterApplicationUsingCache(int callingUid, int appId, int userId) {
+        synchronized (mCacheLock) {
+            final int callingIndex = mShouldFilterCache.indexOfKey(callingUid);
+            if (callingIndex < 0) {
+                Slog.wtf(TAG, "Encountered calling uid with no cached rules: "
+                        + callingUid);
+                return true;
+            }
+            final int targetUid = UserHandle.getUid(userId, appId);
+            final int targetIndex = mShouldFilterCache.indexOfKey(targetUid);
+            if (targetIndex < 0) {
+                Slog.w(TAG, "Encountered calling -> target with no cached rules: "
+                        + callingUid + " -> " + targetUid);
+                return true;
+            }
+            return mShouldFilterCache.valueAt(callingIndex, targetIndex);
+        }
+    }
+
     private boolean shouldFilterApplicationInternal(int callingUid, Object callingSetting,
             PackageStateInternal targetPkgSetting, int targetUserId) {
         if (DEBUG_TRACING) {
@@ -1437,10 +1434,10 @@
                     Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "requestsQueryAllPackages");
                 }
                 if (callingPkgSetting != null) {
-                        if (callingPkgSetting.getPkg() != null
-                                && requestsQueryAllPackages(callingPkgSetting.getPkg())) {
-                            return false;
-                        }
+                    if (callingPkgSetting.getPkg() != null
+                            && requestsQueryAllPackages(callingPkgSetting.getPkg())) {
+                        return false;
+                    }
                 } else {
                     for (int i = callingSharedPkgSettings.size() - 1; i >= 0; i--) {
                         AndroidPackage pkg = callingSharedPkgSettings.valueAt(i).getPkg();
@@ -1747,6 +1744,7 @@
 
     private interface ToString<T> {
         String toString(T input);
+
     }
 
     private static <T> void dumpPackageSet(PrintWriter pw, @Nullable T filteringId,
diff --git a/services/core/java/com/android/server/pm/BroadcastHelper.java b/services/core/java/com/android/server/pm/BroadcastHelper.java
index bf1196d..ed71f1e 100644
--- a/services/core/java/com/android/server/pm/BroadcastHelper.java
+++ b/services/core/java/com/android/server/pm/BroadcastHelper.java
@@ -20,7 +20,6 @@
 import static android.os.PowerExemptionManager.TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_ALLOWED;
 
 import static com.android.server.pm.PackageManagerService.DEBUG_INSTALL;
-import static com.android.server.pm.PackageManagerService.EMPTY_INT_ARRAY;
 import static com.android.server.pm.PackageManagerService.PACKAGE_SCHEME;
 import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
 import static com.android.server.pm.PackageManagerService.TAG;
@@ -86,11 +85,14 @@
             } else {
                 resolvedUserIds = userIds;
             }
-            doSendBroadcast(action, pkg, extras, flags, targetPkg, finishedReceiver,
-                    resolvedUserIds, false, broadcastAllowList, bOptions);
-            if (instantUserIds != null && instantUserIds != EMPTY_INT_ARRAY) {
+
+            if (ArrayUtils.isEmpty(instantUserIds)) {
                 doSendBroadcast(action, pkg, extras, flags, targetPkg, finishedReceiver,
-                        instantUserIds, true, null, bOptions);
+                        resolvedUserIds, false /* isInstantApp */, broadcastAllowList, bOptions);
+            } else {
+                // send restricted broadcasts for instant apps
+                doSendBroadcast(action, pkg, extras, flags, targetPkg, finishedReceiver,
+                        instantUserIds, true /* isInstantApp */, null, bOptions);
             }
         } catch (RemoteException ex) {
         }
diff --git a/services/core/java/com/android/server/pm/DexOptHelper.java b/services/core/java/com/android/server/pm/DexOptHelper.java
index bb2ba5c..249099d 100644
--- a/services/core/java/com/android/server/pm/DexOptHelper.java
+++ b/services/core/java/com/android/server/pm/DexOptHelper.java
@@ -358,9 +358,7 @@
         }
         final long callingId = Binder.clearCallingIdentity();
         try {
-            synchronized (mPm.mInstallLock) {
-                return performDexOptInternalWithDependenciesLI(p, pkgSetting, options);
-            }
+            return performDexOptInternalWithDependenciesLI(p, pkgSetting, options);
         } finally {
             Binder.restoreCallingIdentity(callingId);
         }
@@ -429,20 +427,18 @@
             throw new IllegalArgumentException("Unknown package: " + packageName);
         }
 
-        synchronized (mPm.mInstallLock) {
-            Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "dexopt");
+        Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "dexopt");
 
-            // Whoever is calling forceDexOpt wants a compiled package.
-            // Don't use profiles since that may cause compilation to be skipped.
-            final int res = performDexOptInternalWithDependenciesLI(pkg, packageState,
-                    new DexoptOptions(packageName,
-                            getDefaultCompilerFilter(),
-                            DexoptOptions.DEXOPT_FORCE | DexoptOptions.DEXOPT_BOOT_COMPLETE));
+        // Whoever is calling forceDexOpt wants a compiled package.
+        // Don't use profiles since that may cause compilation to be skipped.
+        final int res = performDexOptInternalWithDependenciesLI(pkg, packageState,
+                new DexoptOptions(packageName,
+                        getDefaultCompilerFilter(),
+                        DexoptOptions.DEXOPT_FORCE | DexoptOptions.DEXOPT_BOOT_COMPLETE));
 
-            Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
-            if (res != PackageDexOptimizer.DEX_OPT_PERFORMED) {
-                throw new IllegalStateException("Failed to dexopt: " + res);
-            }
+        Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
+        if (res != PackageDexOptimizer.DEX_OPT_PERFORMED) {
+            throw new IllegalStateException("Failed to dexopt: " + res);
         }
     }
 
diff --git a/services/core/java/com/android/server/pm/InitAppsHelper.java b/services/core/java/com/android/server/pm/InitAppsHelper.java
index 15f26e7..154f32a 100644
--- a/services/core/java/com/android/server/pm/InitAppsHelper.java
+++ b/services/core/java/com/android/server/pm/InitAppsHelper.java
@@ -30,7 +30,7 @@
 import static com.android.server.pm.PackageManagerService.SCAN_REQUIRE_KNOWN;
 import static com.android.server.pm.PackageManagerService.SYSTEM_PARTITIONS;
 import static com.android.server.pm.PackageManagerService.TAG;
-import static com.android.server.pm.pkg.parsing.ParsingPackageUtils.PARSE_CHECK_MAX_SDK_VERSION;
+import static com.android.server.pm.pkg.parsing.ParsingPackageUtils.PARSE_APK_IN_APEX;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -359,7 +359,7 @@
         try {
             if ((scanFlags & SCAN_AS_APK_IN_APEX) != 0) {
                 // when scanning apk in apexes, we want to check the maxSdkVersion
-                parseFlags |= PARSE_CHECK_MAX_SDK_VERSION;
+                parseFlags |= PARSE_APK_IN_APEX;
             }
             mInstallPackageHelper.installPackagesFromDir(scanDir, frameworkSplits, parseFlags,
                     scanFlags, packageParser, executorService);
diff --git a/services/core/java/com/android/server/pm/IntentResolverInterceptor.java b/services/core/java/com/android/server/pm/IntentResolverInterceptor.java
index 603badb..f5eee5a 100644
--- a/services/core/java/com/android/server/pm/IntentResolverInterceptor.java
+++ b/services/core/java/com/android/server/pm/IntentResolverInterceptor.java
@@ -23,11 +23,12 @@
 import android.annotation.RequiresPermission;
 import android.content.ComponentName;
 import android.content.Context;
-import android.content.Intent;
 import android.content.res.Resources;
 import android.provider.DeviceConfig;
+import android.util.Slog;
 
 import com.android.internal.R;
+import com.android.internal.app.ChooserActivity;
 import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
 import com.android.server.LocalServices;
 import com.android.server.wm.ActivityInterceptorCallback;
@@ -35,25 +36,28 @@
 import com.android.server.wm.ActivityTaskManagerInternal;
 
 /**
- * Service to register an {@code ActivityInterceptorCallback} that modifies any {@code Intent}
- * that's being used to launch a user-space {@code ChooserActivity} by setting the destination
- * component to the delegated component when appropriate.
+ * Redirects Activity starts for the system bundled {@link ChooserActivity} to an external
+ * Sharesheet implementation by modifying the target component when appropriate.
+ * <p>
+ * Note: config_chooserActivity (Used also by ActivityTaskSupervisor) is already updated to point
+ * to the new instance. This value is read and used for the new target component.
  */
 public final class IntentResolverInterceptor {
     private static final String TAG = "IntentResolverIntercept";
-
     private final Context mContext;
-    private boolean mUseDelegateChooser;
+    private final ComponentName mFrameworkChooserComponent;
+    private final ComponentName mUnbundledChooserComponent;
+    private boolean mUseUnbundledSharesheet;
 
     private final ActivityInterceptorCallback mActivityInterceptorCallback =
             new ActivityInterceptorCallback() {
                 @Nullable
                 @Override
                 public ActivityInterceptResult intercept(ActivityInterceptorInfo info) {
-                    if (mUseDelegateChooser && isChooserActivity(info)) {
-                        return new ActivityInterceptResult(
-                                modifyChooserIntent(info.intent),
-                                info.checkedOptions);
+                    if (mUseUnbundledSharesheet && isSystemChooserActivity(info)) {
+                        Slog.d(TAG, "Redirecting to UNBUNDLED Sharesheet");
+                        info.intent.setComponent(mUnbundledChooserComponent);
+                        return new ActivityInterceptResult(info.intent, info.checkedOptions);
                     }
                     return null;
                 }
@@ -61,10 +65,13 @@
 
     public IntentResolverInterceptor(Context context) {
         mContext = context;
+        mFrameworkChooserComponent = new ComponentName(mContext, ChooserActivity.class);
+        mUnbundledChooserComponent =  ComponentName.unflattenFromString(
+                Resources.getSystem().getString(R.string.config_chooserActivity));
     }
 
     /**
-     * Start listening for intents and USE_DELEGATE_CHOOSER property changes.
+     * Start listening for intents and USE_UNBUNDLED_SHARESHEET property changes.
      */
     @RequiresPermission(Manifest.permission.READ_DEVICE_CONFIG)
     public void registerListeners() {
@@ -73,36 +80,25 @@
                         mActivityInterceptorCallback);
 
         DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI,
-                mContext.getMainExecutor(), properties -> updateUseDelegateChooser());
-        updateUseDelegateChooser();
+                mContext.getMainExecutor(), properties -> updateUseUnbundledSharesheet());
+        updateUseUnbundledSharesheet();
     }
 
     @RequiresPermission(Manifest.permission.READ_DEVICE_CONFIG)
-    private void updateUseDelegateChooser() {
-        mUseDelegateChooser = DeviceConfig.getBoolean(
+    private void updateUseUnbundledSharesheet() {
+        mUseUnbundledSharesheet = DeviceConfig.getBoolean(
                 DeviceConfig.NAMESPACE_SYSTEMUI,
                 SystemUiDeviceConfigFlags.USE_UNBUNDLED_SHARESHEET,
                 false);
+        if (mUseUnbundledSharesheet) {
+            Slog.d(TAG, "using UNBUNDLED Sharesheet");
+        } else {
+            Slog.d(TAG, "using FRAMEWORK Sharesheet");
+        }
     }
 
-    private Intent modifyChooserIntent(Intent intent) {
-        intent.setComponent(getUnbundledChooserComponentName());
-        return intent;
-    }
-
-    private static boolean isChooserActivity(ActivityInterceptorInfo info) {
-        ComponentName targetComponent = new ComponentName(info.aInfo.packageName, info.aInfo.name);
-
-        return targetComponent.equals(getSystemChooserComponentName())
-                || targetComponent.equals(getUnbundledChooserComponentName());
-    }
-
-    private static ComponentName getSystemChooserComponentName() {
-        return new ComponentName("android", "com.android.internal.app.ChooserActivity");
-    }
-
-    private static ComponentName getUnbundledChooserComponentName() {
-        return ComponentName.unflattenFromString(
-                Resources.getSystem().getString(R.string.config_chooserActivity));
+    private boolean isSystemChooserActivity(ActivityInterceptorInfo info) {
+        return mFrameworkChooserComponent.getPackageName().equals(info.aInfo.packageName)
+                && mFrameworkChooserComponent.getClassName().equals(info.aInfo.name);
     }
 }
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index bf80a46..f6d8d72 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -3536,6 +3536,8 @@
                     EventLog.writeEvent(0x534e4554, "145981139", packageInfo.applicationInfo.uid,
                             "");
                 }
+                Log.w(TAG, "Missing required system package: " + packageName + (packageInfo != null
+                        ? ", but found with extended search." : "."));
                 return null;
             }
         } finally {
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index e142ed6..d6ab78b 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -3694,7 +3694,7 @@
             }
             List<PermissionInfo> ps = mPermissionManager
                     .queryPermissionsByGroup(groupList.get(i), 0 /*flags*/);
-            final int count = ps.size();
+            final int count = (ps == null ? 0 : ps.size());
             boolean first = true;
             for (int p = 0 ; p < count ; p++) {
                 PermissionInfo pi = ps.get(p);
diff --git a/services/core/java/com/android/server/pm/dex/ArtManagerService.java b/services/core/java/com/android/server/pm/dex/ArtManagerService.java
index 7e4da94..4fae6b8 100644
--- a/services/core/java/com/android/server/pm/dex/ArtManagerService.java
+++ b/services/core/java/com/android/server/pm/dex/ArtManagerService.java
@@ -45,7 +45,6 @@
 import android.util.Log;
 import android.util.Slog;
 
-import com.android.internal.annotations.GuardedBy;
 import com.android.internal.os.BackgroundThread;
 import com.android.internal.os.RoSystemProperties;
 import com.android.internal.util.ArrayUtils;
@@ -94,8 +93,6 @@
 
     private final Context mContext;
     private IPackageManager mPackageManager;
-    private final Object mInstallLock;
-    @GuardedBy("mInstallLock")
     private final Installer mInstaller;
 
     private final Handler mHandler;
@@ -105,10 +102,9 @@
     }
 
     public ArtManagerService(Context context, Installer installer,
-            Object installLock) {
+            Object ignored) {
         mContext = context;
         mInstaller = installer;
-        mInstallLock = installLock;
         mHandler = new Handler(BackgroundThread.getHandler().getLooper());
 
         LocalServices.addService(ArtManagerInternal.class, new ArtManagerInternalImpl());
@@ -273,16 +269,14 @@
     private void createProfileSnapshot(String packageName, String profileName, String classpath,
             int appId, ISnapshotRuntimeProfileCallback callback) {
         // Ask the installer to snapshot the profile.
-        synchronized (mInstallLock) {
-            try {
-                if (!mInstaller.createProfileSnapshot(appId, packageName, profileName, classpath)) {
-                    postError(callback, packageName, ArtManager.SNAPSHOT_FAILED_INTERNAL_ERROR);
-                    return;
-                }
-            } catch (InstallerException e) {
+        try {
+            if (!mInstaller.createProfileSnapshot(appId, packageName, profileName, classpath)) {
                 postError(callback, packageName, ArtManager.SNAPSHOT_FAILED_INTERNAL_ERROR);
                 return;
             }
+        } catch (InstallerException e) {
+            postError(callback, packageName, ArtManager.SNAPSHOT_FAILED_INTERNAL_ERROR);
+            return;
         }
 
         // Open the snapshot and invoke the callback.
@@ -308,13 +302,11 @@
             Slog.d(TAG, "Destroying profile snapshot for" + packageName + ":" + profileName);
         }
 
-        synchronized (mInstallLock) {
-            try {
-                mInstaller.destroyProfileSnapshot(packageName, profileName);
-            } catch (InstallerException e) {
-                Slog.e(TAG, "Failed to destroy profile snapshot for " +
-                    packageName + ":" + profileName, e);
-            }
+        try {
+            mInstaller.destroyProfileSnapshot(packageName, profileName);
+        } catch (InstallerException e) {
+            Slog.e(TAG, "Failed to destroy profile snapshot for " + packageName + ":" + profileName,
+                    e);
         }
     }
 
@@ -480,9 +472,7 @@
             for (int i = packageProfileNames.size() - 1; i >= 0; i--) {
                 String codePath = packageProfileNames.keyAt(i);
                 String profileName = packageProfileNames.valueAt(i);
-                synchronized (mInstallLock) {
-                    mInstaller.dumpProfiles(sharedGid, pkg.getPackageName(), profileName, codePath);
-                }
+                mInstaller.dumpProfiles(sharedGid, pkg.getPackageName(), profileName, codePath);
             }
         } catch (InstallerException e) {
             Slog.w(TAG, "Failed to dump profiles", e);
@@ -512,10 +502,8 @@
                     ") to " + outDexFile);
             final long callingId = Binder.clearCallingIdentity();
             try {
-                synchronized (mInstallLock) {
-                    return mInstaller.compileLayouts(apkPath, packageName, outDexFile,
-                            pkg.getUid());
-                }
+                return mInstaller.compileLayouts(apkPath, packageName, outDexFile,
+                        pkg.getUid());
             } finally {
                 Binder.restoreCallingIdentity(callingId);
             }
diff --git a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java
index f255db4..9897c42 100644
--- a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java
+++ b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java
@@ -209,8 +209,6 @@
 
     public static final int SDK_VERSION = Build.VERSION.SDK_INT;
     public static final String[] SDK_CODENAMES = Build.VERSION.ACTIVE_CODENAMES;
-    public static final String[] PREVIOUS_CODENAMES =
-            Build.VERSION.KNOWN_CODENAMES.toArray(new String[]{});
 
     public static boolean sCompatibilityModeEnabled = true;
     public static boolean sUseRoundIcon = false;
@@ -238,7 +236,7 @@
      */
     public static final int PARSE_IGNORE_OVERLAY_REQUIRED_SYSTEM_PROPERTY = 1 << 7;
     public static final int PARSE_FRAMEWORK_RES_SPLITS = 1 << 8;
-    public static final int PARSE_CHECK_MAX_SDK_VERSION = 1 << 9;
+    public static final int PARSE_APK_IN_APEX = 1 << 9;
 
     public static final int PARSE_CHATTY = 1 << 31;
 
@@ -1534,7 +1532,7 @@
             ParsingPackage pkg, Resources res, XmlResourceParser parser, int flags)
             throws IOException, XmlPullParserException {
         if (SDK_VERSION > 0) {
-            final boolean checkMaxSdkVersion = (flags & PARSE_CHECK_MAX_SDK_VERSION) != 0;
+            final boolean isApkInApex = (flags & PARSE_APK_IN_APEX) != 0;
             TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestUsesSdk);
             try {
                 int minVers = ParsingUtils.DEFAULT_MIN_SDK_VERSION;
@@ -1569,7 +1567,7 @@
                     targetCode = minCode;
                 }
 
-                if (checkMaxSdkVersion) {
+                if (isApkInApex) {
                     val = sa.peekValue(R.styleable.AndroidManifestUsesSdk_maxSdkVersion);
                     if (val != null) {
                         // maxSdkVersion only supports integer
@@ -1578,7 +1576,8 @@
                 }
 
                 ParseResult<Integer> targetSdkVersionResult = FrameworkParsingPackageUtils
-                        .computeTargetSdkVersion(targetVers, targetCode, SDK_CODENAMES, input);
+                        .computeTargetSdkVersion(targetVers, targetCode, SDK_CODENAMES, input,
+                                isApkInApex);
                 if (targetSdkVersionResult.isError()) {
                     return input.error(targetSdkVersionResult);
                 }
@@ -1601,7 +1600,7 @@
 
                 pkg.setMinSdkVersion(minSdkVersion)
                         .setTargetSdkVersion(targetSdkVersion);
-                if (checkMaxSdkVersion) {
+                if (isApkInApex) {
                     ParseResult<Integer> maxSdkVersionResult = FrameworkParsingPackageUtils
                             .computeMaxSdkVersion(maxVers, SDK_VERSION, input);
                     if (maxSdkVersionResult.isError()) {
diff --git a/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java b/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java
index 2f68f56..bce1cce 100644
--- a/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java
+++ b/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java
@@ -29,6 +29,7 @@
 import static android.content.Intent.EXTRA_PACKAGE_NAME;
 import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
 import static android.content.Intent.FLAG_ACTIVITY_NO_USER_ACTION;
+import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 import static android.hardware.SensorPrivacyManager.EXTRA_ALL_SENSORS;
 import static android.hardware.SensorPrivacyManager.EXTRA_SENSOR;
@@ -77,6 +78,7 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
 import android.content.res.Configuration;
 import android.graphics.drawable.Icon;
 import android.hardware.ISensorPrivacyListener;
@@ -155,6 +157,7 @@
     private final AppOpsManager mAppOpsManager;
     private final AppOpsManagerInternal mAppOpsManagerInternal;
     private final TelephonyManager mTelephonyManager;
+    private final PackageManagerInternal mPackageManagerInternal;
 
     private CameraPrivacyLightController mCameraPrivacyLightController;
 
@@ -178,6 +181,7 @@
         mActivityManagerInternal = getLocalService(ActivityManagerInternal.class);
         mActivityTaskManager = context.getSystemService(ActivityTaskManager.class);
         mTelephonyManager = context.getSystemService(TelephonyManager.class);
+        mPackageManagerInternal = getLocalService(PackageManagerInternal.class);
         mSensorPrivacyServiceImpl = new SensorPrivacyServiceImpl();
     }
 
@@ -828,6 +832,12 @@
          * sensor privacy.
          */
         private void enforceObserveSensorPrivacyPermission() {
+            String systemUIPackage = mContext.getString(R.string.config_systemUi);
+            if (Binder.getCallingUid() == mPackageManagerInternal
+                    .getPackageUid(systemUIPackage, MATCH_SYSTEM_ONLY, UserHandle.USER_SYSTEM)) {
+                // b/221782106, possible race condition with role grant might bootloop device.
+                return;
+            }
             enforcePermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY,
                     "Observing sensor privacy changes requires the following permission: "
                             + android.Manifest.permission.OBSERVE_SENSOR_PRIVACY);
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index 8087738..b685572 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -903,11 +903,11 @@
     }
 
     @Override
-    public void hideAuthenticationDialog() {
+    public void hideAuthenticationDialog(long requestId) {
         enforceBiometricDialog();
         if (mBar != null) {
             try {
-                mBar.hideAuthenticationDialog();
+                mBar.hideAuthenticationDialog(requestId);
             } catch (RemoteException ex) {
             }
         }
diff --git a/services/core/java/com/android/server/vibrator/AbstractVibratorStep.java b/services/core/java/com/android/server/vibrator/AbstractVibratorStep.java
index 3550bda..12e68b1 100644
--- a/services/core/java/com/android/server/vibrator/AbstractVibratorStep.java
+++ b/services/core/java/com/android/server/vibrator/AbstractVibratorStep.java
@@ -141,8 +141,16 @@
      */
     protected List<Step> nextSteps(long nextStartTime, long vibratorOffTimeout,
             int segmentsPlayed) {
+        int nextSegmentIndex = segmentIndex + segmentsPlayed;
+        int effectSize = effect.getSegments().size();
+        int repeatIndex = effect.getRepeatIndex();
+        if (nextSegmentIndex >= effectSize && repeatIndex >= 0) {
+            // Count the loops that were played.
+            int loopSize = effectSize - repeatIndex;
+            nextSegmentIndex = repeatIndex + ((nextSegmentIndex - effectSize) % loopSize);
+        }
         Step nextStep = conductor.nextVibrateStep(nextStartTime, controller, effect,
-                segmentIndex + segmentsPlayed, vibratorOffTimeout);
+                nextSegmentIndex, vibratorOffTimeout);
         return nextStep == null ? VibrationStepConductor.EMPTY_STEP_LIST : Arrays.asList(nextStep);
     }
 }
diff --git a/services/core/java/com/android/server/vibrator/ComposePrimitivesVibratorStep.java b/services/core/java/com/android/server/vibrator/ComposePrimitivesVibratorStep.java
index d1ea805..3bc11c8 100644
--- a/services/core/java/com/android/server/vibrator/ComposePrimitivesVibratorStep.java
+++ b/services/core/java/com/android/server/vibrator/ComposePrimitivesVibratorStep.java
@@ -32,6 +32,11 @@
  * {@link PrimitiveSegment} starting at the current index.
  */
 final class ComposePrimitivesVibratorStep extends AbstractVibratorStep {
+    /**
+     * Default limit to the number of primitives in a composition, if none is defined by the HAL,
+     * to prevent repeating effects from generating an infinite list.
+     */
+    private static final int DEFAULT_COMPOSITION_SIZE_LIMIT = 100;
 
     ComposePrimitivesVibratorStep(VibrationStepConductor conductor, long startTime,
             VibratorController controller, VibrationEffect.Composed effect, int index,
@@ -49,18 +54,8 @@
             // Load the next PrimitiveSegments to create a single compose call to the vibrator,
             // limited to the vibrator composition maximum size.
             int limit = controller.getVibratorInfo().getCompositionSizeMax();
-            int segmentCount = limit > 0
-                    ? Math.min(effect.getSegments().size(), segmentIndex + limit)
-                    : effect.getSegments().size();
-            List<PrimitiveSegment> primitives = new ArrayList<>();
-            for (int i = segmentIndex; i < segmentCount; i++) {
-                VibrationEffectSegment segment = effect.getSegments().get(i);
-                if (segment instanceof PrimitiveSegment) {
-                    primitives.add((PrimitiveSegment) segment);
-                } else {
-                    break;
-                }
-            }
+            List<PrimitiveSegment> primitives = unrollPrimitiveSegments(effect, segmentIndex,
+                    limit > 0 ? limit : DEFAULT_COMPOSITION_SIZE_LIMIT);
 
             if (primitives.isEmpty()) {
                 Slog.w(VibrationThread.TAG, "Ignoring wrong segment for a ComposePrimitivesStep: "
@@ -81,4 +76,44 @@
             Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
         }
     }
+
+    /**
+     * Get the primitive segments to be played by this step as a single composition, starting at
+     * {@code startIndex} until:
+     *
+     * <ol>
+     *     <li>There are no more segments in the effect;
+     *     <li>The first non-primitive segment is found;
+     *     <li>The given limit to the composition size is reached.
+     * </ol>
+     *
+     * <p>If the effect is repeating then this method will generate the largest composition within
+     * given limit.
+     */
+    private List<PrimitiveSegment> unrollPrimitiveSegments(VibrationEffect.Composed effect,
+            int startIndex, int limit) {
+        List<PrimitiveSegment> segments = new ArrayList<>(limit);
+        int segmentCount = effect.getSegments().size();
+        int repeatIndex = effect.getRepeatIndex();
+
+        for (int i = startIndex; segments.size() < limit; i++) {
+            if (i == segmentCount) {
+                if (repeatIndex >= 0) {
+                    i = repeatIndex;
+                } else {
+                    // Non-repeating effect, stop collecting primitives.
+                    break;
+                }
+            }
+            VibrationEffectSegment segment = effect.getSegments().get(i);
+            if (segment instanceof PrimitiveSegment) {
+                segments.add((PrimitiveSegment) segment);
+            } else {
+                // First non-primitive segment, stop collecting primitives.
+                break;
+            }
+        }
+
+        return segments;
+    }
 }
diff --git a/services/core/java/com/android/server/vibrator/ComposePwleVibratorStep.java b/services/core/java/com/android/server/vibrator/ComposePwleVibratorStep.java
index 73bf933..919f1be 100644
--- a/services/core/java/com/android/server/vibrator/ComposePwleVibratorStep.java
+++ b/services/core/java/com/android/server/vibrator/ComposePwleVibratorStep.java
@@ -33,6 +33,11 @@
  * {@link StepSegment} or {@link RampSegment} starting at the current index.
  */
 final class ComposePwleVibratorStep extends AbstractVibratorStep {
+    /**
+     * Default limit to the number of PWLE segments, if none is defined by the HAL, to prevent
+     * repeating effects from generating an infinite list.
+     */
+    private static final int DEFAULT_PWLE_SIZE_LIMIT = 100;
 
     ComposePwleVibratorStep(VibrationStepConductor conductor, long startTime,
             VibratorController controller, VibrationEffect.Composed effect, int index,
@@ -50,18 +55,8 @@
             // Load the next RampSegments to create a single composePwle call to the vibrator,
             // limited to the vibrator PWLE maximum size.
             int limit = controller.getVibratorInfo().getPwleSizeMax();
-            int segmentCount = limit > 0
-                    ? Math.min(effect.getSegments().size(), segmentIndex + limit)
-                    : effect.getSegments().size();
-            List<RampSegment> pwles = new ArrayList<>();
-            for (int i = segmentIndex; i < segmentCount; i++) {
-                VibrationEffectSegment segment = effect.getSegments().get(i);
-                if (segment instanceof RampSegment) {
-                    pwles.add((RampSegment) segment);
-                } else {
-                    break;
-                }
-            }
+            List<RampSegment> pwles = unrollRampSegments(effect, segmentIndex,
+                    limit > 0 ? limit : DEFAULT_PWLE_SIZE_LIMIT);
 
             if (pwles.isEmpty()) {
                 Slog.w(VibrationThread.TAG, "Ignoring wrong segment for a ComposePwleStep: "
@@ -81,4 +76,88 @@
             Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
         }
     }
+
+    /**
+     * Get the ramp segments to be played by this step for a waveform, starting at
+     * {@code startIndex} until:
+     *
+     * <ol>
+     *     <li>There are no more segments in the effect;
+     *     <li>The first non-ramp segment is found;
+     *     <li>The given limit to the PWLE size is reached.
+     * </ol>
+     *
+     * <p>If the effect is repeating then this method will generate the largest PWLE within given
+     * limit. This will also optimize to end the list at a ramp to zero-amplitude, if possible, and
+     * avoid braking down the effect in non-zero amplitude.
+     */
+    private List<RampSegment> unrollRampSegments(VibrationEffect.Composed effect, int startIndex,
+            int limit) {
+        List<RampSegment> segments = new ArrayList<>(limit);
+        float bestBreakAmplitude = 1;
+        int bestBreakPosition = limit; // Exclusive index.
+
+        int segmentCount = effect.getSegments().size();
+        int repeatIndex = effect.getRepeatIndex();
+
+        // Loop once after reaching the limit to see if breaking it will really be necessary, then
+        // apply the best break position found, otherwise return the full list as it fits the limit.
+        for (int i = startIndex; segments.size() <= limit; i++) {
+            if (i == segmentCount) {
+                if (repeatIndex >= 0) {
+                    i = repeatIndex;
+                } else {
+                    // Non-repeating effect, stop collecting ramps.
+                    break;
+                }
+            }
+            VibrationEffectSegment segment = effect.getSegments().get(i);
+            if (segment instanceof RampSegment) {
+                RampSegment rampSegment = (RampSegment) segment;
+                segments.add(rampSegment);
+
+                if (isBetterBreakPosition(segments, bestBreakAmplitude, limit)) {
+                    // Mark this position as the best one so far to break a long waveform.
+                    bestBreakAmplitude = rampSegment.getEndAmplitude();
+                    bestBreakPosition = segments.size(); // Break after this ramp ends.
+                }
+            } else {
+                // First non-ramp segment, stop collecting ramps.
+                break;
+            }
+        }
+
+        return segments.size() > limit
+                // Remove excessive segments, using the best breaking position recorded.
+                ? segments.subList(0, bestBreakPosition)
+                // Return all collected ramp segments.
+                : segments;
+    }
+
+    /**
+     * Returns true if the current segment list represents a better break position for a PWLE,
+     * given the current amplitude being used for breaking it at a smaller size and the size limit.
+     */
+    private boolean isBetterBreakPosition(List<RampSegment> segments,
+            float currentBestBreakAmplitude, int limit) {
+        RampSegment lastSegment = segments.get(segments.size() - 1);
+        float breakAmplitudeCandidate = lastSegment.getEndAmplitude();
+        int breakPositionCandidate = segments.size();
+
+        if (breakPositionCandidate > limit) {
+            // We're beyond limit, last break position found should be used.
+            return false;
+        }
+        if (breakAmplitudeCandidate == 0) {
+            // Breaking at amplitude zero at any position is always preferable.
+            return true;
+        }
+        if (breakPositionCandidate < limit / 2) {
+            // Avoid breaking at the first half of the allowed maximum size, even if amplitudes are
+            // lower, to avoid creating PWLEs that are too small unless it's to break at zero.
+            return false;
+        }
+        // Prefer lower amplitudes at a later position for breaking the PWLE in a more subtle way.
+        return breakAmplitudeCandidate <= currentBestBreakAmplitude;
+    }
 }
diff --git a/services/core/java/com/android/server/vibrator/SetAmplitudeVibratorStep.java b/services/core/java/com/android/server/vibrator/SetAmplitudeVibratorStep.java
index d5c1116..1f0d2d7 100644
--- a/services/core/java/com/android/server/vibrator/SetAmplitudeVibratorStep.java
+++ b/services/core/java/com/android/server/vibrator/SetAmplitudeVibratorStep.java
@@ -33,6 +33,12 @@
  * and amplitude to simulate waveforms represented by a sequence of {@link StepSegment}.
  */
 final class SetAmplitudeVibratorStep extends AbstractVibratorStep {
+    /**
+     * The repeating waveform keeps the vibrator ON all the time. Use a minimum duration to
+     * prevent short patterns from turning the vibrator ON too frequently.
+     */
+    private static final int REPEATING_EFFECT_ON_DURATION = 5000; // 5s
+
     private long mNextOffTime;
 
     SetAmplitudeVibratorStep(VibrationStepConductor conductor, long startTime,
@@ -170,10 +176,7 @@
                 repeatIndex = -1;
             }
             if (i == startIndex) {
-                // The repeating waveform keeps the vibrator ON all the time. Use a minimum
-                // of 1s duration to prevent short patterns from turning the vibrator ON too
-                // frequently.
-                return Math.max(timing, 1000);
+                return Math.max(timing, REPEATING_EFFECT_ON_DURATION);
             }
         }
         if (i == segmentCount && effect.getRepeatIndex() < 0) {
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index 31b5579..17016a6 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -106,7 +106,6 @@
     jmethodID interceptMotionBeforeQueueingNonInteractive;
     jmethodID interceptKeyBeforeDispatching;
     jmethodID dispatchUnhandledKey;
-    jmethodID checkInjectEventsPermission;
     jmethodID onPointerDownOutsideFocus;
     jmethodID getVirtualKeyQuietTimeMillis;
     jmethodID getExcludedDeviceNames;
@@ -329,7 +328,6 @@
     bool dispatchUnhandledKey(const sp<IBinder>& token, const KeyEvent* keyEvent,
                               uint32_t policyFlags, KeyEvent* outFallbackKeyEvent) override;
     void pokeUserActivity(nsecs_t eventTime, int32_t eventType, int32_t displayId) override;
-    bool checkInjectEventsPermissionNonReentrant(int32_t injectorPid, int32_t injectorUid) override;
     void onPointerDownOutsideFocus(const sp<IBinder>& touchedToken) override;
     void setPointerCapture(const PointerCaptureRequest& request) override;
     void notifyDropWindow(const sp<IBinder>& token, float x, float y) override;
@@ -1368,18 +1366,6 @@
     android_server_PowerManagerService_userActivity(eventTime, eventType, displayId);
 }
 
-bool NativeInputManager::checkInjectEventsPermissionNonReentrant(
-        int32_t injectorPid, int32_t injectorUid) {
-    ATRACE_CALL();
-    JNIEnv* env = jniEnv();
-    jboolean result = env->CallBooleanMethod(mServiceObj,
-            gServiceClassInfo.checkInjectEventsPermission, injectorPid, injectorUid);
-    if (checkAndClearExceptionFromCallback(env, "checkInjectEventsPermission")) {
-        result = false;
-    }
-    return result;
-}
-
 void NativeInputManager::onPointerDownOutsideFocus(const sp<IBinder>& touchedToken) {
     ATRACE_CALL();
     JNIEnv* env = jniEnv();
@@ -1706,11 +1692,12 @@
             static_cast<BlockUntrustedTouchesMode>(mode));
 }
 
-static jint nativeInjectInputEvent(JNIEnv* env, jclass /* clazz */,
-        jlong ptr, jobject inputEventObj, jint injectorPid, jint injectorUid,
-        jint syncMode, jint timeoutMillis, jint policyFlags) {
+static jint nativeInjectInputEvent(JNIEnv* env, jclass /* clazz */, jlong ptr,
+                                   jobject inputEventObj, jboolean injectIntoUid, jint uid,
+                                   jint syncMode, jint timeoutMillis, jint policyFlags) {
     NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
 
+    const std::optional<int32_t> targetUid = injectIntoUid ? std::make_optional(uid) : std::nullopt;
     // static_cast is safe because the value was already checked at the Java layer
     InputEventInjectionSync mode = static_cast<InputEventInjectionSync>(syncMode);
 
@@ -1723,8 +1710,7 @@
         }
 
         const InputEventInjectionResult result =
-                im->getInputManager()->getDispatcher().injectInputEvent(&keyEvent, injectorPid,
-                                                                        injectorUid, mode,
+                im->getInputManager()->getDispatcher().injectInputEvent(&keyEvent, targetUid, mode,
                                                                         std::chrono::milliseconds(
                                                                                 timeoutMillis),
                                                                         uint32_t(policyFlags));
@@ -1737,8 +1723,8 @@
         }
 
         const InputEventInjectionResult result =
-                im->getInputManager()->getDispatcher().injectInputEvent(motionEvent, injectorPid,
-                                                                        injectorUid, mode,
+                im->getInputManager()->getDispatcher().injectInputEvent(motionEvent, targetUid,
+                                                                        mode,
                                                                         std::chrono::milliseconds(
                                                                                 timeoutMillis),
                                                                         uint32_t(policyFlags));
@@ -2359,7 +2345,7 @@
         {"nativeSetMaximumObscuringOpacityForTouch", "(JF)V",
          (void*)nativeSetMaximumObscuringOpacityForTouch},
         {"nativeSetBlockUntrustedTouchesMode", "(JI)V", (void*)nativeSetBlockUntrustedTouchesMode},
-        {"nativeInjectInputEvent", "(JLandroid/view/InputEvent;IIIII)I",
+        {"nativeInjectInputEvent", "(JLandroid/view/InputEvent;ZIIII)I",
          (void*)nativeInjectInputEvent},
         {"nativeVerifyInputEvent", "(JLandroid/view/InputEvent;)Landroid/view/VerifiedInputEvent;",
          (void*)nativeVerifyInputEvent},
@@ -2499,9 +2485,6 @@
             "dispatchUnhandledKey",
             "(Landroid/os/IBinder;Landroid/view/KeyEvent;I)Landroid/view/KeyEvent;");
 
-    GET_METHOD_ID(gServiceClassInfo.checkInjectEventsPermission, clazz,
-            "checkInjectEventsPermission", "(II)Z");
-
     GET_METHOD_ID(gServiceClassInfo.onPointerDownOutsideFocus, clazz,
             "onPointerDownOutsideFocus", "(Landroid/os/IBinder;)V");
 
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 2b64be2..62447c0 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -13354,8 +13354,12 @@
                 if (receivers.isEmpty()) {
                     return;
                 }
-                packageIntent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
-                mContext.sendBroadcastAsUser(packageIntent, userHandle);
+                for (ResolveInfo receiver : receivers) {
+                    final Intent componentIntent = new Intent(packageIntent)
+                            .setComponent(receiver.getComponentInfo().getComponentName())
+                            .addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
+                    mContext.sendBroadcastAsUser(componentIntent, userHandle);
+                }
             } catch (RemoteException ex) {
                 Slogf.w(LOG_TAG, "Cannot get list of broadcast receivers for %s because: %s.",
                         intent.getAction(), ex);
diff --git a/services/midi/java/com/android/server/midi/MidiService.java b/services/midi/java/com/android/server/midi/MidiService.java
index e1fe1d8..90fd8ed 100644
--- a/services/midi/java/com/android/server/midi/MidiService.java
+++ b/services/midi/java/com/android/server/midi/MidiService.java
@@ -685,11 +685,13 @@
 
     private boolean hasNonMidiUuids(BluetoothDevice btDevice) {
         ParcelUuid[] uuidParcels = btDevice.getUuids();
-        // The assumption is that these services are indicative of devices that
-        // ARE NOT MIDI devices.
-        for (ParcelUuid parcel : uuidParcels) {
-            if (mNonMidiUUIDs.contains(parcel)) {
-                return true;
+        if (uuidParcels != null) {
+            // The assumption is that these services are indicative of devices that
+            // ARE NOT MIDI devices.
+            for (ParcelUuid parcel : uuidParcels) {
+                if (mNonMidiUUIDs.contains(parcel)) {
+                    return true;
+                }
             }
         }
         return false;
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BackgroundRestrictionTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BackgroundRestrictionTest.java
index 77cf543..c0b4f0f 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BackgroundRestrictionTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BackgroundRestrictionTest.java
@@ -671,7 +671,7 @@
             mAppFGSTracker.onForegroundServiceStateChanged(testPkgName, testUid,
                     testPid, true);
             mAppFGSTracker.onForegroundServiceNotificationUpdated(
-                    testPkgName, testUid, notificationId);
+                    testPkgName, testUid, notificationId, false);
             mAppFGSTracker.mNotificationListener.onNotificationPosted(new StatusBarNotification(
                     testPkgName, null, notificationId, null, testUid, testPid,
                     new Notification(), UserHandle.of(testUser), null, mCurrentTimeMillis), null);
@@ -848,7 +848,7 @@
 
             // Pretend we have the notification dismissed.
             mAppFGSTracker.onForegroundServiceNotificationUpdated(
-                    testPkgName, testUid, -notificationId);
+                    testPkgName, testUid, notificationId, true);
             clearInvocations(mInjector.getAppStandbyInternal());
             clearInvocations(mInjector.getNotificationManager());
             clearInvocations(mBgRestrictionController);
@@ -885,7 +885,7 @@
 
             // Pretend notification is back on.
             mAppFGSTracker.onForegroundServiceNotificationUpdated(
-                    testPkgName, testUid, notificationId);
+                    testPkgName, testUid, notificationId, false);
             // Now we'll prompt the user even it has a FGS with notification.
             bgPromptFgsWithNotiToBgRestricted.set(true);
             clearInvocations(mInjector.getAppStandbyInternal());
@@ -1224,7 +1224,7 @@
             mAppFGSTracker.onForegroundServiceStateChanged(testPkgName1, testUid1,
                     testPid1, true);
             mAppFGSTracker.onForegroundServiceNotificationUpdated(
-                    testPkgName1, testUid1, fgsNotificationId);
+                    testPkgName1, testUid1, fgsNotificationId, false);
             mAppFGSTracker.mNotificationListener.onNotificationPosted(new StatusBarNotification(
                     testPkgName1, null, fgsNotificationId, null, testUid1, testPid1,
                     new Notification(), UserHandle.of(testUser1), null, mCurrentTimeMillis), null);
@@ -1235,7 +1235,7 @@
 
             // Pretend we have the notification dismissed.
             mAppFGSTracker.onForegroundServiceNotificationUpdated(
-                    testPkgName1, testUid1, -fgsNotificationId);
+                    testPkgName1, testUid1, fgsNotificationId, true);
 
             // Verify we have the notification.
             notificationId = checkNotificationShown(
@@ -1500,7 +1500,7 @@
         if (withNotification) {
             final int notificationId = 1000;
             mAppFGSTracker.onForegroundServiceNotificationUpdated(
-                    packageName, uid, notificationId);
+                    packageName, uid, notificationId, false);
             final StatusBarNotification noti = new StatusBarNotification(
                     packageName, null, notificationId, null, uid, pid,
                     new Notification(), UserHandle.of(UserHandle.getUserId(uid)),
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/injector/FakeSettingsHelper.java b/services/tests/mockingservicestests/src/com/android/server/location/injector/FakeSettingsHelper.java
index cd70020..b76abe6 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/injector/FakeSettingsHelper.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/injector/FakeSettingsHelper.java
@@ -84,6 +84,8 @@
     private final Setting mBackgroundThrottlePackageWhitelistSetting = new Setting(
             Collections.emptySet());
     private final Setting mGnssMeasurementsFullTrackingSetting = new Setting(Boolean.FALSE);
+    private final Setting mAdasPackageAllowlist = new Setting(
+            new PackageTagsList.Builder().build());
     private final Setting mIgnoreSettingsAllowlist = new Setting(
             new PackageTagsList.Builder().build());
     private final Setting mBackgroundThrottleProximityAlertIntervalSetting = new Setting(
@@ -194,10 +196,29 @@
     }
 
     @Override
+    public PackageTagsList getAdasAllowlist() {
+        return mAdasPackageAllowlist.getValue(PackageTagsList.class);
+    }
+
+    @Override
+    public void addAdasAllowlistChangedListener(GlobalSettingChangedListener listener) {
+        mAdasPackageAllowlist.addListener(listener);
+    }
+
+    @Override
+    public void removeAdasAllowlistChangedListener(GlobalSettingChangedListener listener) {
+        mAdasPackageAllowlist.removeListener(listener);
+    }
+
+    @Override
     public PackageTagsList getIgnoreSettingsAllowlist() {
         return mIgnoreSettingsAllowlist.getValue(PackageTagsList.class);
     }
 
+    public void setAdasSettingsAllowlist(PackageTagsList newValue) {
+        mAdasPackageAllowlist.setValue(newValue);
+    }
+
     public void setIgnoreSettingsAllowlist(PackageTagsList newValue) {
         mIgnoreSettingsAllowlist.setValue(newValue);
     }
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java
index d8f409d..71cc65b 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java
@@ -1107,6 +1107,10 @@
         doReturn(true).when(mPackageManager).hasSystemFeature(FEATURE_AUTOMOTIVE);
         doReturn(true).when(mResources).getBoolean(R.bool.config_defaultAdasGnssLocationEnabled);
 
+        mInjector.getSettingsHelper().setAdasSettingsAllowlist(
+                new PackageTagsList.Builder().add(
+                        IDENTITY.getPackageName()).build());
+
         createManager(GPS_PROVIDER);
 
         ILocationListener listener1 = createMockLocationListener();
@@ -1136,6 +1140,10 @@
         doReturn(true).when(mPackageManager).hasSystemFeature(FEATURE_AUTOMOTIVE);
         doReturn(true).when(mResources).getBoolean(R.bool.config_defaultAdasGnssLocationEnabled);
 
+        mInjector.getSettingsHelper().setAdasSettingsAllowlist(
+                new PackageTagsList.Builder().add(
+                        IDENTITY.getPackageName()).build());
+
         createManager(GPS_PROVIDER);
 
         ILocationListener listener1 = createMockLocationListener();
@@ -1160,11 +1168,16 @@
 
     @Test
     public void testProviderRequest_AdasGnssBypass_ProviderDisabled_AdasDisabled() {
+        doReturn(true).when(mPackageManager).hasSystemFeature(FEATURE_AUTOMOTIVE);
+        doReturn(true).when(mResources).getBoolean(R.bool.config_defaultAdasGnssLocationEnabled);
+
         mInjector.getSettingsHelper().setIgnoreSettingsAllowlist(
                 new PackageTagsList.Builder().add(
                         IDENTITY.getPackageName()).build());
-        doReturn(true).when(mPackageManager).hasSystemFeature(FEATURE_AUTOMOTIVE);
-        doReturn(true).when(mResources).getBoolean(R.bool.config_defaultAdasGnssLocationEnabled);
+
+        mInjector.getSettingsHelper().setAdasSettingsAllowlist(
+                new PackageTagsList.Builder().add(
+                        IDENTITY.getPackageName()).build());
 
         createManager(GPS_PROVIDER);
 
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
index 2ad5eae..85d8aba 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
@@ -192,7 +192,7 @@
         waitForIdle();
 
         assertNull(mBiometricService.mAuthSession);
-        verify(mBiometricService.mStatusBarService).hideAuthenticationDialog();
+        verify(mBiometricService.mStatusBarService).hideAuthenticationDialog(eq(TEST_REQUEST_ID));
         verify(mReceiver1, never()).onError(anyInt(), anyInt(), anyInt());
     }
 
@@ -211,7 +211,8 @@
         waitForIdle();
 
         assertNotNull(mBiometricService.mAuthSession);
-        verify(mBiometricService.mStatusBarService, never()).hideAuthenticationDialog();
+        verify(mBiometricService.mStatusBarService, never())
+                .hideAuthenticationDialog(eq(TEST_REQUEST_ID));
         assertEquals(STATE_CLIENT_DIED_CANCELLING,
                 mBiometricService.mAuthSession.getState());
 
@@ -225,7 +226,7 @@
                 BiometricConstants.BIOMETRIC_ERROR_CANCELED,
                 0 /* vendorCode */);
         waitForIdle();
-        verify(mBiometricService.mStatusBarService).hideAuthenticationDialog();
+        verify(mBiometricService.mStatusBarService).hideAuthenticationDialog(eq(TEST_REQUEST_ID));
         verify(mReceiver1, never()).onError(anyInt(), anyInt(), anyInt());
         assertNull(mBiometricService.mAuthSession);
     }
@@ -666,7 +667,7 @@
                 eq(BiometricAuthenticator.TYPE_FACE),
                 eq(BiometricPrompt.BIOMETRIC_ERROR_CANCELED),
                 eq(0) /* vendorCode */);
-        verify(mBiometricService.mStatusBarService).hideAuthenticationDialog();
+        verify(mBiometricService.mStatusBarService).hideAuthenticationDialog(eq(TEST_REQUEST_ID));
 
         verify(mReceiver2, never()).onError(anyInt(), anyInt(), anyInt());
     }
@@ -745,7 +746,7 @@
                 eq(BiometricConstants.BIOMETRIC_ERROR_CANCELED),
                 eq(0 /* vendorCode */));
         // Dialog is hidden immediately
-        verify(mBiometricService.mStatusBarService).hideAuthenticationDialog();
+        verify(mBiometricService.mStatusBarService).hideAuthenticationDialog(eq(TEST_REQUEST_ID));
         // Auth session is over
         assertNull(mBiometricService.mAuthSession);
     }
@@ -773,7 +774,8 @@
                 eq(TYPE_FINGERPRINT),
                 eq(BiometricConstants.BIOMETRIC_ERROR_UNABLE_TO_PROCESS),
                 eq(0 /* vendorCode */));
-        verify(mBiometricService.mStatusBarService, never()).hideAuthenticationDialog();
+        verify(mBiometricService.mStatusBarService, never())
+                .hideAuthenticationDialog(eq(TEST_REQUEST_ID));
         verify(mReceiver1, never()).onError(anyInt(), anyInt(), anyInt());
 
         // SystemUI animation completed, client is notified, auth session is over
@@ -1152,7 +1154,7 @@
         verify(mReceiver1).onError(eq(TYPE_FINGERPRINT),
                 eq(BiometricConstants.BIOMETRIC_ERROR_CANCELED),
                 eq(0 /* vendorCode */));
-        verify(mBiometricService.mStatusBarService).hideAuthenticationDialog();
+        verify(mBiometricService.mStatusBarService).hideAuthenticationDialog(eq(TEST_REQUEST_ID));
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
index 40c0392..864f315 100644
--- a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
@@ -20,8 +20,8 @@
 import static android.hardware.display.DisplayManager.DeviceConfig.KEY_FIXED_REFRESH_RATE_HIGH_DISPLAY_BRIGHTNESS_THRESHOLDS;
 import static android.hardware.display.DisplayManager.DeviceConfig.KEY_FIXED_REFRESH_RATE_LOW_AMBIENT_BRIGHTNESS_THRESHOLDS;
 import static android.hardware.display.DisplayManager.DeviceConfig.KEY_FIXED_REFRESH_RATE_LOW_DISPLAY_BRIGHTNESS_THRESHOLDS;
-import static android.hardware.display.DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_HBM_SUNLIGHT;
 import static android.hardware.display.DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_HBM_HDR;
+import static android.hardware.display.DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_HBM_SUNLIGHT;
 import static android.hardware.display.DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_HIGH_ZONE;
 import static android.hardware.display.DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_LOW_ZONE;
 
@@ -919,16 +919,6 @@
         // Should be no vote initially
         Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_UDFPS);
         assertNull(vote);
-
-        // Enabling GHBM votes for 60hz
-        hbmListener.onHbmEnabled(IUdfpsHbmListener.GLOBAL_HBM, DISPLAY_ID);
-        vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_UDFPS);
-        assertVoteForRefreshRate(vote, 60.f);
-
-        // Disabling GHBM removes the vote
-        hbmListener.onHbmDisabled(IUdfpsHbmListener.GLOBAL_HBM, DISPLAY_ID);
-        vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_UDFPS);
-        assertNull(vote);
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/ActiveSourceActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/ActiveSourceActionTest.java
index 18f2642..112db76 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/ActiveSourceActionTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/ActiveSourceActionTest.java
@@ -61,7 +61,8 @@
     public void setUp() throws Exception {
         mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
 
-        mHdmiControlService = new HdmiControlService(mContextSpy, Collections.emptyList()) {
+        mHdmiControlService = new HdmiControlService(mContextSpy, Collections.emptyList(),
+                new FakeAudioDeviceVolumeManagerWrapper()) {
             @Override
             AudioManager getAudioManager() {
                 return new AudioManager() {
@@ -93,6 +94,7 @@
         mHdmiControlService.setCecController(hdmiCecController);
         mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService));
         mHdmiControlService.initService();
+        mHdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY);
         mPowerManager = new FakePowerManagerWrapper(mContextSpy);
         mHdmiControlService.setPowerManager(mPowerManager);
         mPhysicalAddress = 0x2000;
@@ -153,7 +155,6 @@
                 mHdmiControlService);
         audioDevice.init();
         mLocalDevices.add(audioDevice);
-        mHdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY);
         mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
         mTestLooper.dispatchAll();
 
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/ArcInitiationActionFromAvrTest.java b/services/tests/servicestests/src/com/android/server/hdmi/ArcInitiationActionFromAvrTest.java
index e4c5ad67..e4eecc6 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/ArcInitiationActionFromAvrTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/ArcInitiationActionFromAvrTest.java
@@ -68,7 +68,8 @@
         mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
 
         HdmiControlService hdmiControlService =
-                new HdmiControlService(mContextSpy, Collections.emptyList()) {
+                new HdmiControlService(mContextSpy, Collections.emptyList(),
+                        new FakeAudioDeviceVolumeManagerWrapper()) {
                     @Override
                     boolean isPowerStandby() {
                         return false;
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java b/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java
index d73cdb5..5b11466 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java
@@ -68,7 +68,8 @@
         mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
 
         HdmiControlService hdmiControlService =
-                new HdmiControlService(mContextSpy, Collections.emptyList()) {
+                new HdmiControlService(mContextSpy, Collections.emptyList(),
+                        new FakeAudioDeviceVolumeManagerWrapper()) {
                     @Override
                     AudioManager getAudioManager() {
                         return mAudioManager;
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/BaseAbsoluteVolumeControlTest.java b/services/tests/servicestests/src/com/android/server/hdmi/BaseAbsoluteVolumeControlTest.java
new file mode 100644
index 0000000..e06877f
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/hdmi/BaseAbsoluteVolumeControlTest.java
@@ -0,0 +1,570 @@
+/*
+ * 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.hdmi;
+
+import static android.hardware.hdmi.HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM;
+
+import static com.android.server.hdmi.HdmiCecKeycode.CEC_KEYCODE_VOLUME_UP;
+import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_BOOT_UP;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.TruthJUnit.assume;
+
+import static org.mockito.AdditionalMatchers.not;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.hardware.hdmi.DeviceFeatures;
+import android.hardware.hdmi.HdmiControlManager;
+import android.hardware.hdmi.HdmiDeviceInfo;
+import android.hardware.hdmi.HdmiPortInfo;
+import android.hardware.tv.cec.V1_0.SendMessageResult;
+import android.media.AudioDeviceAttributes;
+import android.media.AudioDeviceVolumeManager;
+import android.media.AudioManager;
+import android.media.VolumeInfo;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.os.test.TestLooper;
+
+import androidx.test.InstrumentationRegistry;
+
+import com.android.server.SystemService;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.InOrder;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+
+/**
+ * Tests that Absolute Volume Control (AVC) is enabled and disabled correctly, and that
+ * the device responds correctly to incoming <Report Audio Status> messages and API calls
+ * from AudioService when AVC is active.
+ *
+ * This is an abstract base class. Concrete subclasses specify the type of the local device, and the
+ * type of the System Audio device. This allows each test to be run for multiple setups.
+ *
+ * We test the following pairs of (local device, System Audio device):
+ * (Playback, TV): {@link PlaybackDeviceToTvAvcTest}
+ * (Playback, Audio System): {@link PlaybackDeviceToAudioSystemAvcTest}
+ * (TV, Audio System): {@link TvToAudioSystemAvcTest}
+ */
+public abstract class BaseAbsoluteVolumeControlTest {
+    private HdmiControlService mHdmiControlService;
+    private HdmiCecController mHdmiCecController;
+    private HdmiCecLocalDevice mHdmiCecLocalDevice;
+    private FakeHdmiCecConfig mHdmiCecConfig;
+    private FakePowerManagerWrapper mPowerManager;
+    private Looper mLooper;
+    private Context mContextSpy;
+    private ArrayList<HdmiCecLocalDevice> mLocalDevices = new ArrayList<>();
+
+    @Mock protected AudioManager mAudioManager;
+    protected FakeAudioDeviceVolumeManagerWrapper mAudioDeviceVolumeManager;
+
+    protected TestLooper mTestLooper = new TestLooper();
+    protected FakeNativeWrapper mNativeWrapper;
+
+    // Audio Status given by the System Audio device in its initial <Report Audio Status> that
+    // triggers AVC being enabled
+    private static final AudioStatus INITIAL_SYSTEM_AUDIO_DEVICE_STATUS =
+            new AudioStatus(50, false);
+
+    // VolumeInfo passed to AudioDeviceVolumeManager#setDeviceAbsoluteVolumeBehavior to enable AVC
+    private static final VolumeInfo ENABLE_AVC_VOLUME_INFO =
+            new VolumeInfo.Builder(AudioManager.STREAM_MUSIC)
+                    .setMuted(INITIAL_SYSTEM_AUDIO_DEVICE_STATUS.getMute())
+                    .setVolumeIndex(INITIAL_SYSTEM_AUDIO_DEVICE_STATUS.getVolume())
+                    .setMaxVolumeIndex(AudioStatus.MAX_VOLUME)
+                    .setMinVolumeIndex(AudioStatus.MIN_VOLUME)
+                    .build();
+
+    protected abstract HdmiCecLocalDevice createLocalDevice(HdmiControlService hdmiControlService);
+
+    protected abstract int getPhysicalAddress();
+    protected abstract int getDeviceType();
+    protected abstract AudioDeviceAttributes getAudioOutputDevice();
+
+    protected abstract int getSystemAudioDeviceLogicalAddress();
+    protected abstract int getSystemAudioDeviceType();
+
+    @Before
+    public void setUp() throws RemoteException {
+        MockitoAnnotations.initMocks(this);
+
+        mContextSpy = spy(new ContextWrapper(
+                InstrumentationRegistry.getInstrumentation().getTargetContext()));
+
+        mAudioDeviceVolumeManager = spy(new FakeAudioDeviceVolumeManagerWrapper());
+
+        mHdmiControlService =
+                new HdmiControlService(InstrumentationRegistry.getTargetContext(),
+                        Collections.singletonList(getDeviceType()),
+                        mAudioDeviceVolumeManager) {
+                    @Override
+                    AudioManager getAudioManager() {
+                        return mAudioManager;
+                    }
+
+                    @Override
+                    protected void writeStringSystemProperty(String key, String value) {
+                        // do nothing
+                    }
+                };
+
+        mLooper = mTestLooper.getLooper();
+        mHdmiControlService.setIoLooper(mLooper);
+
+        mHdmiCecConfig = new FakeHdmiCecConfig(mContextSpy);
+        mHdmiCecConfig.setIntValue(
+                HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION,
+                HdmiControlManager.HDMI_CEC_VERSION_2_0);
+        mHdmiCecConfig.setIntValue(
+                HdmiControlManager.CEC_SETTING_NAME_VOLUME_CONTROL_MODE,
+                HdmiControlManager.VOLUME_CONTROL_DISABLED);
+        mHdmiControlService.setHdmiCecConfig(mHdmiCecConfig);
+
+        mNativeWrapper = new FakeNativeWrapper();
+        mNativeWrapper.setPhysicalAddress(getPhysicalAddress());
+        mNativeWrapper.setPollAddressResponse(Constants.ADDR_TV, SendMessageResult.SUCCESS);
+
+        mHdmiCecController = HdmiCecController.createWithNativeWrapper(
+                mHdmiControlService, mNativeWrapper, mHdmiControlService.getAtomWriter());
+        mHdmiControlService.setCecController(mHdmiCecController);
+        mHdmiControlService.setHdmiMhlController(
+                HdmiMhlControllerStub.create(mHdmiControlService));
+        mHdmiControlService.initService();
+        mPowerManager = new FakePowerManagerWrapper(mContextSpy);
+        mHdmiControlService.setPowerManager(mPowerManager);
+
+        mHdmiCecLocalDevice = createLocalDevice(mHdmiControlService);
+        mHdmiCecLocalDevice.init();
+        mLocalDevices.add(mHdmiCecLocalDevice);
+
+        HdmiPortInfo[] hdmiPortInfos = new HdmiPortInfo[1];
+        hdmiPortInfos[0] =
+                new HdmiPortInfo(1, HdmiPortInfo.PORT_OUTPUT, 0x0000, true, false, false);
+        mNativeWrapper.setPortInfo(hdmiPortInfos);
+        mNativeWrapper.setPortConnectionStatus(1, true);
+
+        mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_BOOT_UP);
+        mTestLooper.dispatchAll();
+
+        // Simulate AudioManager's behavior and response when setDeviceVolumeBehavior is called
+        doAnswer(invocation -> {
+            setDeviceVolumeBehavior(invocation.getArgument(0), invocation.getArgument(1));
+            return null;
+        }).when(mAudioManager).setDeviceVolumeBehavior(any(), anyInt());
+
+        // Set starting volume behavior
+        doReturn(AudioManager.DEVICE_VOLUME_BEHAVIOR_VARIABLE)
+                .when(mAudioManager).getDeviceVolumeBehavior(eq(getAudioOutputDevice()));
+
+        // Audio service always plays STREAM_MUSIC on the device we need
+        doReturn(Collections.singletonList(getAudioOutputDevice())).when(mAudioManager)
+                .getDevicesForAttributes(HdmiControlService.STREAM_MUSIC_ATTRIBUTES);
+
+        // Max volume of STREAM_MUSIC
+        doReturn(25).when(mAudioManager).getStreamMaxVolume(AudioManager.STREAM_MUSIC);
+
+        // Receive messages from devices to make sure they're registered in HdmiCecNetwork
+        mNativeWrapper.onCecMessage(HdmiCecMessageBuilder.buildGiveDevicePowerStatus(
+                Constants.ADDR_TV, getLogicalAddress()));
+        if (getSystemAudioDeviceType() == DEVICE_AUDIO_SYSTEM) {
+            mNativeWrapper.onCecMessage(HdmiCecMessageBuilder.buildGiveDevicePowerStatus(
+                    Constants.ADDR_AUDIO_SYSTEM, getLogicalAddress()));
+        }
+
+        mHdmiControlService.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY);
+        mHdmiControlService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
+        mTestLooper.dispatchAll();
+    }
+
+    protected int getLogicalAddress() {
+        synchronized (mHdmiCecLocalDevice.mLock) {
+            return mHdmiCecLocalDevice.getDeviceInfo().getLogicalAddress();
+        }
+    }
+
+    /**
+     * Simulates the volume behavior of {@code device} being set to {@code behavior}.
+     */
+    protected void setDeviceVolumeBehavior(AudioDeviceAttributes device,
+            @AudioManager.DeviceVolumeBehavior int behavior) {
+        doReturn(behavior).when(mAudioManager).getDeviceVolumeBehavior(eq(device));
+        mHdmiControlService.onDeviceVolumeBehaviorChanged(device, behavior);
+        mTestLooper.dispatchAll();
+    }
+
+    /**
+     * Changes the setting for CEC volume.
+     */
+    protected void setCecVolumeControlSetting(@HdmiControlManager.VolumeControl int setting) {
+        mHdmiControlService.getHdmiCecConfig().setIntValue(
+                HdmiControlManager.CEC_SETTING_NAME_VOLUME_CONTROL_MODE, setting);
+        mTestLooper.dispatchAll();
+    }
+
+    /**
+     * Has the device receive a <Report Features> message from the System Audio device specifying
+     * whether <Set Audio Volume Level> is supported or not.
+     */
+    protected void receiveSetAudioVolumeLevelSupport(
+            @DeviceFeatures.FeatureSupportStatus int featureSupportStatus) {
+        // <Report Features> can't specify an unknown feature support status
+        if (featureSupportStatus != DeviceFeatures.FEATURE_SUPPORT_UNKNOWN) {
+            mNativeWrapper.onCecMessage(ReportFeaturesMessage.build(
+                    getSystemAudioDeviceLogicalAddress(), HdmiControlManager.HDMI_CEC_VERSION_2_0,
+                    Arrays.asList(getSystemAudioDeviceType()), Constants.RC_PROFILE_SOURCE,
+                    Collections.emptyList(),
+                    DeviceFeatures.NO_FEATURES_SUPPORTED.toBuilder()
+                            .setSetAudioVolumeLevelSupport(featureSupportStatus)
+                            .build()));
+            mTestLooper.dispatchAll();
+        }
+    }
+
+    /**
+     * Enables System Audio mode if the System Audio device is an Audio System.
+     */
+    protected void enableSystemAudioModeIfNeeded() {
+        if (getSystemAudioDeviceType() == DEVICE_AUDIO_SYSTEM) {
+            receiveSetSystemAudioMode(true);
+        }
+    }
+
+    /**
+     * Sets System Audio Mode by having the device receive <Set System Audio Mode>
+     * from the Audio System.
+     */
+    protected void receiveSetSystemAudioMode(boolean status) {
+        mNativeWrapper.onCecMessage(HdmiCecMessageBuilder.buildSetSystemAudioMode(
+                Constants.ADDR_AUDIO_SYSTEM, Constants.ADDR_BROADCAST, status));
+        mTestLooper.dispatchAll();
+    }
+
+    /**
+     * Has the device receive a <Report Audio Status> reporting the status in
+     * {@link #INITIAL_SYSTEM_AUDIO_DEVICE_STATUS}
+     */
+    protected void receiveInitialReportAudioStatus() {
+        receiveReportAudioStatus(
+                INITIAL_SYSTEM_AUDIO_DEVICE_STATUS.getVolume(),
+                INITIAL_SYSTEM_AUDIO_DEVICE_STATUS.getMute());
+    }
+
+    /**
+     * Has the device receive a <Report Audio Status> message from the System Audio Device.
+     */
+    protected void receiveReportAudioStatus(int volume, boolean mute) {
+        mNativeWrapper.onCecMessage(HdmiCecMessageBuilder.buildReportAudioStatus(
+                getSystemAudioDeviceLogicalAddress(),
+                getLogicalAddress(),
+                volume,
+                mute));
+        mTestLooper.dispatchAll();
+    }
+
+    /**
+     * Triggers all the conditions required to enable Absolute Volume Control.
+     */
+    protected void enableAbsoluteVolumeControl() {
+        setDeviceVolumeBehavior(getAudioOutputDevice(), AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL);
+        setCecVolumeControlSetting(HdmiControlManager.VOLUME_CONTROL_ENABLED);
+        receiveSetAudioVolumeLevelSupport(DeviceFeatures.FEATURE_SUPPORTED);
+        enableSystemAudioModeIfNeeded();
+        receiveInitialReportAudioStatus();
+
+        verifyAbsoluteVolumeEnabled();
+    }
+
+    /**
+     * Verifies that AVC was enabled - that is the audio output device's volume behavior was last
+     * set to absolute volume behavior.
+     */
+    protected void verifyAbsoluteVolumeEnabled() {
+        InOrder inOrder = inOrder(mAudioManager, mAudioDeviceVolumeManager);
+        inOrder.verify(mAudioDeviceVolumeManager, atLeastOnce()).setDeviceAbsoluteVolumeBehavior(
+                eq(getAudioOutputDevice()), any(), any(), any(), anyBoolean());
+        inOrder.verify(mAudioManager, never()).setDeviceVolumeBehavior(
+                eq(getAudioOutputDevice()), not(eq(AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE)));
+    }
+
+    /**
+     * Verifies that AVC was disabled - that is, the audio output device's volume behavior was
+     * last set to something other than absolute volume behavior.
+     */
+    protected void verifyAbsoluteVolumeDisabled() {
+        InOrder inOrder = inOrder(mAudioManager, mAudioDeviceVolumeManager);
+        inOrder.verify(mAudioManager, atLeastOnce()).setDeviceVolumeBehavior(
+                eq(getAudioOutputDevice()), not(eq(AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE)));
+        inOrder.verify(mAudioDeviceVolumeManager, never()).setDeviceAbsoluteVolumeBehavior(
+                eq(getAudioOutputDevice()), any(), any(), any(), anyBoolean());
+    }
+
+    protected void verifyGiveAudioStatusNeverSent() {
+        assertThat(mNativeWrapper.getResultMessages()).doesNotContain(
+                HdmiCecMessageBuilder.buildGiveAudioStatus(
+                        getLogicalAddress(), getSystemAudioDeviceLogicalAddress()));
+    }
+
+    protected void verifyGiveAudioStatusSent() {
+        assertThat(mNativeWrapper.getResultMessages()).contains(
+                HdmiCecMessageBuilder.buildGiveAudioStatus(
+                        getLogicalAddress(), getSystemAudioDeviceLogicalAddress()));
+    }
+
+    @Test
+    public void allConditionsExceptSavlSupportMet_sendsSetAudioVolumeLevelAndGiveFeatures() {
+        setDeviceVolumeBehavior(getAudioOutputDevice(), AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL);
+        setCecVolumeControlSetting(HdmiControlManager.VOLUME_CONTROL_ENABLED);
+        enableSystemAudioModeIfNeeded();
+
+        assertThat(mNativeWrapper.getResultMessages()).contains(
+                SetAudioVolumeLevelMessage.build(
+                        getLogicalAddress(), getSystemAudioDeviceLogicalAddress(),
+                        Constants.AUDIO_VOLUME_STATUS_UNKNOWN));
+        assertThat(mNativeWrapper.getResultMessages()).contains(
+                HdmiCecMessageBuilder.buildGiveFeatures(
+                        getLogicalAddress(), getSystemAudioDeviceLogicalAddress()));
+    }
+
+    @Test
+    public void allConditionsMet_savlSupportLast_reportFeatures_giveAudioStatusSent() {
+        setDeviceVolumeBehavior(getAudioOutputDevice(), AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL);
+        setCecVolumeControlSetting(HdmiControlManager.VOLUME_CONTROL_ENABLED);
+        enableSystemAudioModeIfNeeded();
+        verifyGiveAudioStatusNeverSent();
+
+        receiveSetAudioVolumeLevelSupport(DeviceFeatures.FEATURE_SUPPORTED);
+        verifyGiveAudioStatusSent();
+    }
+
+    @Test
+    public void allConditionsMet_savlSupportLast_noFeatureAbort_giveAudioStatusSent() {
+        setDeviceVolumeBehavior(getAudioOutputDevice(), AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL);
+        setCecVolumeControlSetting(HdmiControlManager.VOLUME_CONTROL_ENABLED);
+        enableSystemAudioModeIfNeeded();
+        verifyGiveAudioStatusNeverSent();
+
+        mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS);
+        mTestLooper.dispatchAll();
+        verifyGiveAudioStatusSent();
+    }
+
+    @Test
+    public void allConditionsMet_cecVolumeEnabledLast_giveAudioStatusSent() {
+        setDeviceVolumeBehavior(getAudioOutputDevice(), AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL);
+        enableSystemAudioModeIfNeeded();
+        receiveSetAudioVolumeLevelSupport(DeviceFeatures.FEATURE_SUPPORTED);
+        verifyGiveAudioStatusNeverSent();
+
+        setCecVolumeControlSetting(HdmiControlManager.VOLUME_CONTROL_ENABLED);
+        verifyGiveAudioStatusSent();
+    }
+
+    @Test
+    public void allConditionsMet_fullVolumeBehaviorLast_giveAudioStatusSent() {
+        setCecVolumeControlSetting(HdmiControlManager.VOLUME_CONTROL_ENABLED);
+        enableSystemAudioModeIfNeeded();
+        receiveSetAudioVolumeLevelSupport(DeviceFeatures.FEATURE_SUPPORTED);
+        verifyGiveAudioStatusNeverSent();
+
+        setDeviceVolumeBehavior(getAudioOutputDevice(), AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL);
+        verifyGiveAudioStatusSent();
+    }
+
+    @Test
+    public void allConditionsMet_systemAudioModeEnabledLast_giveAudioStatusSent() {
+        // Only run when the System Audio device is an Audio System.
+        assume().that(getSystemAudioDeviceType()).isEqualTo(HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
+
+        setDeviceVolumeBehavior(getAudioOutputDevice(), AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL);
+        setCecVolumeControlSetting(HdmiControlManager.VOLUME_CONTROL_ENABLED);
+        receiveSetAudioVolumeLevelSupport(DeviceFeatures.FEATURE_SUPPORTED);
+        verifyGiveAudioStatusNeverSent();
+
+        receiveSetSystemAudioMode(true);
+        verifyGiveAudioStatusSent();
+    }
+
+    @Test
+    public void giveAudioStatusSent_systemAudioDeviceSendsReportAudioStatus_avcEnabled() {
+        setCecVolumeControlSetting(HdmiControlManager.VOLUME_CONTROL_ENABLED);
+        enableSystemAudioModeIfNeeded();
+        receiveSetAudioVolumeLevelSupport(DeviceFeatures.FEATURE_SUPPORTED);
+        setDeviceVolumeBehavior(getAudioOutputDevice(), AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL);
+
+        // Verify that AVC was never enabled
+        verify(mAudioDeviceVolumeManager, never()).setDeviceAbsoluteVolumeBehavior(
+                eq(getAudioOutputDevice()), any(), any(), any(), anyBoolean());
+        receiveInitialReportAudioStatus();
+
+        verifyAbsoluteVolumeEnabled();
+    }
+
+    @Test
+    public void avcEnabled_cecVolumeDisabled_absoluteVolumeDisabled() {
+        enableAbsoluteVolumeControl();
+
+        setCecVolumeControlSetting(HdmiControlManager.VOLUME_CONTROL_DISABLED);
+        verifyAbsoluteVolumeDisabled();
+    }
+
+    @Test
+    public void avcEnabled_setAudioVolumeLevelNotSupported_absoluteVolumeDisabled() {
+        enableAbsoluteVolumeControl();
+
+        receiveSetAudioVolumeLevelSupport(DeviceFeatures.FEATURE_NOT_SUPPORTED);
+        verifyAbsoluteVolumeDisabled();
+    }
+
+    @Test
+    public void avcEnabled_setAudioVolumeLevelFeatureAborted_absoluteVolumeDisabled() {
+        enableAbsoluteVolumeControl();
+
+        mNativeWrapper.onCecMessage(HdmiCecMessageBuilder.buildFeatureAbortCommand(
+                getSystemAudioDeviceLogicalAddress(), getLogicalAddress(),
+                Constants.MESSAGE_SET_AUDIO_VOLUME_LEVEL, Constants.ABORT_UNRECOGNIZED_OPCODE));
+        mTestLooper.dispatchAll();
+        verifyAbsoluteVolumeDisabled();
+    }
+
+    @Test
+    public void avcEnabled_systemAudioModeDisabled_absoluteVolumeDisabled() {
+        // Only run when the System Audio device is an Audio System.
+        assume().that(getSystemAudioDeviceType()).isEqualTo(HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
+
+        enableAbsoluteVolumeControl();
+
+        receiveSetSystemAudioMode(false);
+        verifyAbsoluteVolumeDisabled();
+    }
+
+    @Test
+    public void avcEnabled_receiveReportAudioStatus_notifiesVolumeOrMuteChanges() {
+        // Initial <Report Audio Status> has volume=50 and mute=false
+        enableAbsoluteVolumeControl();
+
+        // New volume and mute status: sets both
+        receiveReportAudioStatus(20, true);
+        verify(mAudioManager).setStreamVolume(eq(AudioManager.STREAM_MUSIC), eq(5),
+                anyInt());
+        verify(mAudioManager).adjustStreamVolume(eq(AudioManager.STREAM_MUSIC),
+                eq(AudioManager.ADJUST_MUTE), anyInt());
+        clearInvocations(mAudioManager);
+
+        // New volume only: sets volume only
+        receiveReportAudioStatus(32, true);
+        verify(mAudioManager).setStreamVolume(eq(AudioManager.STREAM_MUSIC), eq(8),
+                anyInt());
+        verify(mAudioManager, never()).adjustStreamVolume(eq(AudioManager.STREAM_MUSIC),
+                eq(AudioManager.ADJUST_MUTE), anyInt());
+        clearInvocations(mAudioManager);
+
+        // New mute status only: sets mute only
+        receiveReportAudioStatus(32, false);
+        verify(mAudioManager, never()).setStreamVolume(eq(AudioManager.STREAM_MUSIC), eq(8),
+                anyInt());
+        verify(mAudioManager).adjustStreamVolume(eq(AudioManager.STREAM_MUSIC),
+                eq(AudioManager.ADJUST_UNMUTE), anyInt());
+        clearInvocations(mAudioManager);
+
+        // Repeat of earlier message: sets neither volume nor mute
+        receiveReportAudioStatus(32, false);
+        verify(mAudioManager, never()).setStreamVolume(eq(AudioManager.STREAM_MUSIC), eq(8),
+                anyInt());
+        verify(mAudioManager, never()).adjustStreamVolume(eq(AudioManager.STREAM_MUSIC),
+                eq(AudioManager.ADJUST_UNMUTE), anyInt());
+        clearInvocations(mAudioManager);
+
+        // If AudioService causes us to send <Set Audio Volume Level>, the System Audio device's
+        // volume changes. Afterward, a duplicate of an earlier <Report Audio Status> should
+        // still cause us to call setStreamVolume()
+        mHdmiControlService.getAbsoluteVolumeChangedListener().onAudioDeviceVolumeChanged(
+                getAudioOutputDevice(),
+                new VolumeInfo.Builder(ENABLE_AVC_VOLUME_INFO)
+                        .setVolumeIndex(20)
+                        .build()
+        );
+        mTestLooper.dispatchAll();
+        receiveReportAudioStatus(32, false);
+        verify(mAudioManager).setStreamVolume(eq(AudioManager.STREAM_MUSIC), eq(8),
+                anyInt());
+        verify(mAudioManager, never()).adjustStreamVolume(eq(AudioManager.STREAM_MUSIC),
+                eq(AudioManager.ADJUST_UNMUTE), anyInt());
+    }
+
+    @Test
+    public void avcEnabled_audioDeviceVolumeAdjusted_sendsUserControlPressedAndGiveAudioStatus() {
+        enableAbsoluteVolumeControl();
+        mNativeWrapper.clearResultMessages();
+
+        mHdmiControlService.getAbsoluteVolumeChangedListener().onAudioDeviceVolumeAdjusted(
+                getAudioOutputDevice(),
+                ENABLE_AVC_VOLUME_INFO,
+                AudioManager.ADJUST_RAISE,
+                AudioDeviceVolumeManager.ADJUST_MODE_NORMAL
+        );
+        mTestLooper.dispatchAll();
+
+        assertThat(mNativeWrapper.getResultMessages()).contains(
+                HdmiCecMessageBuilder.buildUserControlPressed(getLogicalAddress(),
+                        getSystemAudioDeviceLogicalAddress(), CEC_KEYCODE_VOLUME_UP));
+        assertThat(mNativeWrapper.getResultMessages()).contains(
+                HdmiCecMessageBuilder.buildUserControlReleased(getLogicalAddress(),
+                        getSystemAudioDeviceLogicalAddress()));
+        assertThat(mNativeWrapper.getResultMessages()).contains(
+                HdmiCecMessageBuilder.buildGiveAudioStatus(getLogicalAddress(),
+                        getSystemAudioDeviceLogicalAddress()));
+    }
+
+    @Test
+    public void avcEnabled_audioDeviceVolumeChanged_sendsSetAudioVolumeLevel() {
+        enableAbsoluteVolumeControl();
+        mNativeWrapper.clearResultMessages();
+
+        mHdmiControlService.getAbsoluteVolumeChangedListener().onAudioDeviceVolumeChanged(
+                getAudioOutputDevice(),
+                new VolumeInfo.Builder(ENABLE_AVC_VOLUME_INFO)
+                        .setVolumeIndex(20)
+                        .build()
+        );
+        mTestLooper.dispatchAll();
+
+        assertThat(mNativeWrapper.getResultMessages()).contains(
+                SetAudioVolumeLevelMessage.build(getLogicalAddress(),
+                        getSystemAudioDeviceLogicalAddress(), 20));
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/DetectTvSystemAudioModeSupportActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/DetectTvSystemAudioModeSupportActionTest.java
index 5cec8ad..28ba4bb 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/DetectTvSystemAudioModeSupportActionTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/DetectTvSystemAudioModeSupportActionTest.java
@@ -56,7 +56,7 @@
         mDeviceInfoForTests = HdmiDeviceInfo.hardwarePort(1001, 1234);
         HdmiControlService hdmiControlService =
                 new HdmiControlService(InstrumentationRegistry.getTargetContext(),
-                        Collections.emptyList()) {
+                        Collections.emptyList(), new FakeAudioDeviceVolumeManagerWrapper()) {
 
                     @Override
                     void sendCecCommand(
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/DevicePowerStatusActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/DevicePowerStatusActionTest.java
index 52a0b6c..545f318 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/DevicePowerStatusActionTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/DevicePowerStatusActionTest.java
@@ -16,6 +16,7 @@
 
 package com.android.server.hdmi;
 
+import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY;
 import static com.android.server.hdmi.Constants.ADDR_BROADCAST;
 import static com.android.server.hdmi.Constants.ADDR_TV;
 import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC;
@@ -78,7 +79,8 @@
 
         mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
 
-        mHdmiControlService = new HdmiControlService(mContextSpy, Collections.emptyList()) {
+        mHdmiControlService = new HdmiControlService(mContextSpy, Collections.emptyList(),
+                new FakeAudioDeviceVolumeManagerWrapper()) {
             @Override
             AudioManager getAudioManager() {
                 return new AudioManager() {
@@ -110,6 +112,7 @@
         mHdmiControlService.setCecController(hdmiCecController);
         mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService));
         mHdmiControlService.initService();
+        mHdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY);
         mPowerManager = new FakePowerManagerWrapper(mContextSpy);
         mHdmiControlService.setPowerManager(mPowerManager);
         mPhysicalAddress = 0x2000;
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromPlaybackTest.java b/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromPlaybackTest.java
index 35432ed..d7fef90 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromPlaybackTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromPlaybackTest.java
@@ -20,6 +20,7 @@
 import static android.hardware.hdmi.HdmiControlManager.POWER_STATUS_STANDBY;
 import static android.hardware.hdmi.HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON;
 
+import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY;
 import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_1;
 import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_2;
 import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_3;
@@ -100,7 +101,7 @@
 
         mHdmiControlService =
                 new HdmiControlService(InstrumentationRegistry.getTargetContext(),
-                        Collections.emptyList()) {
+                        Collections.emptyList(), new FakeAudioDeviceVolumeManagerWrapper()) {
                     @Override
                     boolean isControlEnabled() {
                         return true;
@@ -136,6 +137,7 @@
 
         mLocalDevices.add(mHdmiCecLocalDevicePlayback);
         mHdmiControlService.initService();
+        mHdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY);
         mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
         mNativeWrapper.setPhysicalAddress(0x0000);
         mPowerManager = new FakePowerManagerWrapper(context);
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromTvTest.java b/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromTvTest.java
index e77cd91..72d36b0 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromTvTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromTvTest.java
@@ -20,6 +20,7 @@
 import static android.hardware.hdmi.HdmiControlManager.POWER_STATUS_STANDBY;
 import static android.hardware.hdmi.HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON;
 
+import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY;
 import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_1;
 import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_2;
 import static com.android.server.hdmi.Constants.ADDR_TV;
@@ -109,7 +110,7 @@
 
         mHdmiControlService =
                 new HdmiControlService(InstrumentationRegistry.getTargetContext(),
-                        Collections.emptyList()) {
+                        Collections.emptyList(), new FakeAudioDeviceVolumeManagerWrapper()) {
                     @Override
                     boolean isControlEnabled() {
                         return true;
@@ -145,6 +146,7 @@
                                  true, false, false);
         mNativeWrapper.setPortInfo(hdmiPortInfos);
         mHdmiControlService.initService();
+        mHdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY);
         mPowerManager = new FakePowerManagerWrapper(context);
         mHdmiControlService.setPowerManager(mPowerManager);
         mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/FakeAudioDeviceVolumeManagerWrapper.java b/services/tests/servicestests/src/com/android/server/hdmi/FakeAudioDeviceVolumeManagerWrapper.java
new file mode 100644
index 0000000..d33ef9b
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/hdmi/FakeAudioDeviceVolumeManagerWrapper.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.hdmi;
+
+import static android.media.AudioDeviceVolumeManager.OnAudioDeviceVolumeChangedListener;
+import static android.media.AudioDeviceVolumeManager.OnDeviceVolumeBehaviorChangedListener;
+
+import android.annotation.CallbackExecutor;
+import android.annotation.NonNull;
+import android.media.AudioDeviceAttributes;
+import android.media.AudioDeviceVolumeManager;
+import android.media.AudioManager;
+import android.media.VolumeInfo;
+
+import java.util.HashSet;
+import java.util.Set;
+import java.util.concurrent.Executor;
+
+/**
+ * Wrapper for {@link AudioDeviceVolumeManager} that stubs its methods. Useful for testing.
+ */
+public class FakeAudioDeviceVolumeManagerWrapper implements
+        AudioDeviceVolumeManagerWrapperInterface {
+
+    private final Set<OnDeviceVolumeBehaviorChangedListener> mVolumeBehaviorListeners;
+
+    public FakeAudioDeviceVolumeManagerWrapper() {
+        mVolumeBehaviorListeners = new HashSet<>();
+    }
+
+    @Override
+    public void addOnDeviceVolumeBehaviorChangedListener(
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull OnDeviceVolumeBehaviorChangedListener listener)
+            throws SecurityException {
+        mVolumeBehaviorListeners.add(listener);
+    }
+
+    @Override
+    public void removeOnDeviceVolumeBehaviorChangedListener(
+            @NonNull OnDeviceVolumeBehaviorChangedListener listener) {
+        mVolumeBehaviorListeners.remove(listener);
+    }
+
+    @Override
+    public void setDeviceAbsoluteVolumeBehavior(
+            @NonNull AudioDeviceAttributes device,
+            @NonNull VolumeInfo volume,
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull OnAudioDeviceVolumeChangedListener vclistener,
+            boolean handlesVolumeAdjustment) {
+        // Notify all volume behavior listeners that the device adopted absolute volume behavior
+        for (OnDeviceVolumeBehaviorChangedListener listener : mVolumeBehaviorListeners) {
+            listener.onDeviceVolumeBehaviorChanged(device,
+                    AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE);
+        }
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecAtomLoggingTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecAtomLoggingTest.java
index 30bcc7e..9f744f9 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecAtomLoggingTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecAtomLoggingTest.java
@@ -89,7 +89,8 @@
         mContextSpy = spy(new ContextWrapper(
                 InstrumentationRegistry.getInstrumentation().getTargetContext()));
 
-        mHdmiControlServiceSpy = spy(new HdmiControlService(mContextSpy, Collections.emptyList()));
+        mHdmiControlServiceSpy = spy(new HdmiControlService(mContextSpy, Collections.emptyList(),
+                new FakeAudioDeviceVolumeManagerWrapper()));
         doNothing().when(mHdmiControlServiceSpy)
                 .writeStringSystemProperty(anyString(), anyString());
         doReturn(mHdmiCecAtomWriterSpy).when(mHdmiControlServiceSpy).getAtomWriter();
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecControllerTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecControllerTest.java
index 2dcc449..0cba106 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecControllerTest.java
@@ -101,7 +101,8 @@
         mMyLooper = mTestLooper.getLooper();
 
         mHdmiControlServiceSpy = spy(new HdmiControlService(
-                InstrumentationRegistry.getTargetContext(), Collections.emptyList()));
+                InstrumentationRegistry.getTargetContext(), Collections.emptyList(),
+                new FakeAudioDeviceVolumeManagerWrapper()));
         doReturn(mMyLooper).when(mHdmiControlServiceSpy).getIoLooper();
         doReturn(mMyLooper).when(mHdmiControlServiceSpy).getServiceLooper();
         doAnswer(__ -> mCecVersion).when(mHdmiControlServiceSpy).getCecVersion();
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java
index 70bc460..91d265c 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java
@@ -88,7 +88,7 @@
 
         mHdmiControlService =
             new HdmiControlService(InstrumentationRegistry.getTargetContext(),
-                    Collections.emptyList()) {
+                    Collections.emptyList(), new FakeAudioDeviceVolumeManagerWrapper()) {
                 @Override
                 AudioManager getAudioManager() {
                     return new AudioManager() {
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
index 86130da..484b5a8 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
@@ -15,6 +15,7 @@
  */
 package com.android.server.hdmi;
 
+import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY;
 import static com.android.server.hdmi.Constants.ABORT_UNRECOGNIZED_OPCODE;
 import static com.android.server.hdmi.Constants.ADDR_AUDIO_SYSTEM;
 import static com.android.server.hdmi.Constants.ADDR_BROADCAST;
@@ -92,7 +93,7 @@
 
         mHdmiControlService =
                 new HdmiControlService(InstrumentationRegistry.getTargetContext(),
-                        Collections.emptyList()) {
+                        Collections.emptyList(), new FakeAudioDeviceVolumeManagerWrapper()) {
                     @Override
                     void wakeUp() {
                         mWokenUp = true;
@@ -151,6 +152,7 @@
         mNativeWrapper.setPortInfo(hdmiPortInfos);
         mNativeWrapper.setPortConnectionStatus(1, true);
         mHdmiControlService.initService();
+        mHdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY);
         mPowerManager = new FakePowerManagerWrapper(context);
         mHdmiControlService.setPowerManager(mPowerManager);
         mHdmiControlService.setPowerManagerInternal(mPowerManagerInternal);
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTest.java
index fb8baa3..48e70fe 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTest.java
@@ -17,6 +17,7 @@
 
 import static android.hardware.hdmi.HdmiDeviceInfo.DEVICE_TV;
 
+import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY;
 import static com.android.server.hdmi.Constants.ADDR_AUDIO_SYSTEM;
 import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_1;
 import static com.android.server.hdmi.Constants.ADDR_TV;
@@ -137,7 +138,8 @@
         Context context = InstrumentationRegistry.getTargetContext();
 
         mHdmiControlService =
-                new HdmiControlService(context, Collections.emptyList()) {
+                new HdmiControlService(context, Collections.emptyList(),
+                        new FakeAudioDeviceVolumeManagerWrapper()) {
                     @Override
                     boolean isControlEnabled() {
                         return isControlEnabled;
@@ -190,6 +192,7 @@
         mNativeWrapper.setPortInfo(hdmiPortInfos);
         mNativeWrapper.setPortConnectionStatus(1, true);
         mHdmiControlService.initService();
+        mHdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY);
         mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
         mNativeWrapper.setPhysicalAddress(0x2000);
         mTestLooper.dispatchAll();
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
index df4aa5d..f27b8c2 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
@@ -15,6 +15,7 @@
  */
 package com.android.server.hdmi;
 
+import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY;
 import static com.android.server.hdmi.Constants.ABORT_UNRECOGNIZED_OPCODE;
 import static com.android.server.hdmi.Constants.ADDR_AUDIO_SYSTEM;
 import static com.android.server.hdmi.Constants.ADDR_BROADCAST;
@@ -29,8 +30,10 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
 import android.content.Context;
 import android.hardware.hdmi.HdmiControlManager;
@@ -125,7 +128,7 @@
 
         mHdmiControlService =
                 new HdmiControlService(InstrumentationRegistry.getTargetContext(),
-                        Collections.emptyList()) {
+                        Collections.emptyList(), new FakeAudioDeviceVolumeManagerWrapper()) {
                     @Override
                     void wakeUp() {
                         mWokenUp = true;
@@ -179,6 +182,7 @@
                 new HdmiPortInfo(2, HdmiPortInfo.PORT_INPUT, 0x2000, true, false, true);
         mNativeWrapper.setPortInfo(hdmiPortInfos);
         mHdmiControlService.initService();
+        mHdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY);
         mPowerManager = new FakePowerManagerWrapper(context);
         mHdmiControlService.setPowerManager(mPowerManager);
         mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
@@ -768,7 +772,7 @@
         // When the device reports its physical address, the listener eventually is invoked.
         HdmiCecMessage reportPhysicalAddress =
                 HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
-                ADDR_PLAYBACK_2, 0x1000, HdmiDeviceInfo.DEVICE_PLAYBACK);
+                        ADDR_PLAYBACK_2, 0x1000, HdmiDeviceInfo.DEVICE_PLAYBACK);
         mNativeWrapper.onCecMessage(reportPhysicalAddress);
         mTestLooper.dispatchAll();
 
@@ -777,6 +781,54 @@
         assertThat(mDeviceEventListeners.size()).isEqualTo(1);
         assertThat(mDeviceEventListeners.get(0).getStatus())
                 .isEqualTo(HdmiControlManager.DEVICE_EVENT_ADD_DEVICE);
+    }
 
+    @Test
+    public void receiveSetAudioVolumeLevel_samNotActivated_noFeatureAbort_volumeChanges() {
+        when(mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC)).thenReturn(25);
+
+        // Max volume of STREAM_MUSIC is retrieved on boot
+        mHdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY);
+        mTestLooper.dispatchAll();
+
+        mNativeWrapper.onCecMessage(SetAudioVolumeLevelMessage.build(
+                ADDR_PLAYBACK_1,
+                ADDR_TV,
+                20));
+        mTestLooper.dispatchAll();
+
+        // <Feature Abort>[Not in correct mode] not sent
+        HdmiCecMessage featureAbortMessage = HdmiCecMessageBuilder.buildFeatureAbortCommand(
+                ADDR_TV,
+                ADDR_PLAYBACK_1,
+                Constants.MESSAGE_SET_AUDIO_VOLUME_LEVEL,
+                Constants.ABORT_NOT_IN_CORRECT_MODE);
+        assertThat(mNativeWrapper.getResultMessages()).doesNotContain(featureAbortMessage);
+
+        // <Set Audio Volume Level> uses volume range [0, 100]; STREAM_MUSIC uses range [0, 25]
+        verify(mAudioManager).setStreamVolume(eq(AudioManager.STREAM_MUSIC), eq(5), anyInt());
+    }
+
+    @Test
+    public void receiveSetAudioVolumeLevel_samActivated_respondsFeatureAbort_noVolumeChange() {
+        mNativeWrapper.onCecMessage(HdmiCecMessageBuilder.buildSetSystemAudioMode(
+                ADDR_AUDIO_SYSTEM, ADDR_TV, true));
+        mTestLooper.dispatchAll();
+
+        mNativeWrapper.onCecMessage(SetAudioVolumeLevelMessage.build(
+                ADDR_PLAYBACK_1, ADDR_TV, 50));
+        mTestLooper.dispatchAll();
+
+        // <Feature Abort>[Not in correct mode] sent
+        HdmiCecMessage featureAbortMessage = HdmiCecMessageBuilder.buildFeatureAbortCommand(
+                ADDR_TV,
+                ADDR_PLAYBACK_1,
+                Constants.MESSAGE_SET_AUDIO_VOLUME_LEVEL,
+                Constants.ABORT_NOT_IN_CORRECT_MODE);
+        assertThat(mNativeWrapper.getResultMessages()).contains(featureAbortMessage);
+
+        // AudioManager not notified of volume change
+        verify(mAudioManager, never()).setStreamVolume(eq(AudioManager.STREAM_MUSIC), anyInt(),
+                anyInt());
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageValidatorTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageValidatorTest.java
index 50c9f70..a446e10 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageValidatorTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageValidatorTest.java
@@ -51,7 +51,8 @@
     @Before
     public void setUp() throws Exception {
         HdmiControlService mHdmiControlService = new HdmiControlService(
-                InstrumentationRegistry.getTargetContext(), Collections.emptyList());
+                InstrumentationRegistry.getTargetContext(), Collections.emptyList(),
+                new FakeAudioDeviceVolumeManagerWrapper());
 
         mHdmiControlService.setIoLooper(mTestLooper.getLooper());
     }
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecNetworkTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecNetworkTest.java
index 03532ae..b8a1ba3 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecNetworkTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecNetworkTest.java
@@ -67,7 +67,8 @@
     @Before
     public void setUp() throws Exception {
         mContext = InstrumentationRegistry.getTargetContext();
-        mHdmiControlService = new HdmiControlService(mContext, Collections.emptyList()) {
+        mHdmiControlService = new HdmiControlService(mContext, Collections.emptyList(),
+                new FakeAudioDeviceVolumeManagerWrapper()) {
             @Override
             void invokeDeviceEventListeners(HdmiDeviceInfo device, int status) {
                 mDeviceEventListenerStatuses.add(status);
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecPowerStatusControllerTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecPowerStatusControllerTest.java
index 7a68285..b94deed 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecPowerStatusControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecPowerStatusControllerTest.java
@@ -15,6 +15,7 @@
  */
 package com.android.server.hdmi;
 
+import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY;
 import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC;
 
 import static com.google.common.truth.Truth.assertThat;
@@ -65,7 +66,8 @@
         Context contextSpy = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
         Looper myLooper = mTestLooper.getLooper();
 
-        mHdmiControlService = new HdmiControlService(contextSpy, Collections.emptyList()) {
+        mHdmiControlService = new HdmiControlService(contextSpy, Collections.emptyList(),
+                new FakeAudioDeviceVolumeManagerWrapper()) {
             @Override
             boolean isControlEnabled() {
                 return true;
@@ -105,6 +107,7 @@
         mNativeWrapper.setPortInfo(hdmiPortInfos);
         mNativeWrapper.setPortConnectionStatus(1, true);
         mHdmiControlService.initService();
+        mHdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY);
         mPowerManager = new FakePowerManagerWrapper(contextSpy);
         mHdmiControlService.setPowerManager(mPowerManager);
         mHdmiControlService.getHdmiCecNetwork().initPortInfo();
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
index 3987c32..6266571 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
@@ -91,7 +91,8 @@
 
         HdmiCecConfig hdmiCecConfig = new FakeHdmiCecConfig(mContextSpy);
 
-        mHdmiControlServiceSpy = spy(new HdmiControlService(mContextSpy, Collections.emptyList()));
+        mHdmiControlServiceSpy = spy(new HdmiControlService(mContextSpy, Collections.emptyList(),
+                new FakeAudioDeviceVolumeManagerWrapper()));
         doNothing().when(mHdmiControlServiceSpy)
                 .writeStringSystemProperty(anyString(), anyString());
 
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/OneTouchPlayActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/OneTouchPlayActionTest.java
index 561e6a5..46a4e86 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/OneTouchPlayActionTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/OneTouchPlayActionTest.java
@@ -16,6 +16,7 @@
 
 package com.android.server.hdmi;
 
+import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY;
 import static com.android.server.hdmi.Constants.ADDR_TV;
 import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC;
 import static com.android.server.hdmi.OneTouchPlayAction.STATE_WAITING_FOR_REPORT_POWER_STATUS;
@@ -87,7 +88,8 @@
         mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
         mHdmiCecConfig = new FakeHdmiCecConfig(mContextSpy);
 
-        mHdmiControlService = new HdmiControlService(mContextSpy, Collections.emptyList()) {
+        mHdmiControlService = new HdmiControlService(mContextSpy, Collections.emptyList(),
+                new FakeAudioDeviceVolumeManagerWrapper()) {
             @Override
             AudioManager getAudioManager() {
                 return new AudioManager() {
@@ -120,6 +122,7 @@
         mHdmiControlService.setCecController(hdmiCecController);
         mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService));
         mHdmiControlService.initService();
+        mHdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY);
         mPowerManager = new FakePowerManagerWrapper(mContextSpy);
         mHdmiControlService.setPowerManager(mPowerManager);
         mPhysicalAddress = 0x2000;
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/PlaybackDeviceToAudioSystemAvcTest.java b/services/tests/servicestests/src/com/android/server/hdmi/PlaybackDeviceToAudioSystemAvcTest.java
new file mode 100644
index 0000000..6418602
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/hdmi/PlaybackDeviceToAudioSystemAvcTest.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.hdmi;
+
+import android.hardware.hdmi.DeviceFeatures;
+import android.hardware.hdmi.HdmiControlManager;
+import android.hardware.hdmi.HdmiDeviceInfo;
+import android.media.AudioDeviceAttributes;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+
+import com.google.android.collect.Lists;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.util.Arrays;
+
+/**
+ * Tests for Absolute Volume Control where the local device is a Playback device and the
+ * System Audio device is an Audio System.
+ */
+@SmallTest
+@Presubmit
+@RunWith(JUnit4.class)
+public class PlaybackDeviceToAudioSystemAvcTest extends BaseAbsoluteVolumeControlTest {
+
+    @Override
+    protected HdmiCecLocalDevice createLocalDevice(HdmiControlService hdmiControlService) {
+        return new HdmiCecLocalDevicePlayback(hdmiControlService);
+    }
+
+    @Override
+    protected int getPhysicalAddress() {
+        return 0x1100;
+    }
+
+    @Override
+    protected int getDeviceType() {
+        return HdmiDeviceInfo.DEVICE_PLAYBACK;
+    }
+
+    @Override
+    protected AudioDeviceAttributes getAudioOutputDevice() {
+        return HdmiControlService.AUDIO_OUTPUT_DEVICE_HDMI;
+    }
+
+    @Override
+    protected int getSystemAudioDeviceLogicalAddress() {
+        return Constants.ADDR_AUDIO_SYSTEM;
+    }
+
+    @Override
+    protected int getSystemAudioDeviceType() {
+        return HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM;
+    }
+
+    /**
+     * AVC is disabled if the Audio System disables System Audio mode, and the TV has unknown
+     * support for <Set Audio Volume Level>. It is enabled once the TV confirms support for
+     * <Set Audio Volume Level> and sends <Report Audio Status>.
+     */
+    @Test
+    public void switchToTv_absoluteVolumeControlDisabledUntilAllConditionsMet() {
+        enableAbsoluteVolumeControl();
+
+        // Audio System disables System Audio Mode. AVC should be disabled.
+        receiveSetSystemAudioMode(false);
+        verifyAbsoluteVolumeDisabled();
+
+        // TV reports support for <Set Audio Volume Level>
+        mNativeWrapper.onCecMessage(ReportFeaturesMessage.build(
+                Constants.ADDR_TV, HdmiControlManager.HDMI_CEC_VERSION_2_0,
+                Arrays.asList(HdmiDeviceInfo.DEVICE_TV), Constants.RC_PROFILE_TV,
+                Lists.newArrayList(Constants.RC_PROFILE_TV_NONE),
+                DeviceFeatures.NO_FEATURES_SUPPORTED.toBuilder()
+                        .setSetAudioVolumeLevelSupport(DeviceFeatures.FEATURE_SUPPORTED)
+                        .build()));
+        mTestLooper.dispatchAll();
+
+        // TV reports its initial audio status
+        mNativeWrapper.onCecMessage(HdmiCecMessageBuilder.buildReportAudioStatus(
+                Constants.ADDR_TV,
+                getLogicalAddress(),
+                30,
+                false));
+        mTestLooper.dispatchAll();
+
+        verifyAbsoluteVolumeEnabled();
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/PlaybackDeviceToTvAvcTest.java b/services/tests/servicestests/src/com/android/server/hdmi/PlaybackDeviceToTvAvcTest.java
new file mode 100644
index 0000000..504c3bc
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/hdmi/PlaybackDeviceToTvAvcTest.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.hdmi;
+
+import static org.mockito.Mockito.clearInvocations;
+
+import android.hardware.hdmi.DeviceFeatures;
+import android.hardware.hdmi.HdmiControlManager;
+import android.hardware.hdmi.HdmiDeviceInfo;
+import android.media.AudioDeviceAttributes;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.util.Arrays;
+import java.util.Collections;
+
+/**
+ * Tests for Absolute Volume Control where the local device is a Playback device and the
+ * System Audio device is a TV.
+ */
+@SmallTest
+@Presubmit
+@RunWith(JUnit4.class)
+public class PlaybackDeviceToTvAvcTest extends BaseAbsoluteVolumeControlTest {
+
+    @Override
+    protected HdmiCecLocalDevice createLocalDevice(HdmiControlService hdmiControlService) {
+        return new HdmiCecLocalDevicePlayback(hdmiControlService);
+    }
+
+    @Override
+    protected int getPhysicalAddress() {
+        return 0x1100;
+    }
+
+    @Override
+    protected int getDeviceType() {
+        return HdmiDeviceInfo.DEVICE_PLAYBACK;
+    }
+
+    @Override
+    protected AudioDeviceAttributes getAudioOutputDevice() {
+        return HdmiControlService.AUDIO_OUTPUT_DEVICE_HDMI;
+    }
+
+    @Override
+    protected int getSystemAudioDeviceLogicalAddress() {
+        return Constants.ADDR_TV;
+    }
+
+    @Override
+    protected int getSystemAudioDeviceType() {
+        return HdmiDeviceInfo.DEVICE_TV;
+    }
+
+    /**
+     * AVC is disabled when an Audio System with unknown support for <Set Audio Volume Level>
+     * becomes the System Audio device. It is enabled once the Audio System reports that it
+     * supports <Set Audio Volume Level> and sends <Report Audio Status>.
+     */
+    @Test
+    public void switchToAudioSystem_absoluteVolumeControlDisabledUntilAllConditionsMet() {
+        enableAbsoluteVolumeControl();
+
+        // Audio System enables System Audio Mode. AVC should be disabled.
+        receiveSetSystemAudioMode(true);
+        verifyAbsoluteVolumeDisabled();
+
+        clearInvocations(mAudioManager, mAudioDeviceVolumeManager);
+
+        // Audio System reports support for <Set Audio Volume Level>
+        mNativeWrapper.onCecMessage(ReportFeaturesMessage.build(
+                Constants.ADDR_AUDIO_SYSTEM, HdmiControlManager.HDMI_CEC_VERSION_2_0,
+                Arrays.asList(HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM), Constants.RC_PROFILE_SOURCE,
+                Collections.emptyList(),
+                DeviceFeatures.NO_FEATURES_SUPPORTED.toBuilder()
+                        .setSetAudioVolumeLevelSupport(DeviceFeatures.FEATURE_SUPPORTED)
+                        .build()));
+        mTestLooper.dispatchAll();
+
+        // Audio system reports its initial audio status
+        mNativeWrapper.onCecMessage(HdmiCecMessageBuilder.buildReportAudioStatus(
+                Constants.ADDR_AUDIO_SYSTEM,
+                getLogicalAddress(),
+                30,
+                false));
+        mTestLooper.dispatchAll();
+
+        verifyAbsoluteVolumeEnabled();
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/PowerStatusMonitorActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/PowerStatusMonitorActionTest.java
index c878f99..e5058be 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/PowerStatusMonitorActionTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/PowerStatusMonitorActionTest.java
@@ -16,6 +16,7 @@
 
 package com.android.server.hdmi;
 
+import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY;
 import static com.android.server.hdmi.Constants.ADDR_BROADCAST;
 import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_1;
 import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_2;
@@ -68,7 +69,8 @@
         mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
 
         mHdmiControlService = new HdmiControlService(mContextSpy,
-                Collections.singletonList(HdmiDeviceInfo.DEVICE_TV)) {
+                Collections.singletonList(HdmiDeviceInfo.DEVICE_TV),
+                new FakeAudioDeviceVolumeManagerWrapper()) {
             @Override
             AudioManager getAudioManager() {
                 return new AudioManager() {
@@ -110,6 +112,7 @@
                 new HdmiPortInfo(2, HdmiPortInfo.PORT_INPUT, 0x2000, true, false, false);
         mNativeWrapper.setPortInfo(hdmiPortInfo);
         mHdmiControlService.initService();
+        mHdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY);
         mPowerManager = new FakePowerManagerWrapper(mContextSpy);
         mHdmiControlService.setPowerManager(mPowerManager);
         mPhysicalAddress = 0x0000;
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/RequestSadActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/RequestSadActionTest.java
index 6184c21..f7983ca 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/RequestSadActionTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/RequestSadActionTest.java
@@ -16,6 +16,7 @@
 
 package com.android.server.hdmi;
 
+import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY;
 import static com.android.server.hdmi.Constants.ADDR_AUDIO_SYSTEM;
 import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC;
 
@@ -96,8 +97,8 @@
         mMyLooper = mTestLooper.getLooper();
 
         mHdmiControlService =
-                new HdmiControlService(context,
-                        Collections.emptyList()) {
+                new HdmiControlService(context, Collections.emptyList(),
+                        new FakeAudioDeviceVolumeManagerWrapper()) {
                     @Override
                     boolean isControlEnabled() {
                         return true;
@@ -125,6 +126,7 @@
         mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService));
         mLocalDevices.add(mHdmiCecLocalDeviceTv);
         mHdmiControlService.initService();
+        mHdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY);
         mPowerManager = new FakePowerManagerWrapper(context);
         mHdmiControlService.setPowerManager(mPowerManager);
         mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/RoutingControlActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/RoutingControlActionTest.java
index 0587864..566a7e0 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/RoutingControlActionTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/RoutingControlActionTest.java
@@ -16,6 +16,7 @@
 
 package com.android.server.hdmi;
 
+import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY;
 import static com.android.server.hdmi.Constants.ADDR_AUDIO_SYSTEM;
 import static com.android.server.hdmi.Constants.ADDR_BROADCAST;
 import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_1;
@@ -149,7 +150,7 @@
 
         mHdmiControlService =
                 new HdmiControlService(InstrumentationRegistry.getTargetContext(),
-                        Collections.emptyList()) {
+                        Collections.emptyList(), new FakeAudioDeviceVolumeManagerWrapper()) {
                     @Override
                     boolean isControlEnabled() {
                         return true;
@@ -186,6 +187,7 @@
                         true, false, false);
         mNativeWrapper.setPortInfo(hdmiPortInfos);
         mHdmiControlService.initService();
+        mHdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY);
         mPowerManager = new FakePowerManagerWrapper(context);
         mHdmiControlService.setPowerManager(mPowerManager);
         mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/SetAudioVolumeLevelDiscoveryActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/SetAudioVolumeLevelDiscoveryActionTest.java
index a34b55c..087e407 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/SetAudioVolumeLevelDiscoveryActionTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/SetAudioVolumeLevelDiscoveryActionTest.java
@@ -20,6 +20,7 @@
 import static android.hardware.hdmi.DeviceFeatures.FEATURE_SUPPORTED;
 import static android.hardware.hdmi.DeviceFeatures.FEATURE_SUPPORT_UNKNOWN;
 
+import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY;
 import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC;
 
 import static com.google.common.truth.Truth.assertThat;
@@ -81,7 +82,8 @@
         mContextSpy = spy(new ContextWrapper(
                 InstrumentationRegistry.getInstrumentation().getTargetContext()));
 
-        mHdmiControlServiceSpy = spy(new HdmiControlService(mContextSpy, Collections.emptyList()));
+        mHdmiControlServiceSpy = spy(new HdmiControlService(mContextSpy, Collections.emptyList(),
+                new FakeAudioDeviceVolumeManagerWrapper()));
         doNothing().when(mHdmiControlServiceSpy)
                 .writeStringSystemProperty(anyString(), anyString());
 
@@ -98,6 +100,7 @@
         mHdmiControlServiceSpy.setHdmiMhlController(
                 HdmiMhlControllerStub.create(mHdmiControlServiceSpy));
         mHdmiControlServiceSpy.initService();
+        mHdmiControlServiceSpy.onBootPhase(PHASE_SYSTEM_SERVICES_READY);
         mPowerManager = new FakePowerManagerWrapper(mContextSpy);
         mHdmiControlServiceSpy.setPowerManager(mPowerManager);
 
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioAutoInitiationActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioAutoInitiationActionTest.java
index 9d14341..1644252 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioAutoInitiationActionTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioAutoInitiationActionTest.java
@@ -17,6 +17,7 @@
 package com.android.server.hdmi;
 
 
+import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY;
 import static com.android.server.hdmi.Constants.ADDR_AUDIO_SYSTEM;
 import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC;
 import static com.android.server.hdmi.SystemAudioAutoInitiationAction.RETRIES_ON_TIMEOUT;
@@ -69,7 +70,8 @@
 
         Looper myLooper = mTestLooper.getLooper();
 
-        mHdmiControlService = new HdmiControlService(mContextSpy, Collections.emptyList()) {
+        mHdmiControlService = new HdmiControlService(mContextSpy, Collections.emptyList(),
+                new FakeAudioDeviceVolumeManagerWrapper()) {
             @Override
             AudioManager getAudioManager() {
                 return new AudioManager() {
@@ -108,6 +110,7 @@
                 new HdmiPortInfo(2, HdmiPortInfo.PORT_INPUT, 0x2000, true, false, true);
         mNativeWrapper.setPortInfo(hdmiPortInfos);
         mHdmiControlService.initService();
+        mHdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY);
         mPowerManager = new FakePowerManagerWrapper(mContextSpy);
         mHdmiControlService.setPowerManager(mPowerManager);
         mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioInitiationActionFromAvrTest.java b/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioInitiationActionFromAvrTest.java
index 095c69c..c2f706a 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioInitiationActionFromAvrTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioInitiationActionFromAvrTest.java
@@ -69,7 +69,7 @@
         Context context = InstrumentationRegistry.getTargetContext();
 
         HdmiControlService hdmiControlService = new HdmiControlService(context,
-                Collections.emptyList()) {
+                Collections.emptyList(), new FakeAudioDeviceVolumeManagerWrapper()) {
                     @Override
                     void sendCecCommand(
                             HdmiCecMessage command, @Nullable SendMessageCallback callback) {
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/TvToAudioSystemAvcTest.java b/services/tests/servicestests/src/com/android/server/hdmi/TvToAudioSystemAvcTest.java
new file mode 100644
index 0000000..41c0e0d
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/hdmi/TvToAudioSystemAvcTest.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.hdmi;
+
+import android.hardware.hdmi.HdmiDeviceInfo;
+import android.media.AudioDeviceAttributes;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Tests for Absolute Volume Control where the local device is a TV and the System Audio device
+ * is an Audio System. Assumes that the TV uses ARC (rather than eARC).
+ */
+@SmallTest
+@Presubmit
+@RunWith(JUnit4.class)
+public class TvToAudioSystemAvcTest extends BaseAbsoluteVolumeControlTest {
+
+    @Override
+    protected HdmiCecLocalDevice createLocalDevice(HdmiControlService hdmiControlService) {
+        return new HdmiCecLocalDeviceTv(hdmiControlService);
+    }
+
+    @Override
+    protected int getPhysicalAddress() {
+        return 0x0000;
+    }
+
+    @Override
+    protected int getDeviceType() {
+        return HdmiDeviceInfo.DEVICE_TV;
+    }
+
+    @Override
+    protected AudioDeviceAttributes getAudioOutputDevice() {
+        return HdmiControlService.AUDIO_OUTPUT_DEVICE_HDMI_ARC;
+    }
+
+    @Override
+    protected int getSystemAudioDeviceLogicalAddress() {
+        return Constants.ADDR_AUDIO_SYSTEM;
+    }
+
+    @Override
+    protected int getSystemAudioDeviceType() {
+        return HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM;
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/locales/LocaleManagerBackupRestoreTest.java b/services/tests/servicestests/src/com/android/server/locales/LocaleManagerBackupRestoreTest.java
index bd35be4..c735d18 100644
--- a/services/tests/servicestests/src/com/android/server/locales/LocaleManagerBackupRestoreTest.java
+++ b/services/tests/servicestests/src/com/android/server/locales/LocaleManagerBackupRestoreTest.java
@@ -22,7 +22,6 @@
 import static org.junit.Assert.assertNotNull;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doNothing;
@@ -39,7 +38,6 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
-import android.content.pm.PackageManagerInternal;
 import android.os.Binder;
 import android.os.HandlerThread;
 import android.os.LocaleList;
@@ -102,8 +100,6 @@
     @Mock
     private Context mMockContext;
     @Mock
-    private PackageManagerInternal mMockPackageManagerInternal;
-    @Mock
     private PackageManager mMockPackageManager;
     @Mock
     private LocaleManagerService mMockLocaleManagerService;
@@ -129,7 +125,6 @@
     @Before
     public void setUp() throws Exception {
         mMockContext = mock(Context.class);
-        mMockPackageManagerInternal = mock(PackageManagerInternal.class);
         mMockPackageManager = mock(PackageManager.class);
         mMockLocaleManagerService = mock(LocaleManagerService.class);
         SystemAppUpdateTracker systemAppUpdateTracker = mock(SystemAppUpdateTracker.class);
@@ -141,7 +136,7 @@
         broadcastHandlerThread.start();
 
         mBackupHelper = spy(new ShadowLocaleManagerBackupHelper(mMockContext,
-                mMockLocaleManagerService, mMockPackageManagerInternal, mClock, STAGE_DATA,
+                mMockLocaleManagerService, mMockPackageManager, mClock, STAGE_DATA,
                 broadcastHandlerThread));
         doNothing().when(mBackupHelper).notifyBackupManager();
 
@@ -158,8 +153,8 @@
 
     @Test
     public void testBackupPayload_noAppsInstalled_returnsNull() throws Exception {
-        doReturn(List.of()).when(mMockPackageManagerInternal)
-                .getInstalledApplications(anyLong(), anyInt(), anyInt());
+        doReturn(List.of()).when(mMockPackageManager)
+                .getInstalledApplicationsAsUser(any(), anyInt());
 
         assertNull(mBackupHelper.getBackupPayload(DEFAULT_USER_ID));
     }
@@ -198,8 +193,8 @@
         ApplicationInfo anotherAppInfo = new ApplicationInfo();
         defaultAppInfo.packageName = DEFAULT_PACKAGE_NAME;
         anotherAppInfo.packageName = "com.android.anotherapp";
-        doReturn(List.of(defaultAppInfo, anotherAppInfo)).when(mMockPackageManagerInternal)
-                .getInstalledApplications(anyLong(), anyInt(), anyInt());
+        doReturn(List.of(defaultAppInfo, anotherAppInfo)).when(mMockPackageManager)
+                .getInstalledApplicationsAsUser(any(), anyInt());
 
         setUpLocalesForPackage(DEFAULT_PACKAGE_NAME, DEFAULT_LOCALES);
         // Exception when getting locales for anotherApp.
@@ -447,8 +442,8 @@
         // Retention period has not elapsed.
         setCurrentTimeMillis(
                 DEFAULT_CREATION_TIME_MILLIS + RETENTION_PERIOD.minusHours(1).toMillis());
-        doReturn(List.of()).when(mMockPackageManagerInternal)
-                .getInstalledApplications(anyLong(), anyInt(), anyInt());
+        doReturn(List.of()).when(mMockPackageManager)
+                .getInstalledApplicationsAsUser(any(), anyInt());
         assertNull(mBackupHelper.getBackupPayload(DEFAULT_USER_ID));
 
         checkStageDataExists(DEFAULT_USER_ID);
@@ -456,8 +451,8 @@
         // Exactly RETENTION_PERIOD amount of time has passed so stage data should still not be
         // removed.
         setCurrentTimeMillis(DEFAULT_CREATION_TIME_MILLIS + RETENTION_PERIOD.toMillis());
-        doReturn(List.of()).when(mMockPackageManagerInternal)
-                .getInstalledApplications(anyLong(), anyInt(), anyInt());
+        doReturn(List.of()).when(mMockPackageManager)
+                .getInstalledApplicationsAsUser(any(), anyInt());
         assertNull(mBackupHelper.getBackupPayload(DEFAULT_USER_ID));
 
         checkStageDataExists(DEFAULT_USER_ID);
@@ -465,8 +460,8 @@
         // Retention period has now expired, stage data should be deleted.
         setCurrentTimeMillis(
                 DEFAULT_CREATION_TIME_MILLIS + RETENTION_PERIOD.plusSeconds(1).toMillis());
-        doReturn(List.of()).when(mMockPackageManagerInternal)
-                .getInstalledApplications(anyLong(), anyInt(), anyInt());
+        doReturn(List.of()).when(mMockPackageManager)
+                .getInstalledApplicationsAsUser(any(), anyInt());
         assertNull(mBackupHelper.getBackupPayload(DEFAULT_USER_ID));
 
         checkStageDataDoesNotExist(DEFAULT_USER_ID);
@@ -577,8 +572,8 @@
     private void setUpDummyAppForPackageManager(String packageName) {
         ApplicationInfo dummyApp = new ApplicationInfo();
         dummyApp.packageName = packageName;
-        doReturn(List.of(dummyApp)).when(mMockPackageManagerInternal)
-                .getInstalledApplications(anyLong(), anyInt(), anyInt());
+        doReturn(List.of(dummyApp)).when(mMockPackageManager)
+                .getInstalledApplicationsAsUser(any(), anyInt());
     }
 
     /**
diff --git a/services/tests/servicestests/src/com/android/server/locales/LocaleManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/locales/LocaleManagerServiceTest.java
index 0b3ef45..1dcdbac 100644
--- a/services/tests/servicestests/src/com/android/server/locales/LocaleManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/locales/LocaleManagerServiceTest.java
@@ -23,7 +23,6 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doNothing;
@@ -40,7 +39,6 @@
 import android.content.pm.InstallSourceInfo;
 import android.content.pm.PackageInstaller;
 import android.content.pm.PackageManager;
-import android.content.pm.PackageManagerInternal;
 import android.os.Binder;
 import android.os.LocaleList;
 
@@ -80,7 +78,7 @@
     @Mock
     private Context mMockContext;
     @Mock
-    private PackageManagerInternal mMockPackageManagerInternal;
+    private PackageManager mMockPackageManager;
     @Mock
     private FakePackageConfigurationUpdater mFakePackageConfigurationUpdater;
     @Mock
@@ -95,14 +93,13 @@
         mMockContext = mock(Context.class);
         mMockActivityTaskManager = mock(ActivityTaskManagerInternal.class);
         mMockActivityManager = mock(ActivityManagerInternal.class);
-        mMockPackageManagerInternal = mock(PackageManagerInternal.class);
+        mMockPackageManager = mock(PackageManager.class);
         mMockPackageMonitor = mock(PackageMonitor.class);
 
         // For unit tests, set the default installer info
-        PackageManager mockPackageManager = mock(PackageManager.class);
-        doReturn(DEFAULT_INSTALL_SOURCE_INFO).when(mockPackageManager)
+        doReturn(DEFAULT_INSTALL_SOURCE_INFO).when(mMockPackageManager)
                 .getInstallSourceInfo(anyString());
-        doReturn(mockPackageManager).when(mMockContext).getPackageManager();
+        doReturn(mMockPackageManager).when(mMockContext).getPackageManager();
 
         mFakePackageConfigurationUpdater = new FakePackageConfigurationUpdater();
         doReturn(mFakePackageConfigurationUpdater)
@@ -117,14 +114,14 @@
 
         mMockBackupHelper = mock(ShadowLocaleManagerBackupHelper.class);
         mLocaleManagerService = new LocaleManagerService(mMockContext, mMockActivityTaskManager,
-                mMockActivityManager, mMockPackageManagerInternal,
+                mMockActivityManager, mMockPackageManager,
                 mMockBackupHelper, mMockPackageMonitor);
     }
 
     @Test(expected = SecurityException.class)
     public void testSetApplicationLocales_arbitraryAppWithoutPermissions_fails() throws Exception {
         doReturn(DEFAULT_UID)
-                .when(mMockPackageManagerInternal).getPackageUid(anyString(), anyLong(), anyInt());
+                .when(mMockPackageManager).getPackageUidAsUser(anyString(), any(), anyInt());
         setUpFailingPermissionCheckFor(Manifest.permission.CHANGE_CONFIGURATION);
 
         try {
@@ -170,7 +167,7 @@
     @Test
     public void testSetApplicationLocales_arbitraryAppWithPermission_succeeds() throws Exception {
         doReturn(DEFAULT_UID)
-                .when(mMockPackageManagerInternal).getPackageUid(anyString(), anyLong(), anyInt());
+                .when(mMockPackageManager).getPackageUidAsUser(anyString(), any(), anyInt());
         // if package is not owned by the caller, the calling app should have the following
         //   permission. We will mock this to succeed to imitate that.
         setUpPassingPermissionCheckFor(Manifest.permission.CHANGE_CONFIGURATION);
@@ -186,7 +183,7 @@
     @Test
     public void testSetApplicationLocales_callerOwnsPackage_succeeds() throws Exception {
         doReturn(Binder.getCallingUid())
-                .when(mMockPackageManagerInternal).getPackageUid(anyString(), anyLong(), anyInt());
+                .when(mMockPackageManager).getPackageUidAsUser(anyString(), any(), anyInt());
 
         mLocaleManagerService.setApplicationLocales(DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID,
                 DEFAULT_LOCALES);
@@ -197,8 +194,8 @@
 
     @Test(expected = IllegalArgumentException.class)
     public void testSetApplicationLocales_invalidPackageOrUserId_fails() throws Exception {
-        doReturn(INVALID_UID)
-                .when(mMockPackageManagerInternal).getPackageUid(anyString(), anyLong(), anyInt());
+        doThrow(new PackageManager.NameNotFoundException("Mock"))
+                .when(mMockPackageManager).getPackageUidAsUser(anyString(), any(), anyInt());
         try {
             mLocaleManagerService.setApplicationLocales(DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID,
                     LocaleList.getEmptyLocaleList());
@@ -211,8 +208,8 @@
 
     @Test(expected = SecurityException.class)
     public void testGetApplicationLocales_arbitraryAppWithoutPermission_fails() throws Exception {
-        doReturn(DEFAULT_UID).when(mMockPackageManagerInternal)
-                .getPackageUid(anyString(), anyLong(), anyInt());
+        doReturn(DEFAULT_UID).when(mMockPackageManager)
+                .getPackageUidAsUser(anyString(), any(), anyInt());
         setUpFailingPermissionCheckFor(Manifest.permission.READ_APP_SPECIFIC_LOCALES);
 
         try {
@@ -229,8 +226,8 @@
     public void testGetApplicationLocales_appSpecificConfigAbsent_returnsEmptyList()
             throws Exception {
         // any valid app calling for its own package or having appropriate permission
-        doReturn(DEFAULT_UID).when(mMockPackageManagerInternal)
-                .getPackageUid(anyString(), anyLong(), anyInt());
+        doReturn(DEFAULT_UID).when(mMockPackageManager)
+                .getPackageUidAsUser(anyString(), any(), anyInt());
         setUpPassingPermissionCheckFor(Manifest.permission.READ_APP_SPECIFIC_LOCALES);
         doReturn(null)
                 .when(mMockActivityTaskManager).getApplicationConfig(anyString(), anyInt());
@@ -244,8 +241,8 @@
     @Test
     public void testGetApplicationLocales_appSpecificLocalesAbsent_returnsEmptyList()
             throws Exception {
-        doReturn(DEFAULT_UID).when(mMockPackageManagerInternal)
-                .getPackageUid(anyString(), anyLong(), anyInt());
+        doReturn(DEFAULT_UID).when(mMockPackageManager)
+                .getPackageUidAsUser(anyString(), any(), anyInt());
         setUpPassingPermissionCheckFor(Manifest.permission.READ_APP_SPECIFIC_LOCALES);
         doReturn(new PackageConfig(/* nightMode = */ 0, /* locales = */ null))
                 .when(mMockActivityTaskManager).getApplicationConfig(any(), anyInt());
@@ -259,8 +256,8 @@
     @Test
     public void testGetApplicationLocales_callerOwnsAppAndConfigPresent_returnsLocales()
             throws Exception {
-        doReturn(Binder.getCallingUid()).when(mMockPackageManagerInternal)
-                .getPackageUid(anyString(), anyLong(), anyInt());
+        doReturn(Binder.getCallingUid()).when(mMockPackageManager)
+                .getPackageUidAsUser(anyString(), any(), anyInt());
         doReturn(new PackageConfig(/* nightMode = */ 0, DEFAULT_LOCALES))
                 .when(mMockActivityTaskManager).getApplicationConfig(anyString(), anyInt());
 
@@ -273,8 +270,8 @@
     @Test
     public void testGetApplicationLocales_arbitraryCallerWithPermissions_returnsLocales()
             throws Exception {
-        doReturn(DEFAULT_UID).when(mMockPackageManagerInternal)
-                .getPackageUid(anyString(), anyLong(), anyInt());
+        doReturn(DEFAULT_UID).when(mMockPackageManager)
+                .getPackageUidAsUser(anyString(), any(), anyInt());
         setUpPassingPermissionCheckFor(Manifest.permission.READ_APP_SPECIFIC_LOCALES);
         doReturn(new PackageConfig(/* nightMode = */ 0, DEFAULT_LOCALES))
                 .when(mMockActivityTaskManager).getApplicationConfig(anyString(), anyInt());
@@ -288,10 +285,10 @@
     @Test
     public void testGetApplicationLocales_callerIsInstaller_returnsLocales()
             throws Exception {
-        doReturn(DEFAULT_UID).when(mMockPackageManagerInternal)
-                .getPackageUid(eq(DEFAULT_PACKAGE_NAME), anyLong(), anyInt());
-        doReturn(Binder.getCallingUid()).when(mMockPackageManagerInternal)
-                .getPackageUid(eq(DEFAULT_INSTALLER_PACKAGE_NAME), anyLong(), anyInt());
+        doReturn(DEFAULT_UID).when(mMockPackageManager)
+                .getPackageUidAsUser(eq(DEFAULT_PACKAGE_NAME), any(), anyInt());
+        doReturn(Binder.getCallingUid()).when(mMockPackageManager)
+                .getPackageUidAsUser(eq(DEFAULT_INSTALLER_PACKAGE_NAME), any(), anyInt());
         doReturn(new PackageConfig(/* nightMode = */ 0, DEFAULT_LOCALES))
                 .when(mMockActivityTaskManager).getApplicationConfig(anyString(), anyInt());
 
diff --git a/services/tests/servicestests/src/com/android/server/locales/ShadowLocaleManagerBackupHelper.java b/services/tests/servicestests/src/com/android/server/locales/ShadowLocaleManagerBackupHelper.java
index ad9be0d..e403c87 100644
--- a/services/tests/servicestests/src/com/android/server/locales/ShadowLocaleManagerBackupHelper.java
+++ b/services/tests/servicestests/src/com/android/server/locales/ShadowLocaleManagerBackupHelper.java
@@ -17,7 +17,7 @@
 package com.android.server.locales;
 
 import android.content.Context;
-import android.content.pm.PackageManagerInternal;
+import android.content.pm.PackageManager;
 import android.os.HandlerThread;
 import android.util.SparseArray;
 
@@ -31,9 +31,10 @@
 public class ShadowLocaleManagerBackupHelper extends LocaleManagerBackupHelper {
     ShadowLocaleManagerBackupHelper(Context context,
             LocaleManagerService localeManagerService,
-            PackageManagerInternal pmInternal, Clock clock,
+            PackageManager packageManager, Clock clock,
             SparseArray<LocaleManagerBackupHelper.StagedData> stagedData,
             HandlerThread broadcastHandlerThread) {
-        super(context, localeManagerService, pmInternal, clock, stagedData, broadcastHandlerThread);
+        super(context, localeManagerService, packageManager, clock, stagedData,
+                broadcastHandlerThread);
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/locales/SystemAppUpdateTrackerTest.java b/services/tests/servicestests/src/com/android/server/locales/SystemAppUpdateTrackerTest.java
index db602ca..808b74e 100644
--- a/services/tests/servicestests/src/com/android/server/locales/SystemAppUpdateTrackerTest.java
+++ b/services/tests/servicestests/src/com/android/server/locales/SystemAppUpdateTrackerTest.java
@@ -38,7 +38,6 @@
 import android.content.pm.InstallSourceInfo;
 import android.content.pm.PackageInstaller;
 import android.content.pm.PackageManager;
-import android.content.pm.PackageManagerInternal;
 import android.os.Binder;
 import android.os.Environment;
 import android.os.LocaleList;
@@ -93,8 +92,6 @@
     @Mock
     PackageManager mMockPackageManager;
     @Mock
-    private PackageManagerInternal mMockPackageManagerInternal;
-    @Mock
     private ActivityTaskManagerInternal mMockActivityTaskManager;
     @Mock
     private ActivityManagerInternal mMockActivityManager;
@@ -110,21 +107,20 @@
         mMockContext = mock(Context.class);
         mMockActivityTaskManager = mock(ActivityTaskManagerInternal.class);
         mMockActivityManager = mock(ActivityManagerInternal.class);
-        mMockPackageManagerInternal = mock(PackageManagerInternal.class);
+        mMockPackageManager = mock(PackageManager.class);
         LocaleManagerBackupHelper mockLocaleManagerBackupHelper =
                 mock(ShadowLocaleManagerBackupHelper.class);
         // PackageMonitor is not needed in LocaleManagerService for these tests hence it is
         // passed as null.
         mLocaleManagerService = new LocaleManagerService(mMockContext,
                 mMockActivityTaskManager, mMockActivityManager,
-                mMockPackageManagerInternal, mockLocaleManagerBackupHelper,
+                mMockPackageManager, mockLocaleManagerBackupHelper,
                 /* mPackageMonitor= */ null);
 
         doReturn(DEFAULT_USER_ID).when(mMockActivityManager)
                 .handleIncomingUser(anyInt(), anyInt(), eq(DEFAULT_USER_ID), anyBoolean(), anyInt(),
                         anyString(), anyString());
 
-        mMockPackageManager = mock(PackageManager.class);
         doReturn(DEFAULT_INSTALL_SOURCE_INFO).when(mMockPackageManager)
                 .getInstallSourceInfo(anyString());
         doReturn(mMockPackageManager).when(mMockContext).getPackageManager();
diff --git a/services/tests/servicestests/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java b/services/tests/servicestests/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java
index 004d7bc..07cca0c 100644
--- a/services/tests/servicestests/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java
@@ -84,15 +84,15 @@
 @RunWith(AndroidJUnit4.class)
 public class PackageParserLegacyCoreTest {
     private static final String RELEASED = null;
-    private static final String OLDER_PRE_RELEASE = "A";
-    private static final String PRE_RELEASE = "B";
-    private static final String NEWER_PRE_RELEASE = "C";
+    private static final String OLDER_PRE_RELEASE = "Q";
+    private static final String PRE_RELEASE = "R";
+    private static final String NEWER_PRE_RELEASE = "Z";
 
     // Codenames with a fingerprint attached to them. These may only be present in the apps
     // declared min SDK and not as platform codenames.
-    private static final String OLDER_PRE_RELEASE_WITH_FINGERPRINT = "A.fingerprint";
-    private static final String PRE_RELEASE_WITH_FINGERPRINT = "B.fingerprint";
-    private static final String NEWER_PRE_RELEASE_WITH_FINGERPRINT = "C.fingerprint";
+    private static final String OLDER_PRE_RELEASE_WITH_FINGERPRINT = "Q.fingerprint";
+    private static final String PRE_RELEASE_WITH_FINGERPRINT = "R.fingerprint";
+    private static final String NEWER_PRE_RELEASE_WITH_FINGERPRINT = "Z.fingerprint";
 
     private static final String[] CODENAMES_RELEASED = { /* empty */};
     private static final String[] CODENAMES_PRE_RELEASE = {PRE_RELEASE};
@@ -199,13 +199,14 @@
     }
 
     private void verifyComputeTargetSdkVersion(int targetSdkVersion, String targetSdkCodename,
-            boolean isPlatformReleased, int expectedTargetSdk) {
+            boolean isPlatformReleased, boolean allowUnknownCodenames, int expectedTargetSdk) {
         final ParseTypeImpl input = ParseTypeImpl.forParsingWithoutPlatformCompat();
         final ParseResult<Integer> result = FrameworkParsingPackageUtils.computeTargetSdkVersion(
                 targetSdkVersion,
                 targetSdkCodename,
                 isPlatformReleased ? CODENAMES_RELEASED : CODENAMES_PRE_RELEASE,
-                input);
+                input,
+                allowUnknownCodenames);
 
         if (expectedTargetSdk == -1) {
             assertTrue(result.isError());
@@ -220,40 +221,61 @@
         // Do allow older release targetSdkVersion on pre-release platform.
         // APP: Released API 10
         // DEV: Pre-release API 20
-        verifyComputeTargetSdkVersion(OLDER_VERSION, RELEASED, false, OLDER_VERSION);
+        verifyComputeTargetSdkVersion(OLDER_VERSION, RELEASED, false, false, OLDER_VERSION);
 
         // Do allow same release targetSdkVersion on pre-release platform.
         // APP: Released API 20
         // DEV: Pre-release API 20
-        verifyComputeTargetSdkVersion(PLATFORM_VERSION, RELEASED, false, PLATFORM_VERSION);
+        verifyComputeTargetSdkVersion(PLATFORM_VERSION, RELEASED, false, false, PLATFORM_VERSION);
 
         // Do allow newer release targetSdkVersion on pre-release platform.
         // APP: Released API 30
         // DEV: Pre-release API 20
-        verifyComputeTargetSdkVersion(NEWER_VERSION, RELEASED, false, NEWER_VERSION);
+        verifyComputeTargetSdkVersion(NEWER_VERSION, RELEASED, false, false, NEWER_VERSION);
 
         // Don't allow older pre-release targetSdkVersion on pre-release platform.
         // APP: Pre-release API 10
         // DEV: Pre-release API 20
-        verifyComputeTargetSdkVersion(OLDER_VERSION, OLDER_PRE_RELEASE, false, -1);
-        verifyComputeTargetSdkVersion(OLDER_VERSION, OLDER_PRE_RELEASE_WITH_FINGERPRINT, false, -1);
+        verifyComputeTargetSdkVersion(OLDER_VERSION, OLDER_PRE_RELEASE, false, false, -1);
+        verifyComputeTargetSdkVersion(OLDER_VERSION, OLDER_PRE_RELEASE_WITH_FINGERPRINT, false,
+                false, -1
+        );
 
+        // Don't allow older pre-release targetSdkVersion on pre-release platform when
+        // allowUnknownCodenames is true.
+        // APP: Pre-release API 10
+        // DEV: Pre-release API 20
+        verifyComputeTargetSdkVersion(OLDER_VERSION, OLDER_PRE_RELEASE, false,
+                true, -1);
+        verifyComputeTargetSdkVersion(OLDER_VERSION, OLDER_PRE_RELEASE_WITH_FINGERPRINT, false,
+                true, -1);
 
         // Do allow same pre-release targetSdkVersion on pre-release platform,
         // but overwrite the specified version with CUR_DEVELOPMENT.
         // APP: Pre-release API 20
         // DEV: Pre-release API 20
         verifyComputeTargetSdkVersion(PLATFORM_VERSION, PRE_RELEASE, false,
-                Build.VERSION_CODES.CUR_DEVELOPMENT);
+                false, Build.VERSION_CODES.CUR_DEVELOPMENT);
         verifyComputeTargetSdkVersion(PLATFORM_VERSION, PRE_RELEASE_WITH_FINGERPRINT, false,
-                Build.VERSION_CODES.CUR_DEVELOPMENT);
-
+                false, Build.VERSION_CODES.CUR_DEVELOPMENT);
 
         // Don't allow newer pre-release targetSdkVersion on pre-release platform.
         // APP: Pre-release API 30
         // DEV: Pre-release API 20
-        verifyComputeTargetSdkVersion(NEWER_VERSION, NEWER_PRE_RELEASE, false, -1);
-        verifyComputeTargetSdkVersion(NEWER_VERSION, NEWER_PRE_RELEASE_WITH_FINGERPRINT, false, -1);
+        verifyComputeTargetSdkVersion(NEWER_VERSION, NEWER_PRE_RELEASE, false, false, -1);
+        verifyComputeTargetSdkVersion(NEWER_VERSION, NEWER_PRE_RELEASE_WITH_FINGERPRINT, false,
+                false, -1
+        );
+
+        // Do allow newer pre-release targetSdkVersion on pre-release platform when
+        // allowUnknownCodenames is true.
+        // APP: Pre-release API 30
+        // DEV: Pre-release API 20
+        verifyComputeTargetSdkVersion(NEWER_VERSION, NEWER_PRE_RELEASE, false,
+                true, Build.VERSION_CODES.CUR_DEVELOPMENT);
+        verifyComputeTargetSdkVersion(NEWER_VERSION, NEWER_PRE_RELEASE_WITH_FINGERPRINT, false,
+                true, Build.VERSION_CODES.CUR_DEVELOPMENT);
+
     }
 
     @Test
@@ -261,36 +283,58 @@
         // Do allow older release targetSdkVersion on released platform.
         // APP: Released API 10
         // DEV: Released API 20
-        verifyComputeTargetSdkVersion(OLDER_VERSION, RELEASED, true, OLDER_VERSION);
+        verifyComputeTargetSdkVersion(OLDER_VERSION, RELEASED, true, false, OLDER_VERSION);
 
         // Do allow same release targetSdkVersion on released platform.
         // APP: Released API 20
         // DEV: Released API 20
-        verifyComputeTargetSdkVersion(PLATFORM_VERSION, RELEASED, true, PLATFORM_VERSION);
+        verifyComputeTargetSdkVersion(PLATFORM_VERSION, RELEASED, true, false, PLATFORM_VERSION);
 
         // Do allow newer release targetSdkVersion on released platform.
         // APP: Released API 30
         // DEV: Released API 20
-        verifyComputeTargetSdkVersion(NEWER_VERSION, RELEASED, true, NEWER_VERSION);
+        verifyComputeTargetSdkVersion(NEWER_VERSION, RELEASED, true, false, NEWER_VERSION);
 
         // Don't allow older pre-release targetSdkVersion on released platform.
         // APP: Pre-release API 10
         // DEV: Released API 20
-        verifyComputeTargetSdkVersion(OLDER_VERSION, OLDER_PRE_RELEASE, true, -1);
-        verifyComputeTargetSdkVersion(OLDER_VERSION, OLDER_PRE_RELEASE_WITH_FINGERPRINT, true, -1);
+        verifyComputeTargetSdkVersion(OLDER_VERSION, OLDER_PRE_RELEASE, true, false, -1);
+        verifyComputeTargetSdkVersion(OLDER_VERSION, OLDER_PRE_RELEASE_WITH_FINGERPRINT, true,
+                false, -1
+        );
 
         // Don't allow same pre-release targetSdkVersion on released platform.
         // APP: Pre-release API 20
         // DEV: Released API 20
-        verifyComputeTargetSdkVersion(PLATFORM_VERSION, PRE_RELEASE, true, -1);
-        verifyComputeTargetSdkVersion(PLATFORM_VERSION, PRE_RELEASE_WITH_FINGERPRINT, true, -1);
+        verifyComputeTargetSdkVersion(PLATFORM_VERSION, PRE_RELEASE, true, false, -1);
+        verifyComputeTargetSdkVersion(PLATFORM_VERSION, PRE_RELEASE_WITH_FINGERPRINT, true, false,
+                -1
+        );
 
+        // Don't allow same pre-release targetSdkVersion on released platform when
+        // allowUnknownCodenames is true.
+        // APP: Pre-release API 20
+        // DEV: Released API 20
+        verifyComputeTargetSdkVersion(PLATFORM_VERSION, PRE_RELEASE, true, true,
+                -1);
+        verifyComputeTargetSdkVersion(PLATFORM_VERSION, PRE_RELEASE_WITH_FINGERPRINT, true, true,
+                -1);
 
         // Don't allow newer pre-release targetSdkVersion on released platform.
         // APP: Pre-release API 30
         // DEV: Released API 20
-        verifyComputeTargetSdkVersion(NEWER_VERSION, NEWER_PRE_RELEASE, true, -1);
-        verifyComputeTargetSdkVersion(NEWER_VERSION, NEWER_PRE_RELEASE_WITH_FINGERPRINT, true, -1);
+        verifyComputeTargetSdkVersion(NEWER_VERSION, NEWER_PRE_RELEASE, true, false, -1);
+        verifyComputeTargetSdkVersion(NEWER_VERSION, NEWER_PRE_RELEASE_WITH_FINGERPRINT, true,
+                false, -1
+        );
+        // Do allow newer pre-release targetSdkVersion on released platform when
+        // allowUnknownCodenames is true.
+        // APP: Pre-release API 30
+        // DEV: Released API 20
+        verifyComputeTargetSdkVersion(NEWER_VERSION, NEWER_PRE_RELEASE, true, true,
+                Build.VERSION_CODES.CUR_DEVELOPMENT);
+        verifyComputeTargetSdkVersion(NEWER_VERSION, NEWER_PRE_RELEASE_WITH_FINGERPRINT, true,
+                true, Build.VERSION_CODES.CUR_DEVELOPMENT);
     }
 
     /**
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java
index 9f13591..de5f6ed 100644
--- a/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java
+++ b/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java
@@ -276,7 +276,7 @@
     }
 
     @Test
-    public void vibrate_singleVibratorRepeatingShortAlwaysOnWaveform_turnsVibratorOnForASecond()
+    public void vibrate_singleVibratorRepeatingShortAlwaysOnWaveform_turnsVibratorOnForLonger()
             throws Exception {
         FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
         fakeVibrator.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
@@ -293,11 +293,71 @@
 
         verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BY_USER);
         assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
-        assertEquals(Arrays.asList(expectedOneShot(1000)),
+        assertEquals(Arrays.asList(expectedOneShot(5000)),
                 fakeVibrator.getEffectSegments(vibrationId));
     }
 
     @Test
+    public void vibrate_singleVibratorRepeatingPwle_generatesLargestPwles() throws Exception {
+        FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
+        fakeVibrator.setCapabilities(IVibrator.CAP_COMPOSE_PWLE_EFFECTS);
+        fakeVibrator.setMinFrequency(100);
+        fakeVibrator.setResonantFrequency(150);
+        fakeVibrator.setFrequencyResolution(50);
+        fakeVibrator.setMaxAmplitudes(1, 1, 1);
+        fakeVibrator.setPwleSizeMax(10);
+
+        long vibrationId = 1;
+        VibrationEffect effect = VibrationEffect.startWaveform(targetAmplitude(1))
+                // Very long segment so thread will be cancelled after first PWLE is triggered.
+                .addTransition(Duration.ofMillis(100), targetFrequency(100))
+                .build();
+        VibrationEffect repeatingEffect = VibrationEffect.startComposition()
+                .repeatEffectIndefinitely(effect)
+                .compose();
+        VibrationStepConductor conductor = startThreadAndDispatcher(vibrationId, repeatingEffect);
+
+        assertTrue(waitUntil(() -> !fakeVibrator.getEffectSegments(vibrationId).isEmpty(),
+                TEST_TIMEOUT_MILLIS));
+        conductor.notifyCancelled(Vibration.Status.CANCELLED_BY_USER, /* immediate= */ false);
+        waitForCompletion();
+
+        // PWLE size max was used to generate a single vibrate call with 10 segments.
+        verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BY_USER);
+        assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
+        assertEquals(10, fakeVibrator.getEffectSegments(vibrationId).size());
+    }
+
+    @Test
+    public void vibrate_singleVibratorRepeatingPrimitives_generatesLargestComposition()
+            throws Exception {
+        FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
+        fakeVibrator.setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
+        fakeVibrator.setSupportedPrimitives(VibrationEffect.Composition.PRIMITIVE_CLICK);
+        fakeVibrator.setCompositionSizeMax(10);
+
+        long vibrationId = 1;
+        VibrationEffect effect = VibrationEffect.startComposition()
+                // Very long delay so thread will be cancelled after first PWLE is triggered.
+                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f, 100)
+                .compose();
+        VibrationEffect repeatingEffect = VibrationEffect.startComposition()
+                .repeatEffectIndefinitely(effect)
+                .compose();
+        VibrationStepConductor conductor = startThreadAndDispatcher(vibrationId, repeatingEffect);
+
+        assertTrue(waitUntil(() -> !fakeVibrator.getEffectSegments(vibrationId).isEmpty(),
+                TEST_TIMEOUT_MILLIS));
+        conductor.notifyCancelled(Vibration.Status.CANCELLED_SUPERSEDED, /* immediate= */ false);
+        waitForCompletion();
+
+        // Composition size max was used to generate a single vibrate call with 10 primitives.
+        verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_SUPERSEDED);
+        assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
+        assertEquals(10, fakeVibrator.getEffectSegments(vibrationId).size());
+    }
+
+    @Test
     public void vibrate_singleVibratorRepeatingLongAlwaysOnWaveform_turnsVibratorOnForACycle()
             throws Exception {
         FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
@@ -319,7 +379,7 @@
                 fakeVibrator.getEffectSegments(vibrationId));
     }
 
-
+    @LargeTest
     @Test
     public void vibrate_singleVibratorRepeatingAlwaysOnWaveform_turnsVibratorBackOn()
             throws Exception {
@@ -329,22 +389,21 @@
         long vibrationId = 1;
         int[] amplitudes = new int[]{1, 2};
         VibrationEffect effect = VibrationEffect.createWaveform(
-                new long[]{900, 50}, amplitudes, 0);
+                new long[]{4900, 50}, amplitudes, 0);
         VibrationStepConductor conductor = startThreadAndDispatcher(vibrationId, effect);
 
-        assertTrue(waitUntil(() -> fakeVibrator.getAmplitudes().size() > 2 * amplitudes.length,
-                1000 + TEST_TIMEOUT_MILLIS));
+        assertTrue(waitUntil(() -> fakeVibrator.getEffectSegments(vibrationId).size() > 1,
+                5000 + TEST_TIMEOUT_MILLIS));
         conductor.notifyCancelled(Vibration.Status.CANCELLED_BY_USER, /* immediate= */ false);
         waitForCompletion();
 
         verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BY_USER);
         assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
-        assertEquals(2, fakeVibrator.getEffectSegments(vibrationId).size());
-        // First time turn vibrator ON for minimum of 1s.
-        assertEquals(1000L, fakeVibrator.getEffectSegments(vibrationId).get(0).getDuration());
+        // First time turn vibrator ON for minimum of 5s.
+        assertEquals(5000L, fakeVibrator.getEffectSegments(vibrationId).get(0).getDuration());
         // Vibrator turns off in the middle of the second execution of first step, turn it back ON
-        // for another 1s + remaining of 850ms.
-        assertEquals(1850,
+        // for another 5s + remaining of 850ms.
+        assertEquals(4900 + 50 + 4900,
                 fakeVibrator.getEffectSegments(vibrationId).get(1).getDuration(), /* delta= */ 20);
         // Set amplitudes for a cycle {1, 2}, start second loop then turn it back on to same value.
         assertEquals(expectedAmplitudes(1, 2, 1, 1),
@@ -530,12 +589,18 @@
 
     @Test
     public void vibrate_singleVibratorComposedEffects_runsDifferentVibrations() throws Exception {
-        mVibratorProviders.get(VIBRATOR_ID).setSupportedEffects(VibrationEffect.EFFECT_CLICK);
-        mVibratorProviders.get(VIBRATOR_ID).setSupportedPrimitives(
+        FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
+        fakeVibrator.setSupportedEffects(VibrationEffect.EFFECT_CLICK);
+        fakeVibrator.setSupportedPrimitives(
                 VibrationEffect.Composition.PRIMITIVE_CLICK,
                 VibrationEffect.Composition.PRIMITIVE_TICK);
-        mVibratorProviders.get(VIBRATOR_ID).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS,
-                IVibrator.CAP_AMPLITUDE_CONTROL);
+        fakeVibrator.setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS,
+                IVibrator.CAP_COMPOSE_PWLE_EFFECTS, IVibrator.CAP_AMPLITUDE_CONTROL);
+        fakeVibrator.setMinFrequency(100);
+        fakeVibrator.setResonantFrequency(150);
+        fakeVibrator.setFrequencyResolution(50);
+        fakeVibrator.setMaxAmplitudes(
+                0.5f /* 100Hz*/, 1 /* 150Hz */, 0.6f /* 200Hz */);
 
         long vibrationId = 1;
         VibrationEffect effect = VibrationEffect.startComposition()
@@ -543,7 +608,11 @@
                 .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f)
                 .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 0.5f)
                 .addEffect(VibrationEffect.get(VibrationEffect.EFFECT_CLICK))
-                .addOffDuration(Duration.ofMillis(100))
+                .addEffect(VibrationEffect.startWaveform()
+                        .addTransition(Duration.ofMillis(10),
+                                targetAmplitude(1), targetFrequency(100))
+                        .addTransition(Duration.ofMillis(20), targetFrequency(120))
+                        .build())
                 .addEffect(VibrationEffect.get(VibrationEffect.EFFECT_CLICK))
                 .compose();
         startThreadAndDispatcher(vibrationId, effect);
@@ -552,7 +621,7 @@
         // Use first duration the vibrator is turned on since we cannot estimate the clicks.
         verify(mManagerHooks).noteVibratorOn(eq(UID), eq(10L));
         verify(mManagerHooks).noteVibratorOff(eq(UID));
-        verify(mControllerCallbacks, times(4)).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
+        verify(mControllerCallbacks, times(5)).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
         verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
         assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
         assertEquals(Arrays.asList(
@@ -560,6 +629,10 @@
                 expectedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 0),
                 expectedPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 0.5f, 0),
                 expectedPrebaked(VibrationEffect.EFFECT_CLICK),
+                expectedRamp(/* startAmplitude= */ 0, /* endAmplitude= */ 0.5f,
+                        /* startFrequencyHz= */ 150, /* endFrequencyHz= */ 100, /* duration= */ 10),
+                expectedRamp(/* startAmplitude= */ 0.5f, /* endAmplitude= */ 0.7f,
+                        /* startFrequencyHz= */ 100, /* endFrequencyHz= */ 120, /* duration= */ 20),
                 expectedPrebaked(VibrationEffect.EFFECT_CLICK)),
                 mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibrationId));
         assertEquals(expectedAmplitudes(100), mVibratorProviders.get(VIBRATOR_ID).getAmplitudes());
@@ -605,30 +678,36 @@
     }
 
     @Test
-    public void vibrate_singleVibratorLargePwle_splitsVibratorComposeCalls() {
+    public void vibrate_singleVibratorLargePwle_splitsComposeCallWhenAmplitudeIsLowest() {
         FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
         fakeVibrator.setCapabilities(IVibrator.CAP_COMPOSE_PWLE_EFFECTS);
         fakeVibrator.setMinFrequency(100);
         fakeVibrator.setResonantFrequency(150);
         fakeVibrator.setFrequencyResolution(50);
         fakeVibrator.setMaxAmplitudes(1, 1, 1);
-        fakeVibrator.setPwleSizeMax(2);
+        fakeVibrator.setPwleSizeMax(3);
 
         long vibrationId = 1;
         VibrationEffect effect = VibrationEffect.startWaveform(targetAmplitude(1))
                 .addSustain(Duration.ofMillis(10))
                 .addTransition(Duration.ofMillis(20), targetAmplitude(0))
+                // Waveform will be split here, after vibration goes to zero amplitude
                 .addTransition(Duration.ZERO, targetAmplitude(0.8f), targetFrequency(100))
                 .addSustain(Duration.ofMillis(30))
                 .addTransition(Duration.ofMillis(40), targetAmplitude(0.6f), targetFrequency(200))
+                // Waveform will be split here at lowest amplitude.
+                .addTransition(Duration.ofMillis(40), targetAmplitude(0.7f), targetFrequency(200))
+                .addTransition(Duration.ofMillis(40), targetAmplitude(0.6f), targetFrequency(200))
                 .build();
         startThreadAndDispatcher(vibrationId, effect);
         waitForCompletion();
 
         verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED);
-        // Vibrator compose called twice.
-        verify(mControllerCallbacks, times(2)).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
-        assertEquals(4, fakeVibrator.getEffectSegments(vibrationId).size());
+
+        // Vibrator compose called 3 times with 2 segments instead of 2 times with 3 segments.
+        // Using best split points instead of max-packing PWLEs.
+        verify(mControllerCallbacks, times(3)).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
+        assertEquals(6, fakeVibrator.getEffectSegments(vibrationId).size());
     }
 
     @Test
diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java
index df114db..e0f5b20 100644
--- a/telecomm/java/android/telecom/TelecomManager.java
+++ b/telecomm/java/android/telecom/TelecomManager.java
@@ -2601,6 +2601,33 @@
     }
 
     /**
+     * Determines whether there are any ongoing {@link PhoneAccount#CAPABILITY_SELF_MANAGED}
+     * calls for a given {@code packageName} and {@code userHandle}.
+     *
+     * @param packageName the package name of the app to check calls for.
+     * @param userHandle the user handle on which to check for calls.
+     * @return {@code true} if there are ongoing calls, {@code false} otherwise.
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+    public boolean isInSelfManagedCall(@NonNull String packageName,
+            @NonNull UserHandle userHandle) {
+        ITelecomService service = getTelecomService();
+        if (service != null) {
+            try {
+                return service.isInSelfManagedCall(packageName, userHandle,
+                        mContext.getOpPackageName());
+            } catch (RemoteException e) {
+                Log.e(TAG, "RemoteException isInSelfManagedCall: " + e);
+                e.rethrowFromSystemServer();
+                return false;
+            }
+        } else {
+            throw new IllegalStateException("Telecom service is not present");
+        }
+    }
+
+    /**
      * Handles {@link Intent#ACTION_CALL} intents trampolined from UserCallActivity.
      * @param intent The {@link Intent#ACTION_CALL} intent to handle.
      * @param callingPackageProxy The original package that called this before it was trampolined.
diff --git a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
index 9999c89..07e18d5 100644
--- a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
+++ b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
@@ -22,6 +22,7 @@
 import android.telecom.PhoneAccountHandle;
 import android.net.Uri;
 import android.os.Bundle;
+import android.os.UserHandle;
 import android.telecom.PhoneAccount;
 
 /**
@@ -374,4 +375,10 @@
      * @see TelecomServiceImpl#setTestCallDiagnosticService
      */
     void setTestCallDiagnosticService(in String packageName);
+
+    /**
+     * @see TelecomServiceImpl#isInSelfManagedCall
+     */
+    boolean isInSelfManagedCall(String packageName, in UserHandle userHandle,
+        String callingPackage);
 }
diff --git a/test-legacy/Android.mk b/test-legacy/Android.mk
index 284008c..da9dc25 100644
--- a/test-legacy/Android.mk
+++ b/test-legacy/Android.mk
@@ -40,6 +40,10 @@
 
 include $(BUILD_STATIC_JAVA_LIBRARY)
 
+$(call declare-license-metadata,$(full_classes_jar),\
+    SPDX-license-identifier-Apache-2.0 SPDX-license-identifier-MIT SPDX-license-identifier-Unicode-DFS,\
+    notice,$(LOCAL_PATH)/../NOTICE,Android,frameworks/base)
+
 # Archive a copy of the classes.jar in SDK build.
 $(call dist-for-goals,sdk,$(full_classes_jar):android.test.legacy.jar)
 
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowToOverViewTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowToOverViewTest.kt
index db2e645..e2e1ae8 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowToOverViewTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowToOverViewTest.kt
@@ -114,7 +114,7 @@
         }
     }
 
-    @Presubmit
+    @FlakyTest(bugId = 228011606)
     @Test
     fun imeLayerIsVisibleAndAssociatedWithAppWidow() {
         testSpec.assertLayersStart {
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest.kt
index 7e3ed82..4b268a8 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest.kt
@@ -32,12 +32,15 @@
 import com.android.server.wm.flicker.helpers.ImeAppAutoFocusHelper
 import com.android.server.wm.flicker.helpers.SimpleAppHelper
 import com.android.server.wm.flicker.helpers.WindowUtils
+import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
 import com.android.server.wm.flicker.helpers.setRotation
 import com.android.server.wm.flicker.navBarWindowIsVisible
 import com.android.server.wm.flicker.statusBarWindowIsVisible
 import com.android.server.wm.traces.common.FlickerComponentName
 import com.android.server.wm.traces.common.WindowManagerConditionsFactory
 import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
+import org.junit.Assume
+import org.junit.Before
 
 import org.junit.FixMethodOrder
 import org.junit.Test
@@ -55,11 +58,16 @@
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
 @Group4
 @Presubmit
-class SwitchImeWindowsFromGestureNavTest(private val testSpec: FlickerTestParameter) {
+open class SwitchImeWindowsFromGestureNavTest(private val testSpec: FlickerTestParameter) {
     private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
     private val testApp = SimpleAppHelper(instrumentation)
     private val imeTestApp = ImeAppAutoFocusHelper(instrumentation, testSpec.startRotation)
 
+    @Before
+    open fun before() {
+        Assume.assumeFalse(isShellTransitionsEnabled)
+    }
+
     @FlickerBuilderProvider
     fun buildFlicker(): FlickerBuilder {
         return FlickerBuilder(instrumentation).apply {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTestShellTransit.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest_ShellTransit.kt
similarity index 76%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTestShellTransit.kt
rename to tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest_ShellTransit.kt
index 2252a94..edd52b7 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTestShellTransit.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest_ShellTransit.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2022 The Android Open Source Project
+ * 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.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.flicker.pip
+package com.android.server.wm.flicker.ime
 
 import androidx.test.filters.FlakyTest
 import androidx.test.filters.RequiresDevice
@@ -24,22 +24,26 @@
 import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
 import org.junit.Assume
 import org.junit.Before
+
 import org.junit.FixMethodOrder
 import org.junit.runner.RunWith
 import org.junit.runners.MethodSorters
 import org.junit.runners.Parameterized
 
+/**
+ * Test IME windows switching with 2-Buttons or gestural navigation.
+ * To run this test: `atest FlickerTests:SwitchImeWindowsFromGestureNavTest`
+ */
 @RequiresDevice
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
 @Group4
-class PipRotationTestShellTransit(testSpec: FlickerTestParameter) : PipRotationTest(testSpec) {
+@FlakyTest(bugId = 228012334)
+class SwitchImeWindowsFromGestureNavTest_ShellTransit(testSpec: FlickerTestParameter)
+    : SwitchImeWindowsFromGestureNavTest(testSpec) {
     @Before
     override fun before() {
         Assume.assumeTrue(isShellTransitionsEnabled)
     }
-
-    @FlakyTest(bugId = 227214914)
-    override fun pipLayerRotates_StartingBounds() = super.pipLayerRotates_StartingBounds()
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt
index ee0f3d8..6e33f66 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt
@@ -26,12 +26,9 @@
 import com.android.server.wm.flicker.LAUNCHER_COMPONENT
 import com.android.server.wm.flicker.annotation.Group1
 import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
 import com.android.server.wm.flicker.helpers.reopenAppFromOverview
 import com.android.server.wm.flicker.helpers.setRotation
 import com.android.server.wm.traces.common.WindowManagerConditionsFactory
-import org.junit.Assume.assumeFalse
-import org.junit.Before
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -64,10 +61,6 @@
 @Group1
 open class OpenAppFromOverviewTest(testSpec: FlickerTestParameter)
     : OpenAppFromLauncherTransition(testSpec) {
-    @Before
-    open fun before() {
-        assumeFalse(isShellTransitionsEnabled)
-    }
 
     /**
      * Defines the transition used to run the test
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest_ShellTransit.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest_ShellTransit.kt
deleted file mode 100644
index 55e1e9b..0000000
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest_ShellTransit.kt
+++ /dev/null
@@ -1,80 +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.server.wm.flicker.launch
-
-import androidx.test.filters.FlakyTest
-import android.platform.test.annotations.RequiresDevice
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.annotation.Group1
-import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
-import org.junit.Assume.assumeTrue
-import org.junit.Before
-import org.junit.FixMethodOrder
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-/**
- * Test launching an app from the recents app view (the overview)
- *
- * To run this test: `atest FlickerTests:OpenAppFromOverviewTest`
- *
- * Actions:
- *     Launch [testApp]
- *     Press recents
- *     Relaunch an app [testApp] by selecting it in the overview screen, and wait animation to
- *     complete (only this action is traced)
- *
- * Notes:
- *     1. Some default assertions (e.g., nav bar, status bar and screen covered)
- *        are inherited [OpenAppTransition]
- *     2. Part of the test setup occurs automatically via
- *        [com.android.server.wm.flicker.TransitionRunnerWithRules],
- *        including configuring navigation mode, initial orientation and ensuring no
- *        apps are running before setup
- */
-@RequiresDevice
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@Group1
-class OpenAppFromOverviewTest_ShellTransit(testSpec: FlickerTestParameter)
-    : OpenAppFromOverviewTest(testSpec) {
-    @Before
-    override fun before() {
-        assumeTrue(isShellTransitionsEnabled)
-    }
-
-    /** {@inheritDoc} */
-    @FlakyTest(bugId = 216266712)
-    @Test
-    override fun appWindowBecomesTopWindow() = super.appWindowBecomesTopWindow()
-
-    /** {@inheritDoc} */
-    @FlakyTest(bugId = 216266712)
-    @Test
-    override fun appWindowReplacesLauncherAsTopWindow() =
-            super.appWindowReplacesLauncherAsTopWindow()
-
-    /** {@inheritDoc} */
-    @FlakyTest(bugId = 218470989)
-    @Test
-    override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
-        super.visibleWindowsShownMoreThanOneConsecutiveEntry()
-}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt
index fbd611a..f357177 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt
@@ -27,10 +27,7 @@
 import com.android.server.wm.flicker.annotation.Group1
 import com.android.server.wm.flicker.helpers.NonResizeableAppHelper
 import com.android.server.wm.flicker.helpers.WindowUtils
-import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
 import com.android.server.wm.traces.common.FlickerComponentName
-import org.junit.Assume
-import org.junit.Before
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -64,11 +61,6 @@
     override val testApp = NonResizeableAppHelper(instrumentation)
     private val colorFadComponent = FlickerComponentName("", "ColorFade BLAST#")
 
-    @Before
-    open fun before() {
-        Assume.assumeFalse(isShellTransitionsEnabled)
-    }
-
     /**
      * Checks that the nav bar layer starts invisible, becomes visible during unlocking animation
      * and remains visible at the end
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppTransition.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppTransition.kt
index 4313b8d..02c1a10 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppTransition.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppTransition.kt
@@ -17,7 +17,6 @@
 package com.android.server.wm.flicker.launch
 
 import android.app.Instrumentation
-import androidx.test.filters.FlakyTest
 import android.platform.test.annotations.Presubmit
 import androidx.test.platform.app.InstrumentationRegistry
 import com.android.server.wm.flicker.FlickerBuilderProvider
@@ -216,7 +215,7 @@
      * Checks that [testApp] window is not on top at the start of the transition, and then becomes
      * the top visible window until the end of the transition.
      */
-    @FlakyTest(bugId = 203538234)
+    @Presubmit
     @Test
     open fun appWindowBecomesTopWindow() {
         testSpec.assertWm {
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt
index 1eb3d8d..c89e6a4 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt
@@ -33,12 +33,15 @@
 import com.android.server.wm.flicker.helpers.NonResizeableAppHelper
 import com.android.server.wm.flicker.helpers.SimpleAppHelper
 import com.android.server.wm.flicker.helpers.WindowUtils
+import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
 import com.android.server.wm.flicker.navBarLayerIsVisible
 import com.android.server.wm.flicker.navBarLayerRotatesAndScales
 import com.android.server.wm.flicker.navBarWindowIsVisible
 import com.android.server.wm.flicker.statusBarLayerIsVisible
 import com.android.server.wm.flicker.statusBarWindowIsVisible
 import com.android.server.wm.traces.common.FlickerComponentName
+import org.junit.Assume
+import org.junit.Before
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -70,6 +73,11 @@
 
     private val startDisplayBounds = WindowUtils.getDisplayBounds(testSpec.startRotation)
 
+    @Before
+    open fun before() {
+        Assume.assumeFalse(isShellTransitionsEnabled)
+    }
+
     @FlickerBuilderProvider
     fun buildFlicker(): FlickerBuilder {
         return FlickerBuilder(instrumentation).apply {
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest_ShellTransit.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest_ShellTransit.kt
similarity index 64%
rename from tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest_ShellTransit.kt
rename to tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest_ShellTransit.kt
index 8a08d07..b9fef08 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest_ShellTransit.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest_ShellTransit.kt
@@ -14,10 +14,10 @@
  * limitations under the License.
  */
 
-package com.android.server.wm.flicker.launch
+package com.android.server.wm.flicker.quickswitch
 
-import androidx.test.filters.FlakyTest
 import android.platform.test.annotations.RequiresDevice
+import androidx.test.filters.FlakyTest
 import com.android.server.wm.flicker.FlickerParametersRunnerFactory
 import com.android.server.wm.flicker.FlickerTestParameter
 import com.android.server.wm.flicker.annotation.Group1
@@ -30,30 +30,24 @@
 import org.junit.runners.Parameterized
 
 /**
- * Test launching an app while the device is locked
+ * Test quick switching back to previous app from last opened app
  *
- * To run this test: `atest FlickerTests:OpenAppNonResizeableTest`
+ * To run this test: `atest FlickerTests:QuickSwitchBetweenTwoAppsBackTest`
  *
  * Actions:
- *     Lock the device.
- *     Launch an app on top of the lock screen [testApp] and wait animation to complete
+ *     Launch an app [testApp1]
+ *     Launch another app [testApp2]
+ *     Swipe right from the bottom of the screen to quick switch back to the first app [testApp1]
  *
- * Notes:
- *     1. Some default assertions (e.g., nav bar, status bar and screen covered)
- *        are inherited [OpenAppTransition]
- *     2. Part of the test setup occurs automatically via
- *        [com.android.server.wm.flicker.TransitionRunnerWithRules],
- *        including configuring navigation mode, initial orientation and ensuring no
- *        apps are running before setup
  */
 @RequiresDevice
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
 @Group1
-@FlakyTest(bugId = 219688533)
-class OpenAppNonResizeableTest_ShellTransit(testSpec: FlickerTestParameter)
-    : OpenAppNonResizeableTest(testSpec) {
+@FlakyTest(bugId = 228009808)
+open class QuickSwitchBetweenTwoAppsBackTest_ShellTransit(testSpec: FlickerTestParameter)
+    : QuickSwitchBetweenTwoAppsBackTest(testSpec) {
     @Before
     override fun before() {
         Assume.assumeTrue(isShellTransitionsEnabled)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt
index 5474a42..725d2c3 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt
@@ -32,6 +32,7 @@
 import com.android.server.wm.flicker.dsl.FlickerBuilder
 import com.android.server.wm.flicker.helpers.NonResizeableAppHelper
 import com.android.server.wm.flicker.helpers.SimpleAppHelper
+import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
 import com.android.server.wm.flicker.navBarLayerIsVisible
 import com.android.server.wm.flicker.navBarLayerRotatesAndScales
 import com.android.server.wm.flicker.navBarWindowIsVisible
@@ -39,6 +40,8 @@
 import com.android.server.wm.flicker.statusBarWindowIsVisible
 import com.android.server.wm.traces.common.FlickerComponentName
 import com.android.server.wm.traces.common.Rect
+import org.junit.Assume
+import org.junit.Before
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -68,6 +71,11 @@
     private val testApp1 = SimpleAppHelper(instrumentation)
     private val testApp2 = NonResizeableAppHelper(instrumentation)
 
+    @Before
+    open fun before() {
+        Assume.assumeFalse(isShellTransitionsEnabled)
+    }
+
     @FlickerBuilderProvider
     fun buildFlicker(): FlickerBuilder {
         return FlickerBuilder(instrumentation).apply {
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest_ShellTransit.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest_ShellTransit.kt
similarity index 62%
copy from tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest_ShellTransit.kt
copy to tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest_ShellTransit.kt
index 8a08d07..9c9dedc2 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest_ShellTransit.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest_ShellTransit.kt
@@ -14,9 +14,8 @@
  * limitations under the License.
  */
 
-package com.android.server.wm.flicker.launch
+package com.android.server.wm.flicker.quickswitch
 
-import androidx.test.filters.FlakyTest
 import android.platform.test.annotations.RequiresDevice
 import com.android.server.wm.flicker.FlickerParametersRunnerFactory
 import com.android.server.wm.flicker.FlickerTestParameter
@@ -30,30 +29,23 @@
 import org.junit.runners.Parameterized
 
 /**
- * Test launching an app while the device is locked
+ * Test quick switching back to previous app from last opened app
  *
- * To run this test: `atest FlickerTests:OpenAppNonResizeableTest`
+ * To run this test: `atest FlickerTests:QuickSwitchBetweenTwoAppsForwardTest`
  *
  * Actions:
- *     Lock the device.
- *     Launch an app on top of the lock screen [testApp] and wait animation to complete
- *
- * Notes:
- *     1. Some default assertions (e.g., nav bar, status bar and screen covered)
- *        are inherited [OpenAppTransition]
- *     2. Part of the test setup occurs automatically via
- *        [com.android.server.wm.flicker.TransitionRunnerWithRules],
- *        including configuring navigation mode, initial orientation and ensuring no
- *        apps are running before setup
+ *     Launch an app [testApp1]
+ *     Launch another app [testApp2]
+ *     Swipe right from the bottom of the screen to quick switch back to the first app [testApp1]
+ *     Swipe left from the bottom of the screen to quick switch forward to the second app [testApp2]
  */
 @RequiresDevice
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
 @Group1
-@FlakyTest(bugId = 219688533)
-class OpenAppNonResizeableTest_ShellTransit(testSpec: FlickerTestParameter)
-    : OpenAppNonResizeableTest(testSpec) {
+open class QuickSwitchBetweenTwoAppsForwardTest_ShellTransit(testSpec: FlickerTestParameter)
+    : QuickSwitchBetweenTwoAppsForwardTest(testSpec) {
     @Before
     override fun before() {
         Assume.assumeTrue(isShellTransitionsEnabled)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt
index f83ae87..cc4a4b2 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt
@@ -28,7 +28,7 @@
 import com.android.server.wm.flicker.FlickerTestParameter
 import com.android.server.wm.flicker.FlickerTestParameterFactory
 import com.android.server.wm.flicker.LAUNCHER_COMPONENT
-import com.android.server.wm.flicker.annotation.Group4
+import com.android.server.wm.flicker.annotation.Group1
 import com.android.server.wm.flicker.dsl.FlickerBuilder
 import com.android.server.wm.flicker.entireScreenCovered
 import com.android.server.wm.flicker.helpers.SimpleAppHelper
@@ -60,7 +60,7 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@Group4
+@Group1
 class QuickSwitchFromLauncherTest(private val testSpec: FlickerTestParameter) {
     private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
     private val taplInstrumentation = LauncherInstrumentation()
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt
index 2b944c6..8b851e5 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt
@@ -25,13 +25,11 @@
 import com.android.server.wm.flicker.annotation.Group3
 import com.android.server.wm.flicker.dsl.FlickerBuilder
 import com.android.server.wm.flicker.helpers.SimpleAppHelper
-import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
 import com.android.server.wm.flicker.rules.WMFlickerServiceRuleForTestSpec
 import com.android.server.wm.flicker.statusBarLayerIsVisible
 import com.android.server.wm.flicker.statusBarLayerRotatesScales
 import com.android.server.wm.flicker.statusBarWindowIsVisible
 import com.android.server.wm.traces.common.FlickerComponentName
-import org.junit.Assume
 import org.junit.FixMethodOrder
 import org.junit.Rule
 import org.junit.Test
@@ -100,7 +98,7 @@
      * Windows maybe recreated when rotated. Checks that the focus does not change or if it does,
      * focus returns to [testApp]
      */
-    @FlakyTest(bugId = 190185577)
+    @Presubmit
     @Test
     fun focusChanges() {
         testSpec.assertEventLog {
@@ -130,18 +128,6 @@
     @Presubmit
     @Test
     fun rotationLayerAppearsAndVanishes() {
-        Assume.assumeFalse(isShellTransitionsEnabled)
-        rotationLayerAppearsAndVanishesAssertion()
-    }
-
-    /**
-     * Checks that the [FlickerComponentName.ROTATION] layer appears during the transition,
-     * doesn't flicker, and disappears before the transition is complete
-     */
-    @FlakyTest(bugId = 218484127)
-    @Test
-    fun rotationLayerAppearsAndVanishes_shellTransit() {
-        Assume.assumeTrue(isShellTransitionsEnabled)
         rotationLayerAppearsAndVanishesAssertion()
     }
 
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt
index 15fd5e1..fac5baf 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt
@@ -26,11 +26,8 @@
 import com.android.server.wm.flicker.annotation.Group3
 import com.android.server.wm.flicker.dsl.FlickerBuilder
 import com.android.server.wm.flicker.helpers.SeamlessRotationAppHelper
-import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
 import com.android.server.wm.flicker.testapp.ActivityOptions
 import com.android.server.wm.traces.common.FlickerComponentName
-import org.junit.Assume
-import org.junit.Before
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -84,11 +81,6 @@
 ) : RotationTransition(testSpec) {
     override val testApp = SeamlessRotationAppHelper(instrumentation)
 
-    @Before
-    open fun before() {
-        Assume.assumeFalse(isShellTransitionsEnabled)
-    }
-
     override val transition: FlickerBuilder.() -> Unit
         get() = {
             super.transition(this)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest_ShellTransit.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest_ShellTransit.kt
deleted file mode 100644
index d397d59..0000000
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest_ShellTransit.kt
+++ /dev/null
@@ -1,82 +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.server.wm.flicker.rotation
-
-import androidx.test.filters.FlakyTest
-import android.platform.test.annotations.RequiresDevice
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.annotation.Group3
-import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
-import org.junit.Assume
-import org.junit.Before
-import org.junit.FixMethodOrder
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-/**
- * Test opening an app and cycling through app rotations using seamless rotations
- *
- * Currently runs:
- *      0 -> 90 degrees
- *      0 -> 90 degrees (with starved UI thread)
- *      90 -> 0 degrees
- *      90 -> 0 degrees (with starved UI thread)
- *
- * Actions:
- *     Launch an app in fullscreen and supporting seamless rotation (via intent)
- *     Set initial device orientation
- *     Start tracing
- *     Change device orientation
- *     Stop tracing
- *
- * To run this test: `atest FlickerTests:SeamlessAppRotationTest`
- *
- * To run only the presubmit assertions add: `--
- *      --module-arg FlickerTests:exclude-annotation:androidx.test.filters.FlakyTest
- *      --module-arg FlickerTests:include-annotation:android.platform.test.annotations.Presubmit`
- *
- * To run only the postsubmit assertions add: `--
- *      --module-arg FlickerTests:exclude-annotation:androidx.test.filters.FlakyTest
- *      --module-arg FlickerTests:include-annotation:android.platform.test.annotations.Postsubmit`
- *
- * To run only the flaky assertions add: `--
- *      --module-arg FlickerTests:include-annotation:androidx.test.filters.FlakyTest`
- *
- * Notes:
- *     1. Some default assertions (e.g., nav bar, status bar and screen covered)
- *        are inherited [RotationTransition]
- *     2. Part of the test setup occurs automatically via
- *        [com.android.server.wm.flicker.TransitionRunnerWithRules],
- *        including configuring navigation mode, initial orientation and ensuring no
- *        apps are running before setup
- */
-@RequiresDevice
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@Group3
-@FlakyTest(bugId = 219689723)
-class SeamlessAppRotationTest_ShellTransit(
-    testSpec: FlickerTestParameter
-) : SeamlessAppRotationTest(testSpec) {
-    @Before
-    override fun before() {
-        Assume.assumeTrue(isShellTransitionsEnabled)
-    }
-}
diff --git a/tools/aapt2/Android.mk b/tools/aapt2/Android.mk
index b165c6b..7b94e71 100644
--- a/tools/aapt2/Android.mk
+++ b/tools/aapt2/Android.mk
@@ -15,6 +15,8 @@
 $(aapt2_results): $(HOST_OUT_NATIVE_TESTS)/aapt2_tests/aapt2_tests
 	-$(HOST_OUT_NATIVE_TESTS)/aapt2_tests/aapt2_tests --gtest_output=xml:$@ > /dev/null 2>&1
 
+$(call declare-0p-target,$(aapt2_results))
+
 aapt2_results :=
 
 include $(call all-makefiles-under,$(LOCAL_PATH))