Merge "MediaCodecInfo: consider only critical flags for level support check" into udc-dev
diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/Alarm.java b/apex/jobscheduler/service/java/com/android/server/alarm/Alarm.java
index 430a1e2..4d646de 100644
--- a/apex/jobscheduler/service/java/com/android/server/alarm/Alarm.java
+++ b/apex/jobscheduler/service/java/com/android/server/alarm/Alarm.java
@@ -292,6 +292,10 @@
                 return "permission";
             case EXACT_ALLOW_REASON_POLICY_PERMISSION:
                 return "policy_permission";
+            case EXACT_ALLOW_REASON_LISTENER:
+                return "listener";
+            case EXACT_ALLOW_REASON_PRIORITIZED:
+                return "prioritized";
             case EXACT_ALLOW_REASON_NOT_APPLICABLE:
                 return "N/A";
             default:
diff --git a/core/java/android/app/admin/DevicePolicyCache.java b/core/java/android/app/admin/DevicePolicyCache.java
index b6e83c8..29f657e 100644
--- a/core/java/android/app/admin/DevicePolicyCache.java
+++ b/core/java/android/app/admin/DevicePolicyCache.java
@@ -19,8 +19,8 @@
 
 import com.android.server.LocalServices;
 
-import java.util.ArrayList;
-import java.util.List;
+import java.util.Collections;
+import java.util.Map;
 
 /**
  * Stores a copy of the set of device policies maintained by {@link DevicePolicyManager} that
@@ -64,10 +64,11 @@
     public abstract boolean canAdminGrantSensorsPermissions();
 
     /**
-     * Returns a list of package names for which all launcher shortcuts should be modified to be
-     * launched in the managed profile and badged accordingly.
+     * Returns a map of package names to package names, for which all launcher shortcuts which
+     * match a key package name should be modified to launch the corresponding value package
+     * name in the managed profile. The overridden shortcut should be badged accordingly.
      */
-    public abstract List<String> getLauncherShortcutOverrides();
+    public abstract Map<String, String> getLauncherShortcutOverrides();
 
     /**
      * Empty implementation.
@@ -95,8 +96,8 @@
             return false;
         }
         @Override
-        public List<String> getLauncherShortcutOverrides() {
-            return new ArrayList<>();
+        public Map<String, String>  getLauncherShortcutOverrides() {
+            return Collections.EMPTY_MAP;
         }
     }
 }
diff --git a/core/java/android/view/IRemoteAnimationRunner.aidl b/core/java/android/view/IRemoteAnimationRunner.aidl
index 1981c9d..1f64fb8 100644
--- a/core/java/android/view/IRemoteAnimationRunner.aidl
+++ b/core/java/android/view/IRemoteAnimationRunner.aidl
@@ -46,5 +46,5 @@
      * won't have any effect anymore.
      */
     @UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553)
-    void onAnimationCancelled(boolean isKeyguardOccluded);
+    void onAnimationCancelled();
 }
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index 7931d1a..2dbff58 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -724,7 +724,10 @@
         }
 
         getPositionListener().addSubscriber(mCursorAnchorInfoNotifier, true);
-        makeBlink();
+        // Call resumeBlink here instead of makeBlink to ensure that if mBlink is not null the
+        // Blink object is uncancelled.  This ensures when a view is removed and added back the
+        // cursor will resume blinking.
+        resumeBlink();
     }
 
     void onDetachedFromWindow() {
@@ -1094,8 +1097,10 @@
     private void resumeBlink() {
         if (mBlink != null) {
             mBlink.uncancel();
-            makeBlink();
         }
+        // Moving makeBlink outside of the null check block ensures that mBlink object gets
+        // instantiated when the view is added to the window if mBlink is still null.
+        makeBlink();
     }
 
     void adjustInputType(boolean password, boolean passwordInputType,
@@ -2921,6 +2926,9 @@
         if (shouldBlink()) {
             mShowCursor = SystemClock.uptimeMillis();
             if (mBlink == null) mBlink = new Blink();
+            // Call uncancel as mBlink could have previously been cancelled and cursor will not
+            // resume blinking unless uncancelled.
+            mBlink.uncancel();
             mTextView.removeCallbacks(mBlink);
             mTextView.postDelayed(mBlink, BLINK);
         } else {
diff --git a/core/java/com/android/internal/accessibility/util/AccessibilityUtils.java b/core/java/com/android/internal/accessibility/util/AccessibilityUtils.java
index 3a8f427..4f9fc39 100644
--- a/core/java/com/android/internal/accessibility/util/AccessibilityUtils.java
+++ b/core/java/com/android/internal/accessibility/util/AccessibilityUtils.java
@@ -32,6 +32,9 @@
 import android.os.Build;
 import android.os.UserHandle;
 import android.provider.Settings;
+import android.telecom.TelecomManager;
+import android.telephony.Annotation;
+import android.telephony.TelephonyManager;
 import android.text.ParcelableSpan;
 import android.text.Spanned;
 import android.text.TextUtils;
@@ -204,6 +207,32 @@
     }
 
     /**
+     * Intercepts the {@link AccessibilityService#GLOBAL_ACTION_KEYCODE_HEADSETHOOK} action
+     * by directly interacting with TelecomManager if a call is incoming or in progress.
+     *
+     * <p>
+     * Provided here in shared utils to be used by both the legacy and modern (SysUI)
+     * system action implementations.
+     * </p>
+     *
+     * @return True if the action was propagated to TelecomManager, otherwise false.
+     */
+    public static boolean interceptHeadsetHookForActiveCall(Context context) {
+        final TelecomManager telecomManager = context.getSystemService(TelecomManager.class);
+        @Annotation.CallState final int callState =
+                telecomManager != null ? telecomManager.getCallState()
+                        : TelephonyManager.CALL_STATE_IDLE;
+        if (callState == TelephonyManager.CALL_STATE_RINGING) {
+            telecomManager.acceptRingingCall();
+            return true;
+        } else if (callState == TelephonyManager.CALL_STATE_OFFHOOK) {
+            telecomManager.endCall();
+            return true;
+        }
+        return false;
+    }
+
+    /**
      * Indicates whether the current user has completed setup via the setup wizard.
      * {@link android.provider.Settings.Secure#USER_SETUP_COMPLETE}
      *
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 31220b4..dd535a1 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -8228,6 +8228,8 @@
         <service android:name="com.android.server.companion.datatransfer.contextsync.CallMetadataSyncInCallService"
                  android:permission="android.permission.BIND_INCALL_SERVICE"
                  android:exported="true">
+            <meta-data android:name="android.telecom.INCLUDE_SELF_MANAGED_CALLS"
+                       android:value="true" />
             <intent-filter>
                 <action android:name="android.telecom.InCallService"/>
             </intent-filter>
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java
index b917ac8..d9b73a8 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java
@@ -83,10 +83,7 @@
     }
 
     @Override
-    public void onAnimationCancelled(boolean isKeyguardOccluded) {
-        if (TaskFragmentAnimationController.DEBUG) {
-            Log.v(TAG, "onAnimationCancelled: isKeyguardOccluded=" + isKeyguardOccluded);
-        }
+    public void onAnimationCancelled() {
         mHandler.post(this::cancelAnimation);
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityAnimation.java
index 22c9015..edefe9e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityAnimation.java
@@ -404,7 +404,7 @@
         }
 
         @Override
-        public void onAnimationCancelled(boolean isKeyguardOccluded) {
+        public void onAnimationCancelled() {
             finishAnimation();
         }
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomizeActivityAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomizeActivityAnimation.java
index f0c5d8b2..2d6ec75 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomizeActivityAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomizeActivityAnimation.java
@@ -323,7 +323,7 @@
         }
 
         @Override
-        public void onAnimationCancelled(boolean isKeyguardOccluded) {
+        public void onAnimationCancelled() {
             finishAnimation();
         }
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
index 2afe8ee..b9ff5f0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
@@ -282,6 +282,11 @@
     /** Whether the expanded view has been hidden, because we are dragging out a bubble. */
     private boolean mExpandedViewTemporarilyHidden = false;
 
+    /**
+     * Whether the last bubble is being removed when expanded, which impacts the collapse animation.
+     */
+    private boolean mRemovingLastBubbleWhileExpanded = false;
+
     /** Animator for animating the expanded view's alpha (including the TaskView inside it). */
     private final ValueAnimator mExpandedViewAlphaAnimator = ValueAnimator.ofFloat(0f, 1f);
 
@@ -765,7 +770,7 @@
 
                 // Update scrim
                 if (!mScrimAnimating) {
-                    showScrim(true);
+                    showScrim(true, null /* runnable */);
                 }
             }
         }
@@ -880,6 +885,7 @@
 
         final Runnable onBubbleAnimatedOut = () -> {
             if (getBubbleCount() == 0) {
+                mExpandedViewTemporarilyHidden = false;
                 mBubbleController.onAllBubblesAnimatedOut();
             }
         };
@@ -1773,6 +1779,20 @@
         if (DEBUG_BUBBLE_STACK_VIEW) {
             Log.d(TAG, "removeBubble: " + bubble);
         }
+        if (isExpanded() && getBubbleCount() == 1) {
+            mRemovingLastBubbleWhileExpanded = true;
+            // We're expanded while the last bubble is being removed. Let the scrim animate away
+            // and then remove our views (removing the icon view triggers the removal of the
+            // bubble window so do that at the end of the animation so we see the scrim animate).
+            showScrim(false, () -> {
+                mRemovingLastBubbleWhileExpanded = false;
+                bubble.cleanupExpandedView();
+                mBubbleContainer.removeView(bubble.getIconView());
+                bubble.cleanupViews(); // cleans up the icon view
+                updateExpandedView(); // resets state for no expanded bubble
+            });
+            return;
+        }
         // Remove it from the views
         for (int i = 0; i < getBubbleCount(); i++) {
             View v = mBubbleContainer.getChildAt(i);
@@ -2143,7 +2163,7 @@
         mExpandedViewAlphaAnimator.start();
     }
 
-    private void showScrim(boolean show) {
+    private void showScrim(boolean show, Runnable after) {
         AnimatorListenerAdapter listener = new AnimatorListenerAdapter() {
             @Override
             public void onAnimationStart(Animator animation) {
@@ -2153,6 +2173,9 @@
             @Override
             public void onAnimationEnd(Animator animation) {
                 mScrimAnimating = false;
+                if (after != null) {
+                    after.run();
+                }
             }
         };
         if (show) {
@@ -2179,7 +2202,7 @@
         }
         beforeExpandedViewAnimation();
 
-        showScrim(true);
+        showScrim(true, null /* runnable */);
         updateZOrder();
         updateBadges(false /* setBadgeForCollapsedStack */);
         mBubbleContainer.setActiveController(mExpandedAnimationController);
@@ -2303,7 +2326,10 @@
         mIsExpanded = false;
         mIsExpansionAnimating = true;
 
-        showScrim(false);
+        if (!mRemovingLastBubbleWhileExpanded) {
+            // When we remove the last bubble it animates the scrim.
+            showScrim(false, null /* runnable */);
+        }
 
         mBubbleContainer.cancelAllAnimations();
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
index 2f25511..6432459 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
@@ -460,7 +460,7 @@
                 }
             }
             @Override
-            public void onAnimationCancelled(boolean isKeyguardOccluded) {
+            public void onAnimationCancelled() {
                 final WindowContainerTransaction evictWct = new WindowContainerTransaction();
                 mStageCoordinator.prepareEvictInvisibleChildTasks(evictWct);
                 mSyncQueue.queue(evictWct);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index 49e8227..4c903f5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -483,7 +483,7 @@
                 }
             }
             @Override
-            public void onAnimationCancelled(boolean isKeyguardOccluded) {
+            public void onAnimationCancelled() {
                 if (isEnteringSplit) {
                     mMainExecutor.execute(() -> exitSplitScreen(
                             mSideStage.getChildCount() == 0 ? mMainStage : mSideStage,
@@ -869,7 +869,7 @@
                         onRemoteAnimationFinished(apps);
                         t.apply();
                         try {
-                            adapter.getRunner().onAnimationCancelled(mKeyguardShowing);
+                            adapter.getRunner().onAnimationCancelled();
                         } catch (RemoteException e) {
                             Slog.e(TAG, "Error starting remote animation", e);
                         }
@@ -1013,11 +1013,11 @@
             }
 
             @Override
-            public void onAnimationCancelled(boolean isKeyguardOccluded) {
+            public void onAnimationCancelled() {
                 onRemoteAnimationFinishedOrCancelled(evictWct);
                 setDividerVisibility(true, null);
                 try {
-                    adapter.getRunner().onAnimationCancelled(isKeyguardOccluded);
+                    adapter.getRunner().onAnimationCancelled();
                 } catch (RemoteException e) {
                     Slog.e(TAG, "Error starting remote animation", e);
                 }
@@ -1038,7 +1038,7 @@
                         onRemoteAnimationFinished(apps);
                         t.apply();
                         try {
-                            adapter.getRunner().onAnimationCancelled(mKeyguardShowing);
+                            adapter.getRunner().onAnimationCancelled();
                         } catch (RemoteException e) {
                             Slog.e(TAG, "Error starting remote animation", e);
                         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/LegacyTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/LegacyTransitions.java
index 61e92f3..61e11e8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/LegacyTransitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/LegacyTransitions.java
@@ -107,7 +107,7 @@
             }
 
             @Override
-            public void onAnimationCancelled(boolean isKeyguardOccluded) throws RemoteException {
+            public void onAnimationCancelled() throws RemoteException {
                 mCancelled = true;
                 mApps = mWallpapers = mNonApps = null;
                 checkApply();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
index f9fdd83..836efe0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
@@ -23,6 +23,7 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.content.res.Configuration;
+import android.content.res.TypedArray;
 import android.graphics.Point;
 import android.graphics.PointF;
 import android.graphics.Region;
@@ -95,7 +96,7 @@
     private int mWindowingPillHeight;
     private int mMoreActionsPillHeight;
     private int mShadowRadius;
-    private int mCornerRadius;
+    private int mMenuCornerRadius;
 
     DesktopModeWindowDecoration(
             Context context,
@@ -182,6 +183,11 @@
         mRelayoutParams.mShadowRadiusId = shadowRadiusID;
         mRelayoutParams.mApplyStartTransactionOnDraw = applyStartTransactionOnDraw;
 
+        final TypedArray ta = mContext.obtainStyledAttributes(
+                new int[]{android.R.attr.dialogCornerRadius});
+        mRelayoutParams.mCornerRadius = ta.getDimensionPixelSize(0, 0);
+        ta.recycle();
+
         relayout(mRelayoutParams, startT, finishT, wct, oldRootView, mResult);
         // After this line, mTaskInfo is up-to-date and should be used instead of taskInfo
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
index e772fc2..ac5ff20 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
@@ -16,6 +16,8 @@
 
 package com.android.wm.shell.windowdecor;
 
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+
 import android.app.ActivityManager.RunningTaskInfo;
 import android.content.Context;
 import android.content.res.Configuration;
@@ -256,13 +258,17 @@
         mTmpColor[0] = (float) Color.red(backgroundColorInt) / 255.f;
         mTmpColor[1] = (float) Color.green(backgroundColorInt) / 255.f;
         mTmpColor[2] = (float) Color.blue(backgroundColorInt) / 255.f;
-        Point taskPosition = mTaskInfo.positionInParent;
+        final Point taskPosition = mTaskInfo.positionInParent;
         startT.setWindowCrop(mTaskSurface, outResult.mWidth, outResult.mHeight)
                 .setShadowRadius(mTaskSurface, shadowRadius)
                 .setColor(mTaskSurface, mTmpColor)
                 .show(mTaskSurface);
         finishT.setPosition(mTaskSurface, taskPosition.x, taskPosition.y)
                 .setWindowCrop(mTaskSurface, outResult.mWidth, outResult.mHeight);
+        if (mTaskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) {
+            startT.setCornerRadius(mTaskSurface, params.mCornerRadius);
+            finishT.setCornerRadius(mTaskSurface, params.mCornerRadius);
+        }
 
         if (mCaptionWindowManager == null) {
             // Put caption under a container surface because ViewRootImpl sets the destination frame
@@ -414,6 +420,8 @@
         int mCaptionWidthId;
         int mShadowRadiusId;
 
+        int mCornerRadius;
+
         int mCaptionX;
         int mCaptionY;
 
@@ -425,6 +433,8 @@
             mCaptionWidthId = Resources.ID_NULL;
             mShadowRadiusId = Resources.ID_NULL;
 
+            mCornerRadius = 0;
+
             mCaptionX = 0;
             mCaptionY = 0;
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
index fc4bfd97..5a2326b 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
@@ -16,6 +16,8 @@
 
 package com.android.wm.shell.windowdecor;
 
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+
 import static com.android.wm.shell.MockSurfaceControlHelper.createMockSurfaceControlBuilder;
 import static com.android.wm.shell.MockSurfaceControlHelper.createMockSurfaceControlTransaction;
 
@@ -86,6 +88,7 @@
 public class WindowDecorationTests extends ShellTestCase {
     private static final Rect TASK_BOUNDS = new Rect(100, 300, 400, 400);
     private static final Point TASK_POSITION_IN_PARENT = new Point(40, 60);
+    private static final int CORNER_RADIUS = 20;
 
     private final WindowDecoration.RelayoutResult<TestView> mRelayoutResult =
             new WindowDecoration.RelayoutResult<>();
@@ -130,6 +133,7 @@
         mCaptionMenuShadowRadiusId = R.dimen.test_caption_menu_shadow_radius;
         mCaptionMenuCornerRadiusId = R.dimen.test_caption_menu_corner_radius;
         mRelayoutParams.mShadowRadiusId = R.dimen.test_window_decor_shadow_radius;
+        mRelayoutParams.mCornerRadius = CORNER_RADIUS;
 
         doReturn(mMockSurfaceControlViewHost).when(mMockSurfaceControlViewHostFactory)
                 .create(any(), any(), any());
@@ -209,6 +213,7 @@
                 .setBounds(TASK_BOUNDS)
                 .setPositionInParent(TASK_POSITION_IN_PARENT.x, TASK_POSITION_IN_PARENT.y)
                 .setVisible(true)
+                .setWindowingMode(WINDOWING_MODE_FREEFORM)
                 .build();
         taskInfo.isFocused = true;
         // Density is 2. Shadow radius is 10px. Caption height is 64px.
@@ -249,6 +254,8 @@
                 .setPosition(taskSurface, TASK_POSITION_IN_PARENT.x, TASK_POSITION_IN_PARENT.y);
         verify(mMockSurfaceControlFinishT)
                 .setWindowCrop(taskSurface, 300, 100);
+        verify(mMockSurfaceControlStartT).setCornerRadius(taskSurface, CORNER_RADIUS);
+        verify(mMockSurfaceControlFinishT).setCornerRadius(taskSurface, CORNER_RADIUS);
         verify(mMockSurfaceControlStartT)
                 .show(taskSurface);
         verify(mMockSurfaceControlStartT)
diff --git a/packages/CredentialManager/res/values/strings.xml b/packages/CredentialManager/res/values/strings.xml
index 905e0ca..a3b2752 100644
--- a/packages/CredentialManager/res/values/strings.xml
+++ b/packages/CredentialManager/res/values/strings.xml
@@ -114,13 +114,13 @@
   <!-- Strings for the get flow. -->
   <!-- This appears as the title of the modal bottom sheet asking for user confirmation to use the single previously saved passkey to sign in to the app. [CHAR LIMIT=200] -->
   <string name="get_dialog_title_use_passkey_for">Use your saved passkey for <xliff:g id="app_name" example="YouTube">%1$s</xliff:g>?</string>
-  <!-- This appears as the title of the dialog asking for user confirmation to use the single previously saved credential to sign in to the app. [CHAR LIMIT=200] -->
-  <string name="get_dialog_title_use_sign_in_for">Use your saved sign-in for <xliff:g id="app_name" example="YouTube">%1$s</xliff:g>?</string>
-  <!-- This appears as the title of the dialog asking for user to make a choice from various previously saved credentials to sign in to the app. [CHAR LIMIT=200] -->
-  <string name="get_dialog_title_choose_sign_in_for">Choose a saved sign-in for <xliff:g id="app_name" example="YouTube">%1$s</xliff:g></string>
-  <!-- This appears as the title of the dialog asking for user to make a choice from various previously saved credentials to sign in to the app. [CHAR LIMIT=200] -->
+  <!-- This appears as the title of the dialog asking for user confirmation to use the single user credential (previously saved or to be created) to sign in to the app. [CHAR LIMIT=200] -->
+  <string name="get_dialog_title_use_sign_in_for">Use your sign-in for <xliff:g id="app_name" example="YouTube">%1$s</xliff:g>?</string>
+  <!-- This appears as the title of the dialog asking for user to make a choice from various available user credentials (previously saved or to be created) to sign in to the app. [CHAR LIMIT=200] -->
+  <string name="get_dialog_title_choose_sign_in_for">Choose a sign-in for <xliff:g id="app_name" example="YouTube">%1$s</xliff:g></string>
+  <!-- This appears as the title of the dialog asking for user to make a choice from options of available user information (e.g. driver's license, vaccination status) to pass to the app. [CHAR LIMIT=200] -->
   <string name="get_dialog_title_choose_option_for">Choose an option for <xliff:g id="app_name" example="YouTube">%1$s</xliff:g>?</string>
-  <!-- This appears as the title of the dialog asking user to use a previously saved credentials to sign in to the app. [CHAR LIMIT=200] -->
+  <!-- This appears as the title of the dialog asking user to send a piece of user information (e.g. driver's license, vaccination status) to the app. [CHAR LIMIT=200] -->
   <string name="get_dialog_title_use_info_on">Use this info on <xliff:g id="app_name" example="YouTube">%1$s</xliff:g>?</string>
   <!-- This is a label for a button that links the user to different sign-in methods . [CHAR LIMIT=80] -->
   <string name="get_dialog_use_saved_passkey_for">Sign in another way</string>
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java
index 45c0d78..adaf4a1 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java
@@ -14,7 +14,9 @@
 import static android.net.NetworkCapabilities.NET_CAPABILITY_PARTIAL_CONNECTIVITY;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
 
+import android.annotation.SuppressLint;
 import android.content.Context;
 import android.content.Intent;
 import android.net.ConnectivityManager;
@@ -26,6 +28,8 @@
 import android.net.NetworkRequest;
 import android.net.NetworkScoreManager;
 import android.net.ScoredNetwork;
+import android.net.TransportInfo;
+import android.net.vcn.VcnTransportInfo;
 import android.net.wifi.WifiInfo;
 import android.net.wifi.WifiManager;
 import android.net.wifi.WifiNetworkScoreCache;
@@ -34,8 +38,9 @@
 import android.os.Looper;
 import android.provider.Settings;
 
+import androidx.annotation.Nullable;
+
 import com.android.settingslib.R;
-import com.android.settingslib.Utils;
 
 import java.io.PrintWriter;
 import java.text.SimpleDateFormat;
@@ -46,6 +51,7 @@
 /**
  * Track status of Wi-Fi for the Sys UI.
  */
+@SuppressLint("MissingPermission")
 public class WifiStatusTracker {
     private static final int HISTORY_SIZE = 32;
     private static final SimpleDateFormat SSDF = new SimpleDateFormat("MM-dd HH:mm:ss.SSS");
@@ -66,8 +72,9 @@
     private final NetworkRequest mNetworkRequest = new NetworkRequest.Builder()
             .clearCapabilities()
             .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
-            .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
-            .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR).build();
+            .addTransportType(TRANSPORT_WIFI)
+            .addTransportType(TRANSPORT_CELLULAR)
+            .build();
     private final NetworkCallback mNetworkCallback =
             new NetworkCallback(NetworkCallback.FLAG_INCLUDE_LOCATION_INFO) {
         // Note: onCapabilitiesChanged is guaranteed to be called "immediately" after onAvailable
@@ -75,18 +82,10 @@
         @Override
         public void onCapabilitiesChanged(
                 Network network, NetworkCapabilities networkCapabilities) {
-            boolean isVcnOverWifi = false;
-            boolean isWifi = false;
-            WifiInfo wifiInfo = null;
-            if (networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) {
-                wifiInfo = Utils.tryGetWifiInfoForVcn(networkCapabilities);
-                isVcnOverWifi = (wifiInfo != null);
-            } else if (networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) {
-                wifiInfo = (WifiInfo) networkCapabilities.getTransportInfo();
-                isWifi = true;
-            }
+            WifiInfo wifiInfo = getMainOrUnderlyingWifiInfo(networkCapabilities);
+            boolean isWifi = connectionIsWifi(networkCapabilities, wifiInfo);
             // As long as it is a WiFi network, we will log it in the dumpsys for debugging.
-            if (isVcnOverWifi || isWifi) {
+            if (isWifi) {
                 String log = new StringBuilder()
                         .append(SSDF.format(System.currentTimeMillis())).append(",")
                         .append("onCapabilitiesChanged: ")
@@ -303,17 +302,8 @@
             return;
         }
         NetworkCapabilities networkCapabilities;
-        isDefaultNetwork = false;
-        if (mDefaultNetworkCapabilities != null) {
-            boolean isWifi = mDefaultNetworkCapabilities.hasTransport(
-                    NetworkCapabilities.TRANSPORT_WIFI);
-            boolean isVcnOverWifi = mDefaultNetworkCapabilities.hasTransport(
-                    NetworkCapabilities.TRANSPORT_CELLULAR)
-                            && (Utils.tryGetWifiInfoForVcn(mDefaultNetworkCapabilities) != null);
-            if (isWifi || isVcnOverWifi) {
-                isDefaultNetwork = true;
-            }
-        }
+        isDefaultNetwork = mDefaultNetworkCapabilities != null
+                && connectionIsWifi(mDefaultNetworkCapabilities);
         if (isDefaultNetwork) {
             // Wifi is connected and the default network.
             networkCapabilities = mDefaultNetworkCapabilities;
@@ -352,6 +342,70 @@
                 ? null : AccessPoint.getSpeedLabel(mContext, scoredNetwork, rssi);
     }
 
+    @Nullable
+    private WifiInfo getMainOrUnderlyingWifiInfo(NetworkCapabilities networkCapabilities) {
+        WifiInfo mainWifiInfo = getMainWifiInfo(networkCapabilities);
+        if (mainWifiInfo != null) {
+            return mainWifiInfo;
+        }
+
+        // Only CELLULAR networks may have underlying wifi information that's relevant to SysUI,
+        // so skip the underlying network check if it's not CELLULAR.
+        if (!networkCapabilities.hasTransport(TRANSPORT_CELLULAR)) {
+            return mainWifiInfo;
+        }
+
+        List<Network> underlyingNetworks = networkCapabilities.getUnderlyingNetworks();
+        if (underlyingNetworks == null) {
+            return null;
+        }
+
+        // Some connections, like VPN connections, may have underlying networks that are
+        // eventually traced to a wifi or carrier merged connection. So, check those underlying
+        // networks for possible wifi information as well. See b/225902574.
+        for (Network underlyingNetwork : underlyingNetworks) {
+            NetworkCapabilities underlyingNetworkCapabilities =
+                    mConnectivityManager.getNetworkCapabilities(underlyingNetwork);
+            WifiInfo underlyingWifiInfo = getMainWifiInfo(underlyingNetworkCapabilities);
+            if (underlyingWifiInfo != null) {
+                return underlyingWifiInfo;
+            }
+        }
+
+        return null;
+    }
+
+    @Nullable
+    private WifiInfo getMainWifiInfo(NetworkCapabilities networkCapabilities) {
+        boolean canHaveWifiInfo = networkCapabilities.hasTransport(TRANSPORT_WIFI)
+                || networkCapabilities.hasTransport(TRANSPORT_CELLULAR);
+        if (!canHaveWifiInfo) {
+            return null;
+        }
+
+        TransportInfo transportInfo = networkCapabilities.getTransportInfo();
+        if (transportInfo instanceof VcnTransportInfo) {
+            // This VcnTransportInfo logic is copied from
+            // [com.android.settingslib.Utils.tryGetWifiInfoForVcn]. It's copied instead of
+            // re-used because it makes the logic here clearer.
+            return ((VcnTransportInfo) transportInfo).getWifiInfo();
+        } else if (transportInfo instanceof WifiInfo) {
+            return (WifiInfo) transportInfo;
+        } else {
+            return null;
+        }
+    }
+
+    private boolean connectionIsWifi(NetworkCapabilities networkCapabilities) {
+        return connectionIsWifi(
+                networkCapabilities,
+                getMainOrUnderlyingWifiInfo(networkCapabilities));
+    }
+
+    private boolean connectionIsWifi(NetworkCapabilities networkCapabilities, WifiInfo wifiInfo) {
+        return wifiInfo != null || networkCapabilities.hasTransport(TRANSPORT_WIFI);
+    }
+
     /** Refresh the status label on Locale changed. */
     public void refreshLocale() {
         updateStatusLabel();
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiStatusTrackerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiStatusTrackerTest.java
index dc7e313d..6e975cf 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiStatusTrackerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiStatusTrackerTest.java
@@ -40,6 +40,8 @@
 import org.mockito.MockitoAnnotations;
 import org.robolectric.RobolectricTestRunner;
 
+import java.util.Arrays;
+
 @RunWith(RobolectricTestRunner.class)
 public class WifiStatusTrackerTest {
     @Mock Context mContext;
@@ -48,13 +50,32 @@
     @Mock ConnectivityManager mConnectivityManager;
     @Mock Runnable mCallback;
 
+    private WifiStatusTracker mWifiStatusTracker;
+
     private final ArgumentCaptor<ConnectivityManager.NetworkCallback>
             mNetworkCallbackCaptor =
             ArgumentCaptor.forClass(ConnectivityManager.NetworkCallback.class);
 
+    private final ArgumentCaptor<ConnectivityManager.NetworkCallback>
+            mDefaultNetworkCallbackCaptor =
+            ArgumentCaptor.forClass(ConnectivityManager.NetworkCallback.class);
+
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
+
+        mWifiStatusTracker = new WifiStatusTracker(
+                mContext,
+                mWifiManager,
+                mNetworkScoreManager,
+                mConnectivityManager,
+                mCallback);
+        mWifiStatusTracker.setListening(true);
+
+        verify(mConnectivityManager)
+                .registerNetworkCallback(any(), mNetworkCallbackCaptor.capture(), any());
+        verify(mConnectivityManager)
+                .registerDefaultNetworkCallback(mDefaultNetworkCallbackCaptor.capture(), any());
     }
 
     /**
@@ -62,13 +83,6 @@
      */
     @Test
     public void testWifiInfoClearedOnPrimaryNetworkLost() {
-        WifiStatusTracker wifiStatusTracker = new WifiStatusTracker(mContext, mWifiManager,
-                mNetworkScoreManager, mConnectivityManager, mCallback);
-        wifiStatusTracker.setListening(true);
-
-        verify(mConnectivityManager)
-                .registerNetworkCallback(any(), mNetworkCallbackCaptor.capture(), any());
-
         // Trigger a validation callback for the primary Wifi network.
         WifiInfo primaryWifiInfo = Mockito.mock(WifiInfo.class);
         when(primaryWifiInfo.makeCopy(anyLong())).thenReturn(primaryWifiInfo);
@@ -86,8 +100,8 @@
         mNetworkCallbackCaptor.getValue().onCapabilitiesChanged(primaryNetwork, primaryCap);
 
         // Verify primary wifi info is the one being used.
-        assertThat(wifiStatusTracker.connected).isTrue();
-        assertThat(wifiStatusTracker.rssi).isEqualTo(primaryRssi);
+        assertThat(mWifiStatusTracker.connected).isTrue();
+        assertThat(mWifiStatusTracker.rssi).isEqualTo(primaryRssi);
 
         // Trigger a validation callback for the non-primary Wifi network.
         WifiInfo nonPrimaryWifiInfo = Mockito.mock(WifiInfo.class);
@@ -106,20 +120,189 @@
         mNetworkCallbackCaptor.getValue().onCapabilitiesChanged(nonPrimaryNetwork, nonPrimaryCap);
 
         // Verify primary wifi info is still the one being used.
-        assertThat(wifiStatusTracker.connected).isTrue();
-        assertThat(wifiStatusTracker.rssi).isEqualTo(primaryRssi);
+        assertThat(mWifiStatusTracker.connected).isTrue();
+        assertThat(mWifiStatusTracker.rssi).isEqualTo(primaryRssi);
 
         // Lose the non-primary network.
         mNetworkCallbackCaptor.getValue().onLost(nonPrimaryNetwork);
 
         // Verify primary wifi info is still the one being used.
-        assertThat(wifiStatusTracker.connected).isTrue();
-        assertThat(wifiStatusTracker.rssi).isEqualTo(primaryRssi);
+        assertThat(mWifiStatusTracker.connected).isTrue();
+        assertThat(mWifiStatusTracker.rssi).isEqualTo(primaryRssi);
 
         // Lose the primary network.
         mNetworkCallbackCaptor.getValue().onLost(primaryNetwork);
 
         // Verify we aren't connected anymore.
-        assertThat(wifiStatusTracker.connected).isFalse();
+        assertThat(mWifiStatusTracker.connected).isFalse();
+    }
+
+    @Test
+    public void isCarrierMerged_typicalWifi_false() {
+        WifiInfo primaryWifiInfo = Mockito.mock(WifiInfo.class);
+        when(primaryWifiInfo.isPrimary()).thenReturn(true);
+
+        NetworkCapabilities primaryCap = Mockito.mock(NetworkCapabilities.class);
+        when(primaryCap.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)).thenReturn(true);
+        when(primaryCap.getTransportInfo()).thenReturn(primaryWifiInfo);
+
+        Network primaryNetwork = Mockito.mock(Network.class);
+        int primaryNetworkId = 1;
+        when(primaryNetwork.getNetId()).thenReturn(primaryNetworkId);
+
+        mNetworkCallbackCaptor.getValue().onCapabilitiesChanged(primaryNetwork, primaryCap);
+
+        assertThat(mWifiStatusTracker.isCarrierMerged).isFalse();
+    }
+
+    @Test
+    public void isCarrierMerged_typicalCellular_false() {
+        NetworkCapabilities primaryCap = Mockito.mock(NetworkCapabilities.class);
+        when(primaryCap.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)).thenReturn(true);
+
+        Network primaryNetwork = Mockito.mock(Network.class);
+        int primaryNetworkId = 1;
+        when(primaryNetwork.getNetId()).thenReturn(primaryNetworkId);
+
+        mNetworkCallbackCaptor.getValue().onCapabilitiesChanged(primaryNetwork, primaryCap);
+
+        assertThat(mWifiStatusTracker.isCarrierMerged).isFalse();
+    }
+
+    @Test
+    public void isCarrierMerged_cellularCarrierMergedWifi_true() {
+        WifiInfo primaryWifiInfo = Mockito.mock(WifiInfo.class);
+        when(primaryWifiInfo.isPrimary()).thenReturn(true);
+        when(primaryWifiInfo.isCarrierMerged()).thenReturn(true);
+
+        NetworkCapabilities primaryCap = Mockito.mock(NetworkCapabilities.class);
+        when(primaryCap.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)).thenReturn(true);
+        when(primaryCap.getTransportInfo()).thenReturn(primaryWifiInfo);
+
+        Network primaryNetwork = Mockito.mock(Network.class);
+        int primaryNetworkId = 1;
+        when(primaryNetwork.getNetId()).thenReturn(primaryNetworkId);
+
+        mNetworkCallbackCaptor.getValue().onCapabilitiesChanged(primaryNetwork, primaryCap);
+
+        assertThat(mWifiStatusTracker.isCarrierMerged).isTrue();
+    }
+
+    /** Test for b/225902574. */
+    @Test
+    public void isCarrierMerged_cellularWithUnderlyingCarrierMergedWifi_true() {
+        WifiInfo underlyingCarrierMergedInfo = Mockito.mock(WifiInfo.class);
+        when(underlyingCarrierMergedInfo.isPrimary()).thenReturn(true);
+        when(underlyingCarrierMergedInfo.isCarrierMerged()).thenReturn(true);
+
+        NetworkCapabilities underlyingNetworkCapabilities = Mockito.mock(NetworkCapabilities.class);
+        when(underlyingNetworkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR))
+                .thenReturn(true);
+        when(underlyingNetworkCapabilities.getTransportInfo())
+                .thenReturn(underlyingCarrierMergedInfo);
+
+        Network underlyingNetwork = Mockito.mock(Network.class);
+        when(mConnectivityManager.getNetworkCapabilities(underlyingNetwork))
+                .thenReturn(underlyingNetworkCapabilities);
+
+        NetworkCapabilities mainCapabilities = Mockito.mock(NetworkCapabilities.class);
+        when(mainCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR))
+                .thenReturn(true);
+        when(mainCapabilities.getTransportInfo()).thenReturn(null);
+        when(mainCapabilities.getUnderlyingNetworks())
+                .thenReturn(Arrays.asList(underlyingNetwork));
+
+        Network primaryNetwork = Mockito.mock(Network.class);
+        int primaryNetworkId = 1;
+        when(primaryNetwork.getNetId()).thenReturn(primaryNetworkId);
+
+        mNetworkCallbackCaptor.getValue().onCapabilitiesChanged(primaryNetwork, mainCapabilities);
+
+        assertThat(mWifiStatusTracker.isCarrierMerged).isTrue();
+    }
+
+    @Test
+    public void isDefaultNetwork_typicalWifi_true() {
+        WifiInfo primaryWifiInfo = Mockito.mock(WifiInfo.class);
+        when(primaryWifiInfo.isPrimary()).thenReturn(true);
+
+        NetworkCapabilities primaryCap = Mockito.mock(NetworkCapabilities.class);
+        when(primaryCap.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)).thenReturn(true);
+        when(primaryCap.getTransportInfo()).thenReturn(primaryWifiInfo);
+
+        Network primaryNetwork = Mockito.mock(Network.class);
+        int primaryNetworkId = 1;
+        when(primaryNetwork.getNetId()).thenReturn(primaryNetworkId);
+
+        mDefaultNetworkCallbackCaptor.getValue().onCapabilitiesChanged(primaryNetwork, primaryCap);
+
+        assertThat(mWifiStatusTracker.isDefaultNetwork).isTrue();
+    }
+
+    @Test
+    public void isDefaultNetwork_typicalCellular_false() {
+        NetworkCapabilities primaryCap = Mockito.mock(NetworkCapabilities.class);
+        when(primaryCap.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)).thenReturn(true);
+
+        Network primaryNetwork = Mockito.mock(Network.class);
+        int primaryNetworkId = 1;
+        when(primaryNetwork.getNetId()).thenReturn(primaryNetworkId);
+
+        mDefaultNetworkCallbackCaptor.getValue().onCapabilitiesChanged(primaryNetwork, primaryCap);
+
+        assertThat(mWifiStatusTracker.isDefaultNetwork).isFalse();
+    }
+
+    @Test
+    public void isDefaultNetwork_cellularCarrierMergedWifi_true() {
+        WifiInfo primaryWifiInfo = Mockito.mock(WifiInfo.class);
+        when(primaryWifiInfo.isPrimary()).thenReturn(true);
+        when(primaryWifiInfo.isCarrierMerged()).thenReturn(true);
+
+        NetworkCapabilities primaryCap = Mockito.mock(NetworkCapabilities.class);
+        when(primaryCap.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)).thenReturn(true);
+        when(primaryCap.getTransportInfo()).thenReturn(primaryWifiInfo);
+
+        Network primaryNetwork = Mockito.mock(Network.class);
+        int primaryNetworkId = 1;
+        when(primaryNetwork.getNetId()).thenReturn(primaryNetworkId);
+
+        mDefaultNetworkCallbackCaptor.getValue().onCapabilitiesChanged(primaryNetwork, primaryCap);
+
+        assertThat(mWifiStatusTracker.isDefaultNetwork).isTrue();
+    }
+
+    /** Test for b/225902574. */
+    @Test
+    public void isDefaultNetwork_cellularWithUnderlyingCarrierMergedWifi_true() {
+        WifiInfo underlyingCarrierMergedInfo = Mockito.mock(WifiInfo.class);
+        when(underlyingCarrierMergedInfo.isPrimary()).thenReturn(true);
+        when(underlyingCarrierMergedInfo.isCarrierMerged()).thenReturn(true);
+
+        NetworkCapabilities underlyingNetworkCapabilities = Mockito.mock(NetworkCapabilities.class);
+        when(underlyingNetworkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR))
+                .thenReturn(true);
+        when(underlyingNetworkCapabilities.getTransportInfo())
+                .thenReturn(underlyingCarrierMergedInfo);
+
+        Network underlyingNetwork = Mockito.mock(Network.class);
+        when(mConnectivityManager.getNetworkCapabilities(underlyingNetwork))
+                .thenReturn(underlyingNetworkCapabilities);
+
+        NetworkCapabilities mainCapabilities = Mockito.mock(NetworkCapabilities.class);
+        when(mainCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR))
+                .thenReturn(true);
+        when(mainCapabilities.getTransportInfo()).thenReturn(null);
+        when(mainCapabilities.getUnderlyingNetworks())
+                .thenReturn(Arrays.asList(underlyingNetwork));
+
+        Network primaryNetwork = Mockito.mock(Network.class);
+        int primaryNetworkId = 1;
+        when(primaryNetwork.getNetId()).thenReturn(primaryNetworkId);
+
+        mDefaultNetworkCallbackCaptor.getValue()
+                .onCapabilitiesChanged(primaryNetwork, mainCapabilities);
+
+        assertThat(mWifiStatusTracker.isDefaultNetwork).isTrue();
     }
 }
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
index 296c2ae..94b3740 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
@@ -436,8 +436,8 @@
         }
 
         @BinderThread
-        override fun onAnimationCancelled(isKeyguardOccluded: Boolean) {
-            context.mainExecutor.execute { delegate.onAnimationCancelled(isKeyguardOccluded) }
+        override fun onAnimationCancelled() {
+            context.mainExecutor.execute { delegate.onAnimationCancelled() }
         }
     }
 
@@ -744,7 +744,7 @@
         }
 
         @UiThread
-        override fun onAnimationCancelled(isKeyguardOccluded: Boolean) {
+        override fun onAnimationCancelled() {
             if (timedOut) {
                 return
             }
@@ -754,7 +754,7 @@
             removeTimeout()
 
             animation?.cancel()
-            controller.onLaunchAnimationCancelled(newKeyguardOccludedState = isKeyguardOccluded)
+            controller.onLaunchAnimationCancelled()
         }
 
         private fun IRemoteAnimationFinishedCallback.invoke() {
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteAnimationDelegate.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteAnimationDelegate.kt
index 337408b..d465962 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteAnimationDelegate.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteAnimationDelegate.kt
@@ -26,5 +26,5 @@
     )
 
     /** Called on the UI thread when a signal is received to cancel the animation. */
-    @UiThread fun onAnimationCancelled(isKeyguardOccluded: Boolean)
+    @UiThread fun onAnimationCancelled()
 }
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationRunnerCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationRunnerCompat.java
index 1b0dacc..8dcd2aa 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationRunnerCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationRunnerCompat.java
@@ -206,7 +206,7 @@
                 t.close();
                 info.releaseAllSurfaces();
                 if (finishRunnable == null) return;
-                onAnimationCancelled(false /* isKeyguardOccluded */);
+                onAnimationCancelled();
                 finishRunnable.run();
             }
         };
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/OWNERS b/packages/SystemUI/src/com/android/systemui/accessibility/OWNERS
new file mode 100644
index 0000000..1f66c91
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/OWNERS
@@ -0,0 +1,3 @@
+# Bug component: 44215
+
+include /core/java/android/view/accessibility/OWNERS
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
index f3c71da..4158390 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
@@ -45,6 +45,8 @@
 
 import com.android.internal.R;
 import com.android.internal.accessibility.dialog.AccessibilityButtonChooserActivity;
+import com.android.internal.accessibility.util.AccessibilityUtils;
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.ScreenshotHelper;
 import com.android.systemui.CoreStartable;
 import com.android.systemui.dagger.SysUISingleton;
@@ -520,8 +522,11 @@
                 SCREENSHOT_ACCESSIBILITY_ACTIONS, new Handler(Looper.getMainLooper()), null);
     }
 
-    private void handleHeadsetHook() {
-        sendDownAndUpKeyEvents(KeyEvent.KEYCODE_HEADSETHOOK);
+    @VisibleForTesting
+    void handleHeadsetHook() {
+        if (!AccessibilityUtils.interceptHeadsetHookForActiveCall(mContext)) {
+            sendDownAndUpKeyEvents(KeyEvent.KEYCODE_HEADSETHOOK);
+        }
     }
 
     private void handleAccessibilityButton() {
diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingControllerImpl.kt
index 3d6d335..18bd467 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingControllerImpl.kt
@@ -41,7 +41,8 @@
     private val context: Context,
     @Background private val backgroundExecutor: DelayableExecutor,
     private val lazyController: Lazy<ControlsController>,
-    userTracker: UserTracker
+    private val packageUpdateMonitorFactory: PackageUpdateMonitor.Factory,
+    userTracker: UserTracker,
 ) : ControlsBindingController {
 
     companion object {
@@ -93,7 +94,8 @@
                 backgroundExecutor,
                 actionCallbackService,
                 currentUser,
-                component
+                component,
+                packageUpdateMonitorFactory
         )
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManager.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManager.kt
index 217f4d8..cb2476c 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManager.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.controls.controller
 
+import android.annotation.WorkerThread
 import android.content.ComponentName
 import android.content.Context
 import android.content.Intent
@@ -23,7 +24,6 @@
 import android.os.Binder
 import android.os.Bundle
 import android.os.IBinder
-import android.os.RemoteException
 import android.os.UserHandle
 import android.service.controls.ControlsProviderService
 import android.service.controls.ControlsProviderService.CALLBACK_BUNDLE
@@ -38,6 +38,7 @@
 import com.android.internal.annotations.GuardedBy
 import com.android.systemui.util.concurrency.DelayableExecutor
 import java.util.concurrent.TimeUnit
+import java.util.concurrent.atomic.AtomicBoolean
 
 /**
  * Manager for the lifecycle of the connection to a given [ControlsProviderService].
@@ -45,6 +46,9 @@
  * This class handles binding and unbinding and requests to the service. The class will queue
  * requests until the service is connected and dispatch them then.
  *
+ * If the provider app is updated, and we are currently bound to it, it will try to rebind after
+ * update is completed.
+ *
  * @property context A SystemUI context for binding to the services
  * @property executor A delayable executor for posting timeouts
  * @property actionCallbackService a callback interface to hand the remote service for sending
@@ -59,22 +63,22 @@
     private val executor: DelayableExecutor,
     private val actionCallbackService: IControlsActionCallback.Stub,
     val user: UserHandle,
-    val componentName: ComponentName
-) : IBinder.DeathRecipient {
+    val componentName: ComponentName,
+    packageUpdateMonitorFactory: PackageUpdateMonitor.Factory,
+) {
 
     val token: IBinder = Binder()
     private var requiresBound = false
     @GuardedBy("queuedServiceMethods")
     private val queuedServiceMethods: MutableSet<ServiceMethod> = ArraySet()
     private var wrapper: ServiceWrapper? = null
-    private var bindTryCount = 0
     private val TAG = javaClass.simpleName
     private var onLoadCanceller: Runnable? = null
 
+    private var lastForPanel = false
+
     companion object {
-        private const val BIND_RETRY_DELAY = 1000L // ms
         private const val LOAD_TIMEOUT_SECONDS = 20L // seconds
-        private const val MAX_BIND_RETRIES = 5
         private const val DEBUG = true
         private val BIND_FLAGS = Context.BIND_AUTO_CREATE or Context.BIND_FOREGROUND_SERVICE or
             Context.BIND_NOT_PERCEPTIBLE
@@ -91,60 +95,56 @@
         })
     }
 
-    private fun bindService(bind: Boolean, forPanel: Boolean = false) {
-        executor.execute {
-            requiresBound = bind
-            if (bind) {
-                if (bindTryCount != MAX_BIND_RETRIES && wrapper == null) {
-                    if (DEBUG) {
-                        Log.d(TAG, "Binding service $intent")
-                    }
-                    bindTryCount++
-                    try {
-                        val flags = if (forPanel) BIND_FLAGS_PANEL else BIND_FLAGS
-                        val bound = context
-                                .bindServiceAsUser(intent, serviceConnection, flags, user)
-                        if (!bound) {
-                            context.unbindService(serviceConnection)
-                        }
-                    } catch (e: SecurityException) {
-                        Log.e(TAG, "Failed to bind to service", e)
-                    }
-                }
-            } else {
-                if (DEBUG) {
-                    Log.d(TAG, "Unbinding service $intent")
-                }
-                bindTryCount = 0
-                wrapper?.run {
-                    context.unbindService(serviceConnection)
-                }
-                wrapper = null
+    private val packageUpdateMonitor = packageUpdateMonitorFactory.create(
+        user,
+        componentName.packageName,
+    ) {
+        if (requiresBound) {
+            // Let's unbind just in case. onBindingDied should have been called and unbound before.
+            executor.execute {
+                unbindAndCleanup("package updated")
+                bindService(true, lastForPanel)
             }
         }
     }
 
+    private fun bindService(bind: Boolean, forPanel: Boolean = false) {
+        executor.execute {
+            bindServiceBackground(bind, forPanel)
+        }
+    }
+
     private val serviceConnection = object : ServiceConnection {
+
+        val connected = AtomicBoolean(false)
+
         override fun onServiceConnected(name: ComponentName, service: IBinder) {
             if (DEBUG) Log.d(TAG, "onServiceConnected $name")
-            bindTryCount = 0
             wrapper = ServiceWrapper(IControlsProvider.Stub.asInterface(service))
-            try {
-                service.linkToDeath(this@ControlsProviderLifecycleManager, 0)
-            } catch (_: RemoteException) {}
+            packageUpdateMonitor.startMonitoring()
             handlePendingServiceMethods()
         }
 
         override fun onServiceDisconnected(name: ComponentName?) {
             if (DEBUG) Log.d(TAG, "onServiceDisconnected $name")
             wrapper = null
-            bindService(false)
+            // No need to call unbind. We may get a new `onServiceConnected`
         }
 
         override fun onNullBinding(name: ComponentName?) {
             if (DEBUG) Log.d(TAG, "onNullBinding $name")
             wrapper = null
-            context.unbindService(this)
+            executor.execute {
+                unbindAndCleanup("null binding")
+            }
+        }
+
+        override fun onBindingDied(name: ComponentName?) {
+            super.onBindingDied(name)
+            if (DEBUG) Log.d(TAG, "onBindingDied $name")
+            executor.execute {
+                unbindAndCleanup("binder died")
+            }
         }
     }
 
@@ -159,14 +159,55 @@
         }
     }
 
-    override fun binderDied() {
-        if (wrapper == null) return
-        wrapper = null
-        if (requiresBound) {
-            if (DEBUG) {
-                Log.d(TAG, "binderDied")
+    @WorkerThread
+    private fun bindServiceBackground(bind: Boolean, forPanel: Boolean = true) {
+        requiresBound = bind
+        if (bind) {
+            if (wrapper == null) {
+                if (DEBUG) {
+                    Log.d(TAG, "Binding service $intent")
+                }
+                try {
+                    lastForPanel = forPanel
+                    val flags = if (forPanel) BIND_FLAGS_PANEL else BIND_FLAGS
+                    var bound = false
+                    if (serviceConnection.connected.compareAndSet(false, true)) {
+                        bound = context
+                            .bindServiceAsUser(intent, serviceConnection, flags, user)
+                    }
+                    if (!bound) {
+                        Log.d(TAG, "Couldn't bind to $intent")
+                        doUnbind()
+                    }
+                } catch (e: SecurityException) {
+                    Log.e(TAG, "Failed to bind to service", e)
+                    // Couldn't even bind. Let's reset the connected value
+                    serviceConnection.connected.set(false)
+                }
             }
-            // Try rebinding some time later
+        } else {
+            unbindAndCleanup("unbind requested")
+            packageUpdateMonitor.stopMonitoring()
+        }
+    }
+
+    @WorkerThread
+    private fun unbindAndCleanup(reason: String) {
+        if (DEBUG) {
+            Log.d(TAG, "Unbinding service $intent. Reason: $reason")
+        }
+        wrapper = null
+        try {
+            doUnbind()
+        } catch (e: IllegalArgumentException) {
+            Log.e(TAG, "Failed to unbind service", e)
+        }
+    }
+
+    @WorkerThread
+    private fun doUnbind() {
+        if (serviceConnection.connected.compareAndSet(true, false)) {
+            context.unbindService(serviceConnection)
         }
     }
 
@@ -313,7 +354,7 @@
         fun run() {
             if (!callWrapper()) {
                 queueServiceMethod(this)
-                binderDied()
+                executor.execute { unbindAndCleanup("couldn't call through binder") }
             }
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/PackageUpdateMonitor.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/PackageUpdateMonitor.kt
new file mode 100644
index 0000000..1973b62
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/PackageUpdateMonitor.kt
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.controls.controller
+
+import android.content.Context
+import android.os.Handler
+import android.os.UserHandle
+import com.android.internal.content.PackageMonitor
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import java.util.concurrent.atomic.AtomicBoolean
+
+/** [PackageMonitor] that tracks when [packageName] has finished updating for user [user]. */
+class PackageUpdateMonitor
+@AssistedInject
+constructor(
+    @Assisted private val user: UserHandle,
+    @Assisted private val packageName: String,
+    @Assisted private val callback: Runnable,
+    @Background private val bgHandler: Handler,
+    @Application private val context: Context,
+) : PackageMonitor() {
+
+    private val monitoring = AtomicBoolean(false)
+
+    @AssistedFactory
+    fun interface Factory {
+        /**
+         * Create a [PackageUpdateMonitor] for a given [user] and [packageName]. It will run
+         * [callback] every time the package finishes updating.
+         */
+        fun create(user: UserHandle, packageName: String, callback: Runnable): PackageUpdateMonitor
+    }
+
+    /** Start monitoring for package updates. No-op if already monitoring. */
+    fun startMonitoring() {
+        if (monitoring.compareAndSet(/* expected */ false, /* new */ true)) {
+            register(context, user, false, bgHandler)
+        }
+    }
+
+    /** Stop monitoring for package updates. No-op if not monitoring. */
+    fun stopMonitoring() {
+        if (monitoring.compareAndSet(/* expected */ true, /* new */ false)) {
+            unregister()
+        }
+    }
+
+    /**
+     * If the package and the user match the ones for this [PackageUpdateMonitor], it will run
+     * [callback].
+     */
+    override fun onPackageUpdateFinished(packageName: String?, uid: Int) {
+        super.onPackageUpdateFinished(packageName, uid)
+        if (packageName == this.packageName && UserHandle.getUserHandleForUid(uid) == user) {
+            callback.run()
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ServiceWrapper.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ServiceWrapper.kt
index 2c717f5..45cb13b 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/controller/ServiceWrapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ServiceWrapper.kt
@@ -16,11 +16,11 @@
 
 package com.android.systemui.controls.controller
 
-import android.service.controls.actions.ControlAction
 import android.service.controls.IControlsActionCallback
 import android.service.controls.IControlsProvider
 import android.service.controls.IControlsSubscriber
 import android.service.controls.IControlsSubscription
+import android.service.controls.actions.ControlAction
 import android.service.controls.actions.ControlActionWrapper
 import android.util.Log
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
index 107e685..8b6bd24 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
@@ -243,7 +243,7 @@
                         Slog.e(TAG, "Called mergeAnimation, but finish callback is missing");
                         return;
                     }
-                    runner.onAnimationCancelled(false /* isKeyguardOccluded */);
+                    runner.onAnimationCancelled();
                     currentFinishCB.onTransitionFinished(null /* wct */, null /* t */);
                 } catch (RemoteException e) {
                     // nothing, we'll just let it finish on its own I guess.
@@ -418,7 +418,7 @@
         }
 
         @Override // Binder interface
-        public void onAnimationCancelled(boolean isKeyguardOccluded) {
+        public void onAnimationCancelled() {
             mKeyguardViewMediator.cancelKeyguardExitAnimation();
         }
     };
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 93ddfba..1a126d7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -959,20 +959,15 @@
                 @Nullable private ValueAnimator mOccludeByDreamAnimator;
 
                 @Override
-                public void onAnimationCancelled(boolean isKeyguardOccluded) {
+                public void onAnimationCancelled() {
                     mContext.getMainExecutor().execute(() -> {
                         if (mOccludeByDreamAnimator != null) {
                             mOccludeByDreamAnimator.cancel();
                         }
                     });
-                    // The value of isKeyguardOccluded here may come from mergeAnimation, which
-                    // isn't reliable. In all cases, after running or cancelling this animation,
-                    // keyguard should be occluded.
+
+                    Log.d(TAG, "OccludeByDreamAnimator#onAnimationCancelled. Set occluded = true");
                     setOccluded(true /* isOccluded */, false /* animate */);
-                    if (DEBUG) {
-                        Log.d(TAG, "Occlude by Dream animation cancelled. Occluded state is now: "
-                                + mOccluded);
-                    }
                 }
 
                 @Override
@@ -984,6 +979,7 @@
                         // Usually we rely on animation completion to synchronize occluded status,
                         // but there was no animation to play, so just update it now.
                         setOccluded(true /* isOccluded */, false /* animate */);
+                        finishedCallback.onAnimationFinished();
                     }
                 }
 
@@ -991,11 +987,8 @@
                         RemoteAnimationTarget[] wallpapers, RemoteAnimationTarget[] nonApps,
                         IRemoteAnimationFinishedCallback finishedCallback) throws RemoteException {
                     if (apps == null || apps.length == 0 || apps[0] == null) {
-                        if (DEBUG) {
-                            Log.d(TAG, "No apps provided to the OccludeByDream runner; "
-                                    + "skipping occluding animation.");
-                        }
-                        finishedCallback.onAnimationFinished();
+                        Log.d(TAG, "No apps provided to the OccludeByDream runner; "
+                                + "skipping occluding animation.");
                         return false;
                     }
 
@@ -1005,7 +998,6 @@
                     if (!isDream) {
                         Log.w(TAG, "The occluding app isn't Dream; "
                                 + "finishing up. Please check that the config is correct.");
-                        finishedCallback.onAnimationFinished();
                         return false;
                     }
 
@@ -1071,17 +1063,14 @@
                 private final Matrix mUnoccludeMatrix = new Matrix();
 
                 @Override
-                public void onAnimationCancelled(boolean isKeyguardOccluded) {
+                public void onAnimationCancelled() {
                     mContext.getMainExecutor().execute(() -> {
                         if (mUnoccludeAnimator != null) {
                             mUnoccludeAnimator.cancel();
                         }
                     });
 
-                    setOccluded(isKeyguardOccluded /* isOccluded */, false /* animate */);
-                    Log.d(TAG, "Unocclude animation cancelled. Occluded state is now: "
-                            + mOccluded);
-
+                    Log.d(TAG, "Unocclude animation cancelled.");
                     mInteractionJankMonitor.cancel(CUJ_LOCKSCREEN_OCCLUSION);
                 }
 
@@ -3404,9 +3393,9 @@
         }
 
         @Override
-        public void onAnimationCancelled(boolean isKeyguardOccluded) throws RemoteException {
+        public void onAnimationCancelled() throws RemoteException {
             if (mRunner != null) {
-                mRunner.onAnimationCancelled(isKeyguardOccluded);
+                mRunner.onAnimationCancelled();
             }
         }
 
@@ -3452,13 +3441,9 @@
         }
 
         @Override
-        public void onAnimationCancelled(boolean isKeyguardOccluded) throws RemoteException {
-            super.onAnimationCancelled(isKeyguardOccluded);
-
-            Log.d(TAG, "Occlude animation cancelled by WM. "
-                    + "Setting occluded state to: " + isKeyguardOccluded);
-            setOccluded(isKeyguardOccluded /* occluded */, false /* animate */);
-
+        public void onAnimationCancelled() throws RemoteException {
+            super.onAnimationCancelled();
+            Log.d(TAG, "Occlude animation cancelled by WM.");
             mInteractionJankMonitor.cancel(CUJ_LOCKSCREEN_OCCLUSION);
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/log/table/LogProxy.kt b/packages/SystemUI/src/com/android/systemui/log/table/LogProxy.kt
new file mode 100644
index 0000000..53886aa
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/log/table/LogProxy.kt
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.log.table
+
+import android.util.Log
+import javax.inject.Inject
+
+/** Dagger-friendly interface so we can inject a fake [android.util.Log] in tests */
+interface LogProxy {
+    /** verbose log */
+    fun v(tag: String, message: String)
+
+    /** debug log */
+    fun d(tag: String, message: String)
+
+    /** info log */
+    fun i(tag: String, message: String)
+
+    /** warning log */
+    fun w(tag: String, message: String)
+
+    /** error log */
+    fun e(tag: String, message: String)
+
+    /** wtf log */
+    fun wtf(tag: String, message: String)
+}
+
+class LogProxyDefault @Inject constructor() : LogProxy {
+    override fun v(tag: String, message: String) {
+        Log.v(tag, message)
+    }
+
+    override fun d(tag: String, message: String) {
+        Log.d(tag, message)
+    }
+
+    override fun i(tag: String, message: String) {
+        Log.i(tag, message)
+    }
+
+    override fun w(tag: String, message: String) {
+        Log.w(tag, message)
+    }
+
+    override fun e(tag: String, message: String) {
+        Log.e(tag, message)
+    }
+
+    override fun wtf(tag: String, message: String) {
+        Log.wtf(tag, message)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/log/table/TableLogBuffer.kt b/packages/SystemUI/src/com/android/systemui/log/table/TableLogBuffer.kt
index faaa205..9d883cc 100644
--- a/packages/SystemUI/src/com/android/systemui/log/table/TableLogBuffer.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/table/TableLogBuffer.kt
@@ -19,10 +19,17 @@
 import android.os.Trace
 import com.android.systemui.Dumpable
 import com.android.systemui.common.buffer.RingBuffer
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.plugins.log.LogLevel
+import com.android.systemui.plugins.log.LogcatEchoTracker
 import com.android.systemui.util.time.SystemClock
 import java.io.PrintWriter
 import java.text.SimpleDateFormat
 import java.util.Locale
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.channels.Channel
+import kotlinx.coroutines.launch
 
 /**
  * A logger that logs changes in table format.
@@ -73,12 +80,18 @@
     maxSize: Int,
     private val name: String,
     private val systemClock: SystemClock,
+    private val logcatEchoTracker: LogcatEchoTracker,
+    @Background private val bgDispatcher: CoroutineDispatcher,
+    private val coroutineScope: CoroutineScope,
+    private val localLogcat: LogProxy = LogProxyDefault(),
 ) : Dumpable {
     init {
         if (maxSize <= 0) {
             throw IllegalArgumentException("maxSize must be > 0")
         }
     }
+    // For local logcat, send messages across this channel so the background job can process them
+    private val logMessageChannel = Channel<TableChange>(capacity = 10)
 
     private val buffer = RingBuffer(maxSize) { TableChange() }
 
@@ -105,6 +118,16 @@
             tableLogBuffer = this,
         )
 
+    /** Start this log buffer logging in the background */
+    internal fun init() {
+        coroutineScope.launch(bgDispatcher) {
+            while (!logMessageChannel.isClosedForReceive) {
+                val log = logMessageChannel.receive()
+                echoToDesiredEndpoints(log)
+            }
+        }
+    }
+
     /**
      * Log the differences between [prevVal] and [newVal].
      *
@@ -189,6 +212,7 @@
         Trace.beginSection("TableLogBuffer#logChange(string)")
         val change = obtain(timestamp, prefix, columnName, isInitial)
         change.set(value)
+        tryAddMessage(change)
         Trace.endSection()
     }
 
@@ -202,6 +226,7 @@
         Trace.beginSection("TableLogBuffer#logChange(boolean)")
         val change = obtain(timestamp, prefix, columnName, isInitial)
         change.set(value)
+        tryAddMessage(change)
         Trace.endSection()
     }
 
@@ -215,9 +240,14 @@
         Trace.beginSection("TableLogBuffer#logChange(int)")
         val change = obtain(timestamp, prefix, columnName, isInitial)
         change.set(value)
+        tryAddMessage(change)
         Trace.endSection()
     }
 
+    private fun tryAddMessage(change: TableChange) {
+        logMessageChannel.trySend(change)
+    }
+
     // TODO(b/259454430): Add additional change types here.
 
     @Synchronized
@@ -258,6 +288,17 @@
         Trace.endSection()
     }
 
+    private fun echoToDesiredEndpoints(change: TableChange) {
+        if (
+            logcatEchoTracker.isBufferLoggable(bufferName = name, LogLevel.DEBUG) ||
+                logcatEchoTracker.isTagLoggable(change.columnName, LogLevel.DEBUG)
+        ) {
+            if (change.hasData()) {
+                localLogcat.d(name, change.logcatRepresentation())
+            }
+        }
+    }
+
     @Synchronized
     override fun dump(pw: PrintWriter, args: Array<out String>) {
         pw.println(HEADER_PREFIX + name)
@@ -284,6 +325,12 @@
         pw.println()
     }
 
+    /** Transforms an individual [TableChange] into a String for logcat */
+    private fun TableChange.logcatRepresentation(): String {
+        val formattedTimestamp = TABLE_LOG_DATE_FORMAT.format(timestamp)
+        return "$formattedTimestamp$SEPARATOR${getName()}$SEPARATOR${getVal()}"
+    }
+
     /**
      * A private implementation of [TableRowLogger].
      *
diff --git a/packages/SystemUI/src/com/android/systemui/log/table/TableLogBufferFactory.kt b/packages/SystemUI/src/com/android/systemui/log/table/TableLogBufferFactory.kt
index 06668d3..42e742d 100644
--- a/packages/SystemUI/src/com/android/systemui/log/table/TableLogBufferFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/table/TableLogBufferFactory.kt
@@ -17,10 +17,15 @@
 package com.android.systemui.log.table
 
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.log.LogBufferHelper.Companion.adjustMaxSize
+import com.android.systemui.plugins.log.LogcatEchoTracker
 import com.android.systemui.util.time.SystemClock
 import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
 
 @SysUISingleton
 class TableLogBufferFactory
@@ -28,6 +33,9 @@
 constructor(
     private val dumpManager: DumpManager,
     private val systemClock: SystemClock,
+    private val logcatEchoTracker: LogcatEchoTracker,
+    @Background private val bgDispatcher: CoroutineDispatcher,
+    @Application private val coroutineScope: CoroutineScope,
 ) {
     private val existingBuffers = mutableMapOf<String, TableLogBuffer>()
 
@@ -44,8 +52,17 @@
         name: String,
         maxSize: Int,
     ): TableLogBuffer {
-        val tableBuffer = TableLogBuffer(adjustMaxSize(maxSize), name, systemClock)
+        val tableBuffer =
+            TableLogBuffer(
+                adjustMaxSize(maxSize),
+                name,
+                systemClock,
+                logcatEchoTracker,
+                bgDispatcher,
+                coroutineScope,
+            )
         dumpManager.registerNormalDumpable(name, tableBuffer)
+        tableBuffer.init()
         return tableBuffer
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDeviceManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDeviceManager.kt
index 120704c..3fc3ad6 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDeviceManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDeviceManager.kt
@@ -154,6 +154,7 @@
             get() = controller?.sessionToken
         private var started = false
         private var playbackType = PLAYBACK_TYPE_UNKNOWN
+        private var playbackVolumeControlId: String? = null
         private var current: MediaDeviceData? = null
             set(value) {
                 val sameWithoutIcon = value != null && value.equalsWithoutIcon(field)
@@ -181,6 +182,7 @@
                     localMediaManager.startScan()
                     muteAwaitConnectionManager?.startListening()
                     playbackType = controller?.playbackInfo?.playbackType ?: PLAYBACK_TYPE_UNKNOWN
+                    playbackVolumeControlId = controller?.playbackInfo?.volumeControlId
                     controller?.registerCallback(this)
                     updateCurrent()
                     started = true
@@ -209,6 +211,8 @@
                 println("    current device is ${current?.name}")
                 val type = controller?.playbackInfo?.playbackType
                 println("    PlaybackType=$type (1 for local, 2 for remote) cached=$playbackType")
+                val volumeControlId = controller?.playbackInfo?.volumeControlId
+                println("    volumeControlId=$volumeControlId cached= $playbackVolumeControlId")
                 println("    routingSession=$routingSession")
                 println("    selectedRoutes=$selectedRoutes")
             }
@@ -217,10 +221,15 @@
         @WorkerThread
         override fun onAudioInfoChanged(info: MediaController.PlaybackInfo?) {
             val newPlaybackType = info?.playbackType ?: PLAYBACK_TYPE_UNKNOWN
-            if (newPlaybackType == playbackType) {
+            val newPlaybackVolumeControlId = info?.volumeControlId
+            if (
+                newPlaybackType == playbackType &&
+                    newPlaybackVolumeControlId == playbackVolumeControlId
+            ) {
                 return
             }
             playbackType = newPlaybackType
+            playbackVolumeControlId = newPlaybackVolumeControlId
             updateCurrent()
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt
index aa8e2c0..187019a 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt
@@ -154,5 +154,5 @@
             }
         }
 
-        override fun onAnimationCancelled(isKeyguardOccluded: Boolean) {}
+        override fun onAnimationCancelled() {}
     }
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index c9d1da38..77a65b2 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
@@ -150,7 +150,7 @@
                 }
 
                 @Override
-                public void onAnimationCancelled(boolean isKeyguardOccluded) {
+                public void onAnimationCancelled() {
                 }
             };
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/OWNERS b/packages/SystemUI/tests/src/com/android/systemui/accessibility/OWNERS
new file mode 100644
index 0000000..a2001e6
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/OWNERS
@@ -0,0 +1 @@
+include /core/java/android/view/accessibility/OWNERS
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/SystemActionsTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/SystemActionsTest.java
new file mode 100644
index 0000000..025c88c
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/SystemActionsTest.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.accessibility;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.hardware.input.InputManager;
+import android.os.RemoteException;
+import android.telecom.TelecomManager;
+import android.telephony.TelephonyManager;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.view.KeyEvent;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.recents.Recents;
+import com.android.systemui.settings.FakeDisplayTracker;
+import com.android.systemui.settings.UserTracker;
+import com.android.systemui.shade.ShadeController;
+import com.android.systemui.statusbar.NotificationShadeWindowController;
+import com.android.systemui.statusbar.phone.CentralSurfaces;
+
+import dagger.Lazy;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+
+@TestableLooper.RunWithLooper
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class SystemActionsTest extends SysuiTestCase {
+    @Mock
+    private UserTracker mUserTracker;
+    @Mock
+    private NotificationShadeWindowController mNotificationShadeController;
+    @Mock
+    private ShadeController mShadeController;
+    @Mock
+    private Lazy<Optional<CentralSurfaces>> mCentralSurfacesOptionalLazy;
+    @Mock
+    private Optional<Recents> mRecentsOptional;
+    @Mock
+    private TelecomManager mTelecomManager;
+    @Mock
+    private InputManager mInputManager;
+    private final FakeDisplayTracker mDisplayTracker = new FakeDisplayTracker(mContext);
+
+    private SystemActions mSystemActions;
+
+    @Before
+    public void setUp() throws RemoteException {
+        MockitoAnnotations.initMocks(this);
+        mContext.addMockSystemService(TelecomManager.class, mTelecomManager);
+        mContext.addMockSystemService(InputManager.class, mInputManager);
+        mSystemActions = new SystemActions(mContext, mUserTracker, mNotificationShadeController,
+                mShadeController, mCentralSurfacesOptionalLazy, mRecentsOptional, mDisplayTracker);
+    }
+
+    @Test
+    public void handleHeadsetHook_callStateIdle_injectsKeyEvents() {
+        when(mTelecomManager.getCallState()).thenReturn(TelephonyManager.CALL_STATE_IDLE);
+        // Use a custom doAnswer captor that copies the KeyEvent before storing it, because the
+        // method under test modifies the event object after injecting it which prevents
+        // reliably asserting on the event properties.
+        final List<KeyEvent> keyEvents = new ArrayList<>();
+        doAnswer(invocation -> {
+            keyEvents.add(new KeyEvent(invocation.getArgument(0)));
+            return null;
+        }).when(mInputManager).injectInputEvent(any(), anyInt());
+
+        mSystemActions.handleHeadsetHook();
+
+        assertThat(keyEvents.size()).isEqualTo(2);
+        assertThat(keyEvents.get(0).getKeyCode()).isEqualTo(KeyEvent.KEYCODE_HEADSETHOOK);
+        assertThat(keyEvents.get(0).getAction()).isEqualTo(KeyEvent.ACTION_DOWN);
+        assertThat(keyEvents.get(1).getKeyCode()).isEqualTo(KeyEvent.KEYCODE_HEADSETHOOK);
+        assertThat(keyEvents.get(1).getAction()).isEqualTo(KeyEvent.ACTION_UP);
+    }
+
+    @Test
+    public void handleHeadsetHook_callStateRinging_answersCall() {
+        when(mTelecomManager.getCallState()).thenReturn(TelephonyManager.CALL_STATE_RINGING);
+
+        mSystemActions.handleHeadsetHook();
+
+        verify(mTelecomManager).acceptRingingCall();
+    }
+
+    @Test
+    public void handleHeadsetHook_callStateOffhook_endsCall() {
+        when(mTelecomManager.getCallState()).thenReturn(TelephonyManager.CALL_STATE_OFFHOOK);
+
+        mSystemActions.handleHeadsetHook();
+
+        verify(mTelecomManager).endCall();
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityLaunchAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityLaunchAnimatorTest.kt
index 578e1d4..cc00436 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityLaunchAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityLaunchAnimatorTest.kt
@@ -159,22 +159,11 @@
     @Test
     fun doesNotStartIfAnimationIsCancelled() {
         val runner = activityLaunchAnimator.createRunner(controller)
-        runner.onAnimationCancelled(false /* isKeyguardOccluded */)
+        runner.onAnimationCancelled()
         runner.onAnimationStart(0, emptyArray(), emptyArray(), emptyArray(), iCallback)
 
         waitForIdleSync()
-        verify(controller).onLaunchAnimationCancelled(false /* newKeyguardOccludedState */)
-        verify(controller, never()).onLaunchAnimationStart(anyBoolean())
-    }
-
-    @Test
-    fun passesOccludedStateToLaunchAnimationCancelled_ifTrue() {
-        val runner = activityLaunchAnimator.createRunner(controller)
-        runner.onAnimationCancelled(true /* isKeyguardOccluded */)
-        runner.onAnimationStart(0, emptyArray(), emptyArray(), emptyArray(), iCallback)
-
-        waitForIdleSync()
-        verify(controller).onLaunchAnimationCancelled(true /* newKeyguardOccludedState */)
+        verify(controller).onLaunchAnimationCancelled()
         verify(controller, never()).onLaunchAnimationStart(anyBoolean())
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsBindingControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsBindingControllerImplTest.kt
index ebbe096..26cbd77 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsBindingControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsBindingControllerImplTest.kt
@@ -41,11 +41,11 @@
 import org.mockito.Captor
 import org.mockito.Mock
 import org.mockito.Mockito
-import org.mockito.Mockito.`when`
 import org.mockito.Mockito.mock
 import org.mockito.Mockito.never
 import org.mockito.Mockito.times
 import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
 import org.mockito.MockitoAnnotations
 
 @SmallTest
@@ -378,7 +378,13 @@
     executor: DelayableExecutor,
     lazyController: Lazy<ControlsController>,
     userTracker: UserTracker
-) : ControlsBindingControllerImpl(context, executor, lazyController, userTracker) {
+) : ControlsBindingControllerImpl(
+    context,
+    executor,
+    lazyController,
+    mock(PackageUpdateMonitor.Factory::class.java),
+    userTracker
+) {
 
     companion object {
         val providers = mutableListOf<ControlsProviderLifecycleManager>()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManagerTest.kt
index da548f7..b5d3476 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManagerTest.kt
@@ -30,6 +30,10 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.time.FakeSystemClock
 import org.junit.After
 import org.junit.Assert.assertEquals
@@ -39,17 +43,17 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.ArgumentCaptor
-import org.mockito.ArgumentMatchers
-import org.mockito.ArgumentMatchers.any
 import org.mockito.ArgumentMatchers.anyString
-import org.mockito.ArgumentMatchers.eq
 import org.mockito.Captor
 import org.mockito.Mock
-import org.mockito.Mockito.`when`
 import org.mockito.Mockito.anyInt
-import org.mockito.Mockito.mock
+import org.mockito.Mockito.clearInvocations
+import org.mockito.Mockito.inOrder
 import org.mockito.Mockito.never
+import org.mockito.Mockito.times
 import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyNoMoreInteractions
+import org.mockito.Mockito.`when`
 import org.mockito.MockitoAnnotations
 
 @SmallTest
@@ -62,16 +66,21 @@
     private lateinit var subscriberService: IControlsSubscriber.Stub
     @Mock
     private lateinit var service: IControlsProvider.Stub
-
+    @Mock
+    private lateinit var packageUpdateMonitor: PackageUpdateMonitor
     @Captor
     private lateinit var wrapperCaptor: ArgumentCaptor<ControlActionWrapper>
 
+    private lateinit var packageUpdateMonitorFactory: FakePackageUpdateMonitorFactory
+
     private val componentName = ComponentName("test.pkg", "test.cls")
     private lateinit var manager: ControlsProviderLifecycleManager
     private lateinit var executor: FakeExecutor
+    private lateinit var fakeSystemClock: FakeSystemClock
 
     companion object {
         fun <T> capture(argumentCaptor: ArgumentCaptor<T>): T = argumentCaptor.capture()
+        private val USER = UserHandle.of(0)
     }
 
     @Before
@@ -79,16 +88,20 @@
         MockitoAnnotations.initMocks(this)
 
         context.addMockService(componentName, service)
-        executor = FakeExecutor(FakeSystemClock())
+        fakeSystemClock = FakeSystemClock()
+        executor = FakeExecutor(fakeSystemClock)
         `when`(service.asBinder()).thenCallRealMethod()
-        `when`(service.queryLocalInterface(ArgumentMatchers.anyString())).thenReturn(service)
+        `when`(service.queryLocalInterface(anyString())).thenReturn(service)
+
+        packageUpdateMonitorFactory = FakePackageUpdateMonitorFactory(packageUpdateMonitor)
 
         manager = ControlsProviderLifecycleManager(
                 context,
                 executor,
                 actionCallbackService,
-                UserHandle.of(0),
-                componentName
+                USER,
+                componentName,
+                packageUpdateMonitorFactory,
         )
     }
 
@@ -122,7 +135,7 @@
 
     @Test
     fun testNullBinding() {
-        val mockContext = mock(Context::class.java)
+        val mockContext = mock<Context>()
         lateinit var serviceConnection: ServiceConnection
         `when`(mockContext.bindServiceAsUser(any(), any(), anyInt(), any())).thenAnswer {
             val component = (it.arguments[0] as Intent).component
@@ -139,8 +152,9 @@
                 mockContext,
                 executor,
                 actionCallbackService,
-                UserHandle.of(0),
-                componentName
+                USER,
+                componentName,
+                packageUpdateMonitorFactory,
         )
 
         nullManager.bindService()
@@ -229,14 +243,15 @@
 
     @Test
     fun testFalseBindCallsUnbind() {
-        val falseContext = mock(Context::class.java)
+        val falseContext = mock<Context>()
         `when`(falseContext.bindServiceAsUser(any(), any(), anyInt(), any())).thenReturn(false)
         val manager = ControlsProviderLifecycleManager(
             falseContext,
             executor,
             actionCallbackService,
-            UserHandle.of(0),
-            componentName
+            USER,
+            componentName,
+            packageUpdateMonitorFactory,
         )
         manager.bindService()
         executor.runAllReady()
@@ -247,4 +262,150 @@
         verify(falseContext).bindServiceAsUser(any(), captor.capture(), anyInt(), any())
         verify(falseContext).unbindService(captor.value)
     }
+
+    @Test
+    fun testPackageUpdateMonitor_createdWithCorrectValues() {
+        assertEquals(USER, packageUpdateMonitorFactory.lastUser)
+        assertEquals(componentName.packageName, packageUpdateMonitorFactory.lastPackage)
+    }
+
+    @Test
+    fun testBound_packageMonitorStartsMonitoring() {
+        manager.bindService()
+        executor.runAllReady()
+
+        // Service will connect and monitoring should start
+        verify(packageUpdateMonitor).startMonitoring()
+    }
+
+    @Test
+    fun testOnPackageUpdateWhileBound_unbound_thenBindAgain() {
+        val mockContext = mock<Context> {
+            `when`(bindServiceAsUser(any(), any(), anyInt(), any())).thenReturn(true)
+        }
+
+        val manager = ControlsProviderLifecycleManager(
+            mockContext,
+            executor,
+            actionCallbackService,
+            USER,
+            componentName,
+            packageUpdateMonitorFactory,
+        )
+
+        manager.bindService()
+        executor.runAllReady()
+        clearInvocations(mockContext)
+
+        packageUpdateMonitorFactory.lastCallback?.run()
+        executor.runAllReady()
+
+        val inOrder = inOrder(mockContext)
+        inOrder.verify(mockContext).unbindService(any())
+        inOrder.verify(mockContext).bindServiceAsUser(any(), any(), anyInt(), any())
+    }
+
+    @Test
+    fun testOnPackageUpdateWhenNotBound_nothingHappens() {
+        val mockContext = mock<Context> {
+            `when`(bindServiceAsUser(any(), any(), anyInt(), any())).thenReturn(true)
+        }
+
+        ControlsProviderLifecycleManager(
+            mockContext,
+            executor,
+            actionCallbackService,
+            USER,
+            componentName,
+            packageUpdateMonitorFactory,
+        )
+
+        packageUpdateMonitorFactory.lastCallback?.run()
+        verifyNoMoreInteractions(mockContext)
+    }
+
+    @Test
+    fun testUnbindService_stopsTracking() {
+        manager.bindService()
+        manager.unbindService()
+        executor.runAllReady()
+
+        verify(packageUpdateMonitor).stopMonitoring()
+    }
+
+    @Test
+    fun testRebindForPanelWithSameFlags() {
+        val mockContext = mock<Context> {
+            `when`(bindServiceAsUser(any(), any(), anyInt(), any())).thenReturn(true)
+        }
+
+        val manager = ControlsProviderLifecycleManager(
+            mockContext,
+            executor,
+            actionCallbackService,
+            USER,
+            componentName,
+            packageUpdateMonitorFactory,
+        )
+
+        manager.bindServiceForPanel()
+        executor.runAllReady()
+
+        val flagsCaptor = argumentCaptor<Int>()
+        verify(mockContext).bindServiceAsUser(any(), any(), capture(flagsCaptor), any())
+        clearInvocations(mockContext)
+
+        packageUpdateMonitorFactory.lastCallback?.run()
+        executor.runAllReady()
+
+        verify(mockContext).bindServiceAsUser(any(), any(), eq(flagsCaptor.value), any())
+    }
+
+    @Test
+    fun testBindAfterSecurityExceptionWorks() {
+        val mockContext = mock<Context> {
+            `when`(bindServiceAsUser(any(), any(), anyInt(), any()))
+                .thenThrow(SecurityException("exception"))
+        }
+
+        val manager = ControlsProviderLifecycleManager(
+            mockContext,
+            executor,
+            actionCallbackService,
+            USER,
+            componentName,
+            packageUpdateMonitorFactory,
+        )
+
+        manager.bindServiceForPanel()
+        executor.runAllReady()
+
+        `when`(mockContext.bindServiceAsUser(any(), any(), anyInt(), any())).thenReturn(true)
+
+        manager.bindServiceForPanel()
+        executor.runAllReady()
+
+        verify(mockContext, times(2)).bindServiceAsUser(any(), any(), anyInt(), any())
+    }
+
+    private class FakePackageUpdateMonitorFactory(
+        private val monitor: PackageUpdateMonitor
+    ) : PackageUpdateMonitor.Factory {
+
+        var lastUser: UserHandle? = null
+        var lastPackage: String? = null
+        var lastCallback: Runnable? = null
+
+        override fun create(
+            user: UserHandle,
+            packageName: String,
+            callback: Runnable
+        ): PackageUpdateMonitor {
+            lastUser = user
+            lastPackage = packageName
+            lastCallback = callback
+            return monitor
+        }
+    }
 }
+
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/PackageUpdateMonitorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/PackageUpdateMonitorTest.kt
new file mode 100644
index 0000000..6954710
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/PackageUpdateMonitorTest.kt
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.controls.controller
+
+import android.content.Context
+import android.os.Handler
+import android.os.UserHandle
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.mock
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.clearInvocations
+import org.mockito.Mockito.never
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyNoMoreInteractions
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class PackageUpdateMonitorTest : SysuiTestCase() {
+
+    @Mock private lateinit var context: Context
+    @Mock private lateinit var bgHandler: Handler
+
+    private lateinit var underTest: PackageUpdateMonitor
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+    }
+
+    @Test
+    fun startMonitoring_registerOnlyOnce() {
+        underTest = PackageUpdateMonitor(USER, PACKAGE, {}, bgHandler, context)
+
+        underTest.startMonitoring()
+        // There are two receivers registered
+        verify(context, times(2))
+            .registerReceiverAsUser(any(), eq(USER), any(), eq(null), eq(bgHandler))
+
+        underTest.startMonitoring()
+        verifyNoMoreInteractions(context)
+    }
+
+    @Test
+    fun stopMonitoring_unregistersOnlyOnce() {
+        underTest = PackageUpdateMonitor(USER, PACKAGE, {}, bgHandler, context)
+
+        underTest.startMonitoring()
+        clearInvocations(context)
+
+        underTest.stopMonitoring()
+        verify(context).unregisterReceiver(any())
+
+        underTest.stopMonitoring()
+        verifyNoMoreInteractions(context)
+    }
+
+    @Test
+    fun onPackageUpdated_correctPackageAndUser_callbackRuns() {
+        val callback = mock<Runnable>()
+
+        underTest = PackageUpdateMonitor(USER, PACKAGE, callback, bgHandler, context)
+
+        underTest.onPackageUpdateFinished(PACKAGE, UserHandle.getUid(USER.identifier, 10000))
+        verify(callback).run()
+    }
+
+    @Test
+    fun onPackageUpdated_correctPackage_wrongUser_callbackDoesntRun() {
+        val callback = mock<Runnable>()
+
+        underTest = PackageUpdateMonitor(USER, PACKAGE, callback, bgHandler, context)
+
+        underTest.onPackageUpdateFinished(PACKAGE, UserHandle.getUid(USER.identifier + 1, 10000))
+        verify(callback, never()).run()
+    }
+
+    @Test
+    fun onPackageUpdated_wrongPackage_correctUser_callbackDoesntRun() {
+        val callback = mock<Runnable>()
+
+        underTest = PackageUpdateMonitor(USER, PACKAGE, callback, bgHandler, context)
+
+        underTest.onPackageUpdateFinished("bad", UserHandle.getUid(USER.identifier + 1, 10000))
+        verify(callback, never()).run()
+    }
+
+    companion object {
+        private val USER = UserHandle.of(0)
+        private val PACKAGE = "pkg"
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
index b50cf73..1d0b58a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
@@ -67,6 +67,7 @@
 import com.android.systemui.user.data.repository.FakeUserRepository
 import com.android.systemui.util.mockito.KotlinArgumentCaptor
 import com.android.systemui.util.mockito.captureMany
+import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
 import com.android.systemui.util.time.FakeSystemClock
 import com.android.systemui.util.time.SystemClock
@@ -193,8 +194,24 @@
         bypassControllerOverride: KeyguardBypassController? = bypassController
     ): DeviceEntryFaceAuthRepositoryImpl {
         val systemClock = FakeSystemClock()
-        val faceAuthBuffer = TableLogBuffer(10, "face auth", systemClock)
-        val faceDetectBuffer = TableLogBuffer(10, "face detect", systemClock)
+        val faceAuthBuffer =
+            TableLogBuffer(
+                10,
+                "face auth",
+                systemClock,
+                mock(),
+                testDispatcher,
+                testScope.backgroundScope
+            )
+        val faceDetectBuffer =
+            TableLogBuffer(
+                10,
+                "face detect",
+                systemClock,
+                mock(),
+                testDispatcher,
+                testScope.backgroundScope
+            )
         keyguardTransitionRepository = FakeKeyguardTransitionRepository()
         val keyguardTransitionInteractor =
             KeyguardTransitionInteractor(keyguardTransitionRepository)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/log/table/FakeLogProxy.kt b/packages/SystemUI/tests/src/com/android/systemui/log/table/FakeLogProxy.kt
new file mode 100644
index 0000000..471c461
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/log/table/FakeLogProxy.kt
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.log.table
+
+/**
+ * Fake [LogProxy] that collects all lines sent to it. Mimics the ADB logcat format without the
+ * timestamp. [FakeLogProxy.d] will write a log like so:
+ * ```
+ * logger.d("TAG", "message here")
+ * // writes this to the [logs] field
+ * "D TAG: message here"
+ * ```
+ *
+ * Logs sent to this class are collected as a list of strings for simple test assertions.
+ */
+class FakeLogProxy : LogProxy {
+    val logs: MutableList<String> = mutableListOf()
+
+    override fun v(tag: String, message: String) {
+        logs.add("V $tag: $message")
+    }
+
+    override fun d(tag: String, message: String) {
+        logs.add("D $tag: $message")
+    }
+
+    override fun i(tag: String, message: String) {
+        logs.add("I $tag: $message")
+    }
+
+    override fun w(tag: String, message: String) {
+        logs.add("W $tag: $message")
+    }
+
+    override fun e(tag: String, message: String) {
+        logs.add("E $tag: $message")
+    }
+
+    override fun wtf(tag: String, message: String) {
+        logs.add("WTF $tag: $message")
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/log/table/LogDiffsForTableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/log/table/LogDiffsForTableTest.kt
index c49337a..1d182cf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/log/table/LogDiffsForTableTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/log/table/LogDiffsForTableTest.kt
@@ -19,6 +19,7 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.log.table.TableChange.Companion.IS_INITIAL_PREFIX
+import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
 import java.io.PrintWriter
@@ -39,7 +40,8 @@
 @OptIn(ExperimentalCoroutinesApi::class)
 class LogDiffsForTableTest : SysuiTestCase() {
 
-    private val testScope = TestScope(UnconfinedTestDispatcher())
+    private val testDispatcher = UnconfinedTestDispatcher()
+    private val testScope = TestScope(testDispatcher)
 
     private lateinit var systemClock: FakeSystemClock
     private lateinit var tableLogBuffer: TableLogBuffer
@@ -47,7 +49,15 @@
     @Before
     fun setUp() {
         systemClock = FakeSystemClock()
-        tableLogBuffer = TableLogBuffer(MAX_SIZE, BUFFER_NAME, systemClock)
+        tableLogBuffer =
+            TableLogBuffer(
+                MAX_SIZE,
+                BUFFER_NAME,
+                systemClock,
+                mock(),
+                testDispatcher,
+                testScope.backgroundScope,
+            )
     }
 
     // ---- Flow<Boolean> tests ----
diff --git a/packages/SystemUI/tests/src/com/android/systemui/log/table/TableLogBufferFactoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/log/table/TableLogBufferFactoryTest.kt
index af83a56..8ba7643 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/log/table/TableLogBufferFactoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/log/table/TableLogBufferFactoryTest.kt
@@ -22,13 +22,20 @@
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
 import org.junit.Test
 
+@OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
 class TableLogBufferFactoryTest : SysuiTestCase() {
     private val dumpManager: DumpManager = mock()
     private val systemClock = FakeSystemClock()
-    private val underTest = TableLogBufferFactory(dumpManager, systemClock)
+    private val testDispatcher = UnconfinedTestDispatcher()
+    private val testScope = TestScope(testDispatcher)
+    private val underTest =
+        TableLogBufferFactory(dumpManager, systemClock, mock(), testDispatcher, testScope)
 
     @Test
     fun create_alwaysCreatesNewInstance() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/log/table/TableLogBufferTest.kt b/packages/SystemUI/tests/src/com/android/systemui/log/table/TableLogBufferTest.kt
index aed830a..a2b2322 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/log/table/TableLogBufferTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/log/table/TableLogBufferTest.kt
@@ -19,31 +19,66 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.log.table.TableChange.Companion.IS_INITIAL_PREFIX
+import com.android.systemui.plugins.log.LogLevel
+import com.android.systemui.plugins.log.LogcatEchoTracker
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
 import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
 import java.io.PrintWriter
 import java.io.StringWriter
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
 import org.junit.Before
 import org.junit.Test
 
+@OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
 class TableLogBufferTest : SysuiTestCase() {
     private lateinit var underTest: TableLogBuffer
 
     private lateinit var systemClock: FakeSystemClock
     private lateinit var outputWriter: StringWriter
+    private lateinit var logcatEchoTracker: LogcatEchoTracker
+    private lateinit var localLogcat: FakeLogProxy
+
+    private val testDispatcher = UnconfinedTestDispatcher()
+    private val testScope = TestScope(testDispatcher)
 
     @Before
     fun setup() {
+        localLogcat = FakeLogProxy()
+        logcatEchoTracker = mock()
         systemClock = FakeSystemClock()
         outputWriter = StringWriter()
 
-        underTest = TableLogBuffer(MAX_SIZE, NAME, systemClock)
+        underTest =
+            TableLogBuffer(
+                MAX_SIZE,
+                NAME,
+                systemClock,
+                logcatEchoTracker,
+                testDispatcher,
+                testScope.backgroundScope,
+                localLogcat = localLogcat,
+            )
+        underTest.init()
     }
 
     @Test(expected = IllegalArgumentException::class)
     fun maxSizeZero_throwsException() {
-        TableLogBuffer(maxSize = 0, "name", systemClock)
+        TableLogBuffer(
+            maxSize = 0,
+            "name",
+            systemClock,
+            logcatEchoTracker,
+            testDispatcher,
+            testScope.backgroundScope,
+            localLogcat = localLogcat,
+        )
     }
 
     @Test
@@ -791,6 +826,112 @@
         assertThat(dumpedString).contains(evictedColumnLog3)
     }
 
+    @Test
+    fun logcat_bufferNotLoggable_tagNotLoggable_noEcho() {
+        whenever(logcatEchoTracker.isBufferLoggable(eq(NAME), any())).thenReturn(false)
+        whenever(logcatEchoTracker.isTagLoggable(eq("columnName"), any())).thenReturn(false)
+
+        underTest.logChange("prefix", "columnName", true)
+
+        assertThat(localLogcat.logs).isEmpty()
+    }
+
+    @Test
+    fun logcat_bufferIsLoggable_tagNotLoggable_echoes() {
+        whenever(logcatEchoTracker.isBufferLoggable(eq(NAME), any())).thenReturn(true)
+        whenever(logcatEchoTracker.isTagLoggable(eq("columnName"), any())).thenReturn(false)
+
+        underTest.logChange("prefix", "columnName", true)
+
+        assertThat(localLogcat.logs).hasSize(1)
+    }
+
+    @Test
+    fun logcat_bufferNotLoggable_tagIsLoggable_echoes() {
+        whenever(logcatEchoTracker.isBufferLoggable(eq(NAME), any())).thenReturn(false)
+        whenever(logcatEchoTracker.isTagLoggable(eq("columnName"), any())).thenReturn(true)
+
+        underTest.logChange("prefix", "columnName", true)
+
+        assertThat(localLogcat.logs).hasSize(1)
+    }
+
+    @Test
+    fun logcat_echoesDebugLogs_debugDisabled_noEcho() {
+        // Allow any log other than debug
+        whenever(logcatEchoTracker.isBufferLoggable(eq(NAME), any())).thenAnswer { invocation ->
+            (invocation.getArgument(1) as LogLevel) != LogLevel.DEBUG
+        }
+
+        underTest.logChange("prefix", "columnName", true)
+
+        assertThat(localLogcat.logs).isEmpty()
+    }
+
+    @Test
+    fun logcat_echoesDebugLogs_debugEnabled_echoes() {
+        // Only allow debug logs
+        whenever(logcatEchoTracker.isBufferLoggable(eq(NAME), eq(LogLevel.DEBUG))).thenReturn(true)
+
+        underTest.logChange("prefix", "columnName", true)
+
+        assertThat(localLogcat.logs).hasSize(1)
+    }
+
+    @Test
+    fun logcat_bufferNotLoggable_tagIsLoggable_usesColNameForTagCheck() {
+        systemClock.setCurrentTimeMillis(1000L)
+
+        val nonLoggingTag = "nonLoggingColName"
+        val loggingTag = "loggingColName"
+
+        whenever(logcatEchoTracker.isBufferLoggable(eq(NAME), any())).thenReturn(false)
+        whenever(logcatEchoTracker.isTagLoggable(eq(loggingTag), eq(LogLevel.DEBUG)))
+            .thenReturn(true)
+        whenever(logcatEchoTracker.isTagLoggable(eq(nonLoggingTag), eq(LogLevel.DEBUG)))
+            .thenReturn(false)
+
+        underTest.logChange("", nonLoggingTag, true)
+        underTest.logChange("", loggingTag, true)
+
+        assertThat(localLogcat.logs).hasSize(1)
+
+        val timestamp = TABLE_LOG_DATE_FORMAT.format(1000L)
+        val expectedMessage = "${timestamp}${SEPARATOR}${loggingTag}${SEPARATOR}true"
+        val expectedLine = "D $NAME: $expectedMessage"
+
+        assertThat(localLogcat.logs[0]).isEqualTo(expectedLine)
+    }
+
+    @Test
+    fun logcat_bufferLoggable_multipleMessagesAreEchoed() {
+        systemClock.setCurrentTimeMillis(1000L)
+        whenever(logcatEchoTracker.isBufferLoggable(eq(NAME), any())).thenReturn(true)
+
+        val col1 = "column1"
+        val col2 = "column2"
+
+        // Log a couple of columns that flip bits
+        underTest.logChange("", col1, true)
+        underTest.logChange("", col2, false)
+        underTest.logChange("", col1, false)
+        underTest.logChange("", col2, true)
+
+        assertThat(localLogcat.logs).hasSize(4)
+
+        val timestamp = TABLE_LOG_DATE_FORMAT.format(1000L)
+        val msg1 = "${timestamp}${SEPARATOR}${col1}${SEPARATOR}true"
+        val msg2 = "${timestamp}${SEPARATOR}${col2}${SEPARATOR}false"
+        val msg3 = "${timestamp}${SEPARATOR}${col1}${SEPARATOR}false"
+        val msg4 = "${timestamp}${SEPARATOR}${col2}${SEPARATOR}true"
+        val expected = listOf(msg1, msg2, msg3, msg4).map { "D $NAME: $it" }
+
+        // Logs use the same bg dispatcher for writing to logcat, they should be in order
+        for ((msg, logLine) in expected zip localLogcat.logs) {
+            assertThat(logLine).isEqualTo(msg)
+        }
+    }
+
     private fun dumpChanges(): String {
         underTest.dump(PrintWriter(outputWriter), arrayOf())
         return outputWriter.toString()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDeviceManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDeviceManagerTest.kt
index a45e9d9..0c57e7b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDeviceManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDeviceManagerTest.kt
@@ -468,7 +468,7 @@
     }
 
     @Test
-    fun audioInfoChanged() {
+    fun audioInfoPlaybackTypeChanged() {
         whenever(playbackInfo.getPlaybackType()).thenReturn(PlaybackInfo.PLAYBACK_TYPE_LOCAL)
         whenever(controller.getPlaybackInfo()).thenReturn(playbackInfo)
         // GIVEN a controller with local playback type
@@ -486,6 +486,25 @@
     }
 
     @Test
+    fun audioInfoVolumeControlIdChanged() {
+        whenever(playbackInfo.getPlaybackType()).thenReturn(PlaybackInfo.PLAYBACK_TYPE_LOCAL)
+        whenever(playbackInfo.getVolumeControlId()).thenReturn(null)
+        whenever(controller.getPlaybackInfo()).thenReturn(playbackInfo)
+        // GIVEN a controller with local playback type
+        manager.onMediaDataLoaded(KEY, null, mediaData)
+        fakeBgExecutor.runAllReady()
+        fakeFgExecutor.runAllReady()
+        reset(mr2)
+        // WHEN onAudioInfoChanged fires with a volume control id change
+        whenever(playbackInfo.getVolumeControlId()).thenReturn("placeholder id")
+        val captor = ArgumentCaptor.forClass(MediaController.Callback::class.java)
+        verify(controller).registerCallback(captor.capture())
+        captor.value.onAudioInfoChanged(playbackInfo)
+        // THEN the route is checked
+        verify(mr2).getRoutingSessionForMediaController(eq(controller))
+    }
+
+    @Test
     fun audioInfoHasntChanged() {
         whenever(playbackInfo.getPlaybackType()).thenReturn(PlaybackInfo.PLAYBACK_TYPE_REMOTE)
         whenever(controller.getPlaybackInfo()).thenReturn(playbackInfo)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt
index 3ec9690..bde05b9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt
@@ -53,6 +53,7 @@
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
 import org.junit.After
 import org.junit.Before
 import org.junit.Test
@@ -92,13 +93,15 @@
     private val mobileMappings = FakeMobileMappingsProxy()
     private val subscriptionManagerProxy = FakeSubscriptionManagerProxy()
 
+    private val testDispatcher = UnconfinedTestDispatcher()
     private val scope = CoroutineScope(IMMEDIATE)
 
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
 
-        logFactory = TableLogBufferFactory(dumpManager, FakeSystemClock())
+        logFactory =
+            TableLogBufferFactory(dumpManager, FakeSystemClock(), mock(), testDispatcher, scope)
 
         // Never start in demo mode
         whenever(demoModeController.isInDemoMode).thenReturn(false)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionParameterizedTest.kt
index 37fac34..7573b28 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionParameterizedTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionParameterizedTest.kt
@@ -61,11 +61,18 @@
 internal class DemoMobileConnectionParameterizedTest(private val testCase: TestCase) :
     SysuiTestCase() {
 
-    private val logFactory = TableLogBufferFactory(mock(), FakeSystemClock())
-
     private val testDispatcher = UnconfinedTestDispatcher()
     private val testScope = TestScope(testDispatcher)
 
+    private val logFactory =
+        TableLogBufferFactory(
+            mock(),
+            FakeSystemClock(),
+            mock(),
+            testDispatcher,
+            testScope.backgroundScope,
+        )
+
     private val fakeNetworkEventFlow = MutableStateFlow<FakeNetworkEventModel?>(null)
     private val fakeWifiEventFlow = MutableStateFlow<FakeWifiEventModel?>(null)
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepositoryTest.kt
index 1251dfa..efaf152 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepositoryTest.kt
@@ -54,13 +54,20 @@
 @SmallTest
 class DemoMobileConnectionsRepositoryTest : SysuiTestCase() {
     private val dumpManager: DumpManager = mock()
-    private val logFactory = TableLogBufferFactory(dumpManager, FakeSystemClock())
 
     private val testDispatcher = UnconfinedTestDispatcher()
     private val testScope = TestScope(testDispatcher)
 
     private val fakeNetworkEventFlow = MutableStateFlow<FakeNetworkEventModel?>(null)
     private val fakeWifiEventFlow = MutableStateFlow<FakeWifiEventModel?>(null)
+    private val logFactory =
+        TableLogBufferFactory(
+            dumpManager,
+            FakeSystemClock(),
+            mock(),
+            testDispatcher,
+            testScope.backgroundScope,
+        )
 
     private lateinit var underTest: DemoMobileConnectionsRepository
     private lateinit var mobileDataSource: DemoModeMobileConnectionDataSource
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt
index 9f77744..b701fbd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt
@@ -66,7 +66,15 @@
     private val systemClock = FakeSystemClock()
     private val testDispatcher = UnconfinedTestDispatcher()
     private val testScope = TestScope(testDispatcher)
-    private val tableLogBuffer = TableLogBuffer(maxSize = 100, name = "TestName", systemClock)
+    private val tableLogBuffer =
+        TableLogBuffer(
+            maxSize = 100,
+            name = "TestName",
+            systemClock,
+            mock(),
+            testDispatcher,
+            testScope.backgroundScope,
+        )
     private val mobileFactory = mock<MobileConnectionRepositoryImpl.Factory>()
     private val carrierMergedFactory = mock<CarrierMergedConnectionRepository.Factory>()
 
@@ -294,7 +302,14 @@
     @Test
     fun factory_reusesLogBuffersForSameConnection() =
         testScope.runTest {
-            val realLoggerFactory = TableLogBufferFactory(mock(), FakeSystemClock())
+            val realLoggerFactory =
+                TableLogBufferFactory(
+                    mock(),
+                    FakeSystemClock(),
+                    mock(),
+                    testDispatcher,
+                    testScope.backgroundScope,
+                )
 
             val factory =
                 FullMobileConnectionRepository.Factory(
@@ -329,7 +344,14 @@
     @Test
     fun factory_reusesLogBuffersForSameSubIDevenIfCarrierMerged() =
         testScope.runTest {
-            val realLoggerFactory = TableLogBufferFactory(mock(), FakeSystemClock())
+            val realLoggerFactory =
+                TableLogBufferFactory(
+                    mock(),
+                    FakeSystemClock(),
+                    mock(),
+                    testDispatcher,
+                    testScope.backgroundScope,
+                )
 
             val factory =
                 FullMobileConnectionRepository.Factory(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt
index c84c9c0..6e1ab58 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt
@@ -61,6 +61,16 @@
     private val testDispatcher = UnconfinedTestDispatcher()
     private val testScope = TestScope(testDispatcher)
 
+    private val tableLogBuffer =
+        TableLogBuffer(
+            8,
+            "MobileIconsInteractorTest",
+            FakeSystemClock(),
+            mock(),
+            testDispatcher,
+            testScope.backgroundScope,
+        )
+
     @Mock private lateinit var carrierConfigTracker: CarrierConfigTracker
 
     @Before
@@ -687,16 +697,14 @@
     }
 
     companion object {
-        private val tableLogBuffer =
-            TableLogBuffer(8, "MobileIconsInteractorTest", FakeSystemClock())
 
         private const val SUB_1_ID = 1
         private val SUB_1 = SubscriptionModel(subscriptionId = SUB_1_ID)
-        private val CONNECTION_1 = FakeMobileConnectionRepository(SUB_1_ID, tableLogBuffer)
+        private val CONNECTION_1 = FakeMobileConnectionRepository(SUB_1_ID, mock())
 
         private const val SUB_2_ID = 2
         private val SUB_2 = SubscriptionModel(subscriptionId = SUB_2_ID)
-        private val CONNECTION_2 = FakeMobileConnectionRepository(SUB_2_ID, tableLogBuffer)
+        private val CONNECTION_2 = FakeMobileConnectionRepository(SUB_2_ID, mock())
 
         private const val SUB_3_ID = 3
         private val SUB_3_OPP =
@@ -705,7 +713,7 @@
                 isOpportunistic = true,
                 groupUuid = ParcelUuid(UUID.randomUUID()),
             )
-        private val CONNECTION_3 = FakeMobileConnectionRepository(SUB_3_ID, tableLogBuffer)
+        private val CONNECTION_3 = FakeMobileConnectionRepository(SUB_3_ID, mock())
 
         private const val SUB_4_ID = 4
         private val SUB_4_OPP =
@@ -714,6 +722,6 @@
                 isOpportunistic = true,
                 groupUuid = ParcelUuid(UUID.randomUUID()),
             )
-        private val CONNECTION_4 = FakeMobileConnectionRepository(SUB_4_ID, tableLogBuffer)
+        private val CONNECTION_4 = FakeMobileConnectionRepository(SUB_4_ID, mock())
     }
 }
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index a3b4a0f..a510d16 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -490,7 +490,7 @@
                 mMainHandler, context,
                 new PolicyWarningUIController.NotificationController(context));
         mSecurityPolicy = new AccessibilitySecurityPolicy(policyWarningUIController, mContext,
-                this);
+                this, LocalServices.getService(PackageManagerInternal.class));
         mA11yWindowManager = new AccessibilityWindowManager(mLock, mMainHandler,
                 mWindowManagerService, this, mSecurityPolicy, this, mTraceManager);
         mA11yDisplayListener = new AccessibilityDisplayListener(mContext, mMainHandler);
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java b/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java
index 8865623..65c1873 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java
@@ -29,6 +29,7 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
 import android.content.pm.UserInfo;
@@ -99,6 +100,7 @@
 
     private final Context mContext;
     private final PackageManager mPackageManager;
+    private final PackageManagerInternal mPackageManagerInternal;
     private final UserManager mUserManager;
     private final AppOpsManager mAppOpsManager;
     private final AccessibilityUserManager mAccessibilityUserManager;
@@ -116,10 +118,12 @@
      */
     public AccessibilitySecurityPolicy(PolicyWarningUIController policyWarningUIController,
             @NonNull Context context,
-            @NonNull AccessibilityUserManager a11yUserManager) {
+            @NonNull AccessibilityUserManager a11yUserManager,
+            @NonNull PackageManagerInternal packageManagerInternal) {
         mContext = context;
         mAccessibilityUserManager = a11yUserManager;
         mPackageManager = mContext.getPackageManager();
+        mPackageManagerInternal = packageManagerInternal;
         mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
         mAppOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
         mPolicyWarningUIController = policyWarningUIController;
@@ -513,12 +517,7 @@
     private boolean isValidPackageForUid(String packageName, int uid) {
         final long token = Binder.clearCallingIdentity();
         try {
-            // Since we treat calls from a profile as if made by its parent, using
-            // MATCH_ANY_USER to query the uid of the given package name.
-            return uid == mPackageManager.getPackageUidAsUser(
-                    packageName, PackageManager.MATCH_ANY_USER, UserHandle.getUserId(uid));
-        } catch (PackageManager.NameNotFoundException e) {
-            return false;
+            return mPackageManagerInternal.isSameApp(packageName, uid, UserHandle.getUserId(uid));
         } finally {
             Binder.restoreCallingIdentity(token);
         }
diff --git a/services/accessibility/java/com/android/server/accessibility/SystemActionPerformer.java b/services/accessibility/java/com/android/server/accessibility/SystemActionPerformer.java
index a13df47..9747579 100644
--- a/services/accessibility/java/com/android/server/accessibility/SystemActionPerformer.java
+++ b/services/accessibility/java/com/android/server/accessibility/SystemActionPerformer.java
@@ -36,6 +36,7 @@
 import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
 
 import com.android.internal.R;
+import com.android.internal.accessibility.util.AccessibilityUtils;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.ScreenshotHelper;
@@ -302,8 +303,10 @@
                 case AccessibilityService.GLOBAL_ACTION_TAKE_SCREENSHOT:
                     return takeScreenshot();
                 case AccessibilityService.GLOBAL_ACTION_KEYCODE_HEADSETHOOK:
-                    sendDownAndUpKeyEvents(KeyEvent.KEYCODE_HEADSETHOOK,
-                            InputDevice.SOURCE_KEYBOARD);
+                    if (!AccessibilityUtils.interceptHeadsetHookForActiveCall(mContext)) {
+                        sendDownAndUpKeyEvents(KeyEvent.KEYCODE_HEADSETHOOK,
+                                InputDevice.SOURCE_KEYBOARD);
+                    }
                     return true;
                 case AccessibilityService.GLOBAL_ACTION_DPAD_UP:
                     sendDownAndUpKeyEvents(KeyEvent.KEYCODE_DPAD_UP,
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index 402fb30..9b1a80be 100644
--- a/services/core/java/com/android/server/pm/LauncherAppsService.java
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -1120,12 +1120,12 @@
                     return shortcutOverridesInfo;
                 }
 
-                List<String> packagesToOverride =
+                Map<String, String> packagesToOverride =
                         DevicePolicyCache.getInstance().getLauncherShortcutOverrides();
-                for (String packageName : packagesToOverride) {
+                for (Map.Entry<String, String> packageNames : packagesToOverride.entrySet()) {
                     Intent intent = new Intent(Intent.ACTION_MAIN)
                             .addCategory(Intent.CATEGORY_LAUNCHER)
-                            .setPackage(packageName);
+                            .setPackage(packageNames.getValue());
 
                     List<LauncherActivityInfoInternal> possibleShortcutOverrides =
                             queryIntentLauncherActivities(
@@ -1135,7 +1135,8 @@
                             );
 
                     if (!possibleShortcutOverrides.isEmpty()) {
-                        shortcutOverridesInfo.put(packageName, possibleShortcutOverrides.get(0));
+                        shortcutOverridesInfo.put(packageNames.getKey(),
+                                possibleShortcutOverrides.get(0));
                     }
                 }
                 return shortcutOverridesInfo;
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 51872b3..98d2d3d 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -803,6 +803,20 @@
     protected WallpaperData mLastLockWallpaper;
     private IWallpaperManagerCallback mKeyguardListener;
     private boolean mWaitingForUnlock;
+
+    /**
+     * Flag set to true after reboot if the home wallpaper is waiting for the device to be unlocked.
+     * This happens for wallpapers that are not direct-boot aware; they can only be rendered after
+     * the user unlocks the device for the first time after a reboot. In the meantime, the default
+     * wallpaper is shown instead.
+     */
+    private boolean mHomeWallpaperWaitingForUnlock;
+
+    /**
+     * Flag set to true after reboot if the lock wallpaper is waiting for the device to be unlocked.
+     */
+    private boolean mLockWallpaperWaitingForUnlock;
+
     private boolean mShuttingDown;
 
     /**
@@ -1790,7 +1804,23 @@
     public void onUnlockUser(final int userId) {
         synchronized (mLock) {
             if (mCurrentUserId == userId) {
-                if (mWaitingForUnlock) {
+                if (mIsLockscreenLiveWallpaperEnabled) {
+                    if (mHomeWallpaperWaitingForUnlock) {
+                        final WallpaperData systemWallpaper =
+                                getWallpaperSafeLocked(userId, FLAG_SYSTEM);
+                        switchWallpaper(systemWallpaper, null);
+                        // TODO(b/278261563): call notifyCallbacksLocked inside switchWallpaper
+                        notifyCallbacksLocked(systemWallpaper);
+                    }
+                    if (mLockWallpaperWaitingForUnlock) {
+                        final WallpaperData lockWallpaper =
+                                getWallpaperSafeLocked(userId, FLAG_LOCK);
+                        switchWallpaper(lockWallpaper, null);
+                        notifyCallbacksLocked(lockWallpaper);
+                    }
+                }
+
+                if (mWaitingForUnlock && !mIsLockscreenLiveWallpaperEnabled) {
                     // the desired wallpaper is not direct-boot aware, load it now
                     final WallpaperData systemWallpaper =
                             getWallpaperSafeLocked(userId, FLAG_SYSTEM);
@@ -1845,13 +1875,23 @@
                 }
                 mCurrentUserId = userId;
                 systemWallpaper = getWallpaperSafeLocked(userId, FLAG_SYSTEM);
-                final WallpaperData tmpLockWallpaper = mLockWallpaperMap.get(userId);
-                lockWallpaper = tmpLockWallpaper == null ? systemWallpaper : tmpLockWallpaper;
+
+                if (mIsLockscreenLiveWallpaperEnabled) {
+                    lockWallpaper = systemWallpaper.mWhich == (FLAG_LOCK | FLAG_SYSTEM)
+                            ? systemWallpaper : getWallpaperSafeLocked(userId, FLAG_LOCK);
+                } else {
+                    final WallpaperData tmpLockWallpaper = mLockWallpaperMap.get(userId);
+                    lockWallpaper = tmpLockWallpaper == null ? systemWallpaper : tmpLockWallpaper;
+                }
+
                 // Not started watching yet, in case wallpaper data was loaded for other reasons.
                 if (systemWallpaper.wallpaperObserver == null) {
                     systemWallpaper.wallpaperObserver = new WallpaperObserver(systemWallpaper);
                     systemWallpaper.wallpaperObserver.startWatching();
                 }
+                if (mIsLockscreenLiveWallpaperEnabled && lockWallpaper != systemWallpaper)  {
+                    switchWallpaper(lockWallpaper, null);
+                }
                 switchWallpaper(systemWallpaper, reply);
             }
 
@@ -1870,6 +1910,11 @@
     void switchWallpaper(WallpaperData wallpaper, IRemoteCallback reply) {
         synchronized (mLock) {
             mWaitingForUnlock = false;
+            if (mIsLockscreenLiveWallpaperEnabled) {
+                if ((wallpaper.mWhich & FLAG_SYSTEM) != 0) mHomeWallpaperWaitingForUnlock = false;
+                if ((wallpaper.mWhich & FLAG_LOCK) != 0) mLockWallpaperWaitingForUnlock = false;
+            }
+
             final ComponentName cname = wallpaper.wallpaperComponent != null ?
                     wallpaper.wallpaperComponent : wallpaper.nextWallpaperComponent;
             if (!bindWallpaperComponentLocked(cname, true, false, wallpaper, reply)) {
@@ -1882,6 +1927,11 @@
                 } catch (RemoteException ignored) {
                 }
 
+                if (mIsLockscreenLiveWallpaperEnabled) {
+                    onSwitchWallpaperFailLocked(wallpaper, reply, si);
+                    return;
+                }
+
                 if (si == null) {
                     Slog.w(TAG, "Failure starting previous wallpaper; clearing");
                     clearWallpaperLocked(false, FLAG_SYSTEM, wallpaper.userId, reply);
@@ -1899,6 +1949,43 @@
         }
     }
 
+    /**
+     * Fallback method if a wallpaper fails to load on boot or after a user switch.
+     * Only called if mIsLockscreenLiveWallpaperEnabled is true.
+     */
+    private void onSwitchWallpaperFailLocked(
+            WallpaperData wallpaper, IRemoteCallback reply, ServiceInfo serviceInfo) {
+
+        if (serviceInfo == null) {
+            Slog.w(TAG, "Failure starting previous wallpaper; clearing");
+
+            if (wallpaper.mWhich == (FLAG_LOCK | FLAG_SYSTEM)) {
+                clearWallpaperLocked(false, FLAG_SYSTEM, wallpaper.userId, null);
+                clearWallpaperLocked(false, FLAG_LOCK, wallpaper.userId, reply);
+            } else {
+                clearWallpaperLocked(false, wallpaper.mWhich, wallpaper.userId, reply);
+            }
+            return;
+        }
+        Slog.w(TAG, "Wallpaper isn't direct boot aware; using fallback until unlocked");
+        // We might end up persisting the current wallpaper data
+        // while locked, so pretend like the component was actually
+        // bound into place
+        wallpaper.wallpaperComponent = wallpaper.nextWallpaperComponent;
+        final WallpaperData fallback = new WallpaperData(wallpaper.userId, wallpaper.mWhich);
+
+        // files from the previous static wallpaper may still be stored in memory.
+        // delete them in order to show the default wallpaper.
+        if (wallpaper.wallpaperFile.exists()) {
+            wallpaper.wallpaperFile.delete();
+            wallpaper.cropFile.delete();
+        }
+
+        bindWallpaperComponentLocked(mImageWallpaper, true, false, fallback, reply);
+        if ((wallpaper.mWhich & FLAG_SYSTEM) != 0) mHomeWallpaperWaitingForUnlock = true;
+        if ((wallpaper.mWhich & FLAG_LOCK) != 0) mLockWallpaperWaitingForUnlock = true;
+    }
+
     @Override
     public void clearWallpaper(String callingPackage, int which, int userId) {
         if (DEBUG) Slog.v(TAG, "clearWallpaper");
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index f843b7c..78c066b 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -5296,6 +5296,10 @@
             if (isCollecting) {
                 mTransitionController.collect(this);
             } else {
+                // Failsafe to make sure that we show any activities that were incorrectly hidden
+                // during a transition. If this vis-change is a result of finishing, ignore it.
+                // Finish should only ever commit visibility=false, so we can check full containment
+                // rather than just direct membership.
                 inFinishingTransition = mTransitionController.inFinishingTransition(this);
                 if (!inFinishingTransition && !mDisplayContent.isSleeping()) {
                     Slog.e(TAG, "setVisibility=" + visible
diff --git a/services/core/java/com/android/server/wm/RemoteAnimationController.java b/services/core/java/com/android/server/wm/RemoteAnimationController.java
index b879d1a..eb639b6 100644
--- a/services/core/java/com/android/server/wm/RemoteAnimationController.java
+++ b/services/core/java/com/android/server/wm/RemoteAnimationController.java
@@ -362,15 +362,8 @@
 
     private void invokeAnimationCancelled(String reason) {
         ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS, "cancelAnimation(): reason=%s", reason);
-        final boolean isKeyguardOccluded = mDisplayContent.isKeyguardOccluded();
-
         try {
-            EventLogTags.writeWmSetKeyguardOccluded(
-                    isKeyguardOccluded ? 1 : 0,
-                    0 /* animate */,
-                    0 /* transit */,
-                    "onAnimationCancelled");
-            mRemoteAnimationAdapter.getRunner().onAnimationCancelled(isKeyguardOccluded);
+            mRemoteAnimationAdapter.getRunner().onAnimationCancelled();
         } catch (RemoteException e) {
             Slog.e(TAG, "Failed to notify cancel", e);
         }
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index abc9f8a..d531ad1 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -244,6 +244,16 @@
     private IContainerFreezer mContainerFreezer = null;
     private final SurfaceControl.Transaction mTmpTransaction = new SurfaceControl.Transaction();
 
+    /**
+     * {@code true} if some other operation may have caused the originally-recorded state (in
+     * mChanges) to be dirty. This is usually due to finishTransition being called mid-collect;
+     * and, the reason that finish can alter the "start" state of other transitions is because
+     * setVisible(false) is deferred until then.
+     * Instead of adding this conditional, we could re-check always; but, this situation isn't
+     * common so it'd be wasted work.
+     */
+    boolean mPriorVisibilityMightBeDirty = false;
+
     final TransitionController.Logger mLogger = new TransitionController.Logger();
 
     /** Whether this transition was forced to play early (eg for a SLEEP signal). */
@@ -966,28 +976,30 @@
         mController.mFinishingTransition = this;
 
         if (mTransientHideTasks != null && !mTransientHideTasks.isEmpty()) {
-            // Record all the now-hiding activities so that they are committed after
-            // recalculating visibilities. We just use mParticipants because we can and it will
-            // ensure proper reporting of `isInFinishTransition`.
-            for (int i = 0; i < mTransientHideTasks.size(); ++i) {
-                mTransientHideTasks.get(i).forAllActivities(r -> {
-                    // Only check leaf-tasks that were collected
-                    if (!mParticipants.contains(r.getTask())) return;
-                    // Only concern ourselves with anything that can become invisible
-                    if (!r.isVisible()) return;
-                    mParticipants.add(r);
-                });
-            }
             // The transient hide tasks could be occluded now, e.g. returning to home. So trigger
             // the update to make the activities in the tasks invisible-requested, then the next
             // step can continue to commit the visibility.
             mController.mAtm.mRootWindowContainer.ensureActivitiesVisible(null /* starting */,
                     0 /* configChanges */, true /* preserveWindows */);
+            // Record all the now-hiding activities so that they are committed. Just use
+            // mParticipants because we can avoid a new list this way.
+            for (int i = 0; i < mTransientHideTasks.size(); ++i) {
+                // Only worry about tasks that were actually hidden. Otherwise, we could end-up
+                // committing visibility for activity-level changes that aren't part of this
+                // transition.
+                if (mTransientHideTasks.get(i).isVisibleRequested()) continue;
+                mTransientHideTasks.get(i).forAllActivities(r -> {
+                    // Only check leaf-tasks that were collected
+                    if (!mParticipants.contains(r.getTask())) return;
+                    mParticipants.add(r);
+                });
+            }
         }
 
         boolean hasParticipatedDisplay = false;
         boolean hasVisibleTransientLaunch = false;
         boolean enterAutoPip = false;
+        boolean committedSomeInvisible = false;
         // Commit all going-invisible containers
         for (int i = 0; i < mParticipants.size(); ++i) {
             final WindowContainer<?> participant = mParticipants.valueAt(i);
@@ -1023,6 +1035,7 @@
                         }
                         ar.commitVisibility(false /* visible */, false /* performLayout */,
                                 true /* fromTransition */);
+                        committedSomeInvisible = true;
                     } else {
                         enterAutoPip = true;
                     }
@@ -1081,6 +1094,9 @@
                 }
             }
         }
+        if (committedSomeInvisible) {
+            mController.onCommittedInvisibles();
+        }
 
         if (hasVisibleTransientLaunch) {
             // Notify the change about the transient-below task if entering auto-pip.
@@ -1293,6 +1309,9 @@
         // leftover order changes.
         collectOrderChanges(mController.mWaitingTransitions.isEmpty());
 
+        if (mPriorVisibilityMightBeDirty) {
+            updatePriorVisibility();
+        }
         // Resolve the animating targets from the participants.
         mTargets = calculateTargets(mParticipants, mChanges);
         // Check whether the participants were animated from back navigation.
@@ -1806,6 +1825,20 @@
         }
     }
 
+    private void updatePriorVisibility() {
+        for (int i = 0; i < mChanges.size(); ++i) {
+            final ChangeInfo chg = mChanges.valueAt(i);
+            // For task/activity, recalculate the current "real" visibility.
+            if (chg.mContainer.asActivityRecord() == null && chg.mContainer.asTask() == null) {
+                continue;
+            }
+            // This ONLY works in the visible -> invisible case (and is only needed for this case)
+            // because commitVisible(false) is deferred until finish.
+            if (!chg.mVisible) continue;
+            chg.mVisible = chg.mContainer.isVisible();
+        }
+    }
+
     /**
      * Under some conditions (eg. all visible targets within a parent container are transitioning
      * the same way) the transition can be "promoted" to the parent container. This means an
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index 7972637..7950eda 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -408,9 +408,9 @@
         return false;
     }
 
-    /** Returns {@code true} if the `wc` is a participant of the finishing transition. */
+    /** Returns {@code true} if the finishing transition contains `wc`. */
     boolean inFinishingTransition(WindowContainer<?> wc) {
-        return mFinishingTransition != null && mFinishingTransition.mParticipants.contains(wc);
+        return mFinishingTransition != null && mFinishingTransition.isInTransition(wc);
     }
 
     /** @return {@code true} if a transition is running */
@@ -827,6 +827,16 @@
         }
     }
 
+    /** Called by {@link Transition#finishTransition} if it committed invisible to any activities */
+    void onCommittedInvisibles() {
+        if (mCollectingTransition != null) {
+            mCollectingTransition.mPriorVisibilityMightBeDirty = true;
+        }
+        for (int i = mWaitingTransitions.size() - 1; i >= 0; --i) {
+            mWaitingTransitions.get(i).mPriorVisibilityMightBeDirty = true;
+        }
+    }
+
     private void validateStates() {
         for (int i = 0; i < mStateValidators.size(); ++i) {
             mStateValidators.get(i).run();
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index f4a1765d..510e675 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -3839,7 +3839,8 @@
             // This can still happen if WMCore starts a new transition when there is ongoing
             // sync transaction from Shell. Please file a bug if it happens.
             throw new IllegalStateException("Can't sync on 2 groups simultaneously"
-                    + " currentSyncId=" + mSyncGroup.mSyncId + " newSyncId=" + group.mSyncId);
+                    + " currentSyncId=" + mSyncGroup.mSyncId + " newSyncId=" + group.mSyncId
+                    + " wc=" + this);
         }
         mSyncGroup = group;
     }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyCacheImpl.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyCacheImpl.java
index 80100a9..c681b88 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyCacheImpl.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyCacheImpl.java
@@ -19,13 +19,13 @@
 import android.app.admin.DevicePolicyCache;
 import android.app.admin.DevicePolicyManager;
 import android.os.UserHandle;
+import android.util.ArrayMap;
 import android.util.IndentingPrintWriter;
 import android.util.SparseIntArray;
 
 import com.android.internal.annotations.GuardedBy;
 
-import java.util.ArrayList;
-import java.util.List;
+import java.util.Map;
 import java.util.concurrent.atomic.AtomicBoolean;
 
 /**
@@ -55,8 +55,7 @@
     private final SparseIntArray mPermissionPolicy = new SparseIntArray();
 
     @GuardedBy("mLock")
-    private List<String> mLauncherShortcutOverrides =
-            new ArrayList<>();
+    private ArrayMap<String, String> mLauncherShortcutOverrides = new ArrayMap<>();
 
 
     /** Maps to {@code ActiveAdmin.mAdminCanGrantSensorsPermissions}. */
@@ -130,18 +129,20 @@
     }
 
     @Override
-    public List<String> getLauncherShortcutOverrides() {
+    public Map<String, String> getLauncherShortcutOverrides() {
         synchronized (mLock) {
-            return new ArrayList<>(mLauncherShortcutOverrides);
+            return new ArrayMap<>(mLauncherShortcutOverrides);
         }
     }
 
     /**
-     * Sets a list of packages for which shortcuts should be replaced by their badged version.
+     * Sets a map of packages names to package names, for which all launcher shortcuts which
+     * match a key package name should be modified to launch the corresponding value package
+     * name in the managed profile. The overridden shortcut should be badged accordingly.
      */
-    public void setLauncherShortcutOverrides(List<String> launcherShortcutOverrides) {
+    public void setLauncherShortcutOverrides(ArrayMap<String, String> launcherShortcutOverrides) {
         synchronized (mLock) {
-            mLauncherShortcutOverrides = new ArrayList<>(launcherShortcutOverrides);
+            mLauncherShortcutOverrides = new ArrayMap<>(launcherShortcutOverrides);
         }
     }
 
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 22f684b..7d661eb 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -7721,7 +7721,7 @@
     }
 
     private void clearLauncherShortcutOverrides() {
-        mPolicyCache.setLauncherShortcutOverrides(new ArrayList<>());
+        mPolicyCache.setLauncherShortcutOverrides(new ArrayMap<>());
     }
 
     private void updateTelephonyCrossProfileIntentFilters(int parentUserId, int profileUserId,
@@ -21613,7 +21613,7 @@
         }
         copyAccount(targetUser, sourceUser, accountToMigrate, callerPackage);
         if (!keepAccountMigrated) {
-            removeAccount(accountToMigrate);
+            removeAccount(accountToMigrate, sourceUserId);
         }
     }
 
@@ -21657,9 +21657,10 @@
                 .write();
     }
 
-    private void removeAccount(Account account) {
-        final AccountManager accountManager =
-                mContext.getSystemService(AccountManager.class);
+    private void removeAccount(Account account, @UserIdInt int sourceUserId) {
+        final AccountManager accountManager = mContext.createContextAsUser(
+                        UserHandle.of(sourceUserId), /* flags= */ 0)
+                .getSystemService(AccountManager.class);
         final AccountManagerFuture<Bundle> bundle = accountManager.removeAccount(account,
                 null, null /* callback */, null /* handler */);
         try {
@@ -23720,15 +23721,14 @@
 
     private void updateDialerAndSmsManagedShortcutsOverrideCache(
             String defaultDialerPackageName, String defaultSmsPackageName) {
-
-        List<String> shortcutOverrides = new ArrayList<>();
+        ArrayMap<String, String> shortcutOverrides = new ArrayMap<>();
 
         if (defaultDialerPackageName != null) {
-            shortcutOverrides.add(defaultDialerPackageName);
+            shortcutOverrides.put(defaultDialerPackageName, defaultDialerPackageName);
         }
 
         if (defaultSmsPackageName != null) {
-            shortcutOverrides.add(defaultSmsPackageName);
+            shortcutOverrides.put(defaultSmsPackageName, defaultSmsPackageName);
         }
         mPolicyCache.setLauncherShortcutOverrides(shortcutOverrides);
     }
diff --git a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
index a5adf3f..f1d4de9 100644
--- a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
@@ -54,6 +54,7 @@
 import static com.android.server.SystemTimeZone.TIME_ZONE_CONFIDENCE_HIGH;
 import static com.android.server.alarm.Alarm.EXACT_ALLOW_REASON_ALLOW_LIST;
 import static com.android.server.alarm.Alarm.EXACT_ALLOW_REASON_COMPAT;
+import static com.android.server.alarm.Alarm.EXACT_ALLOW_REASON_LISTENER;
 import static com.android.server.alarm.Alarm.EXACT_ALLOW_REASON_NOT_APPLICABLE;
 import static com.android.server.alarm.Alarm.EXACT_ALLOW_REASON_PERMISSION;
 import static com.android.server.alarm.Alarm.EXACT_ALLOW_REASON_POLICY_PERMISSION;
@@ -2728,6 +2729,66 @@
     }
 
     @Test
+    public void exactListenerBinderCallWithoutPermissionWithoutAllowlist() throws RemoteException {
+        mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true);
+        mockChangeEnabled(AlarmManager.ENABLE_USE_EXACT_ALARM, true);
+
+        mockScheduleExactAlarmState(false);
+        mockUseExactAlarmState(false);
+        when(mDeviceIdleInternal.isAppOnWhitelist(anyInt())).thenReturn(false);
+
+        final IAlarmListener listener = getNewListener(() -> {});
+        mBinder.set(TEST_CALLING_PACKAGE, ELAPSED_REALTIME_WAKEUP, 1234, WINDOW_EXACT, 0,
+                0, null, listener, "test-tag", null, null);
+
+        verify(mService, never()).hasUseExactAlarmInternal(TEST_CALLING_PACKAGE, TEST_CALLING_UID);
+        verify(mService, never()).hasScheduleExactAlarmInternal(TEST_CALLING_PACKAGE,
+                TEST_CALLING_UID);
+        verify(mDeviceIdleInternal, never()).isAppOnWhitelist(anyInt());
+
+        final ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
+        verify(mService).setImpl(eq(ELAPSED_REALTIME_WAKEUP), eq(1234L), eq(WINDOW_EXACT), eq(0L),
+                isNull(), eq(listener), eq("test-tag"), eq(FLAG_STANDALONE), isNull(), isNull(),
+                eq(TEST_CALLING_UID), eq(TEST_CALLING_PACKAGE), bundleCaptor.capture(),
+                eq(EXACT_ALLOW_REASON_LISTENER));
+
+        final BroadcastOptions idleOptions = new BroadcastOptions(bundleCaptor.getValue());
+        final int type = idleOptions.getTemporaryAppAllowlistType();
+        assertEquals(TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED, type);
+    }
+
+    @Test
+    public void exactAllowWhileIdleListenerBinderCallWithoutPermissionWithoutAllowlist()
+            throws RemoteException {
+        mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true);
+        mockChangeEnabled(AlarmManager.ENABLE_USE_EXACT_ALARM, true);
+
+        mockScheduleExactAlarmState(false);
+        mockUseExactAlarmState(false);
+        when(mDeviceIdleInternal.isAppOnWhitelist(anyInt())).thenReturn(false);
+
+        final IAlarmListener listener = getNewListener(() -> {});
+        mBinder.set(TEST_CALLING_PACKAGE, ELAPSED_REALTIME_WAKEUP, 1234, WINDOW_EXACT, 0,
+                FLAG_ALLOW_WHILE_IDLE, null, listener, "test-tag", null, null);
+
+        verify(mService, never()).hasUseExactAlarmInternal(TEST_CALLING_PACKAGE, TEST_CALLING_UID);
+        verify(mService, never()).hasScheduleExactAlarmInternal(TEST_CALLING_PACKAGE,
+                TEST_CALLING_UID);
+        verify(mDeviceIdleInternal, never()).isAppOnWhitelist(anyInt());
+
+        final ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
+        verify(mService).setImpl(eq(ELAPSED_REALTIME_WAKEUP), eq(1234L), eq(WINDOW_EXACT), eq(0L),
+                isNull(), eq(listener), eq("test-tag"),
+                eq(FLAG_STANDALONE | FLAG_ALLOW_WHILE_IDLE_COMPAT), isNull(), isNull(),
+                eq(TEST_CALLING_UID), eq(TEST_CALLING_PACKAGE), bundleCaptor.capture(),
+                eq(EXACT_ALLOW_REASON_LISTENER));
+
+        final BroadcastOptions idleOptions = new BroadcastOptions(bundleCaptor.getValue());
+        final int type = idleOptions.getTemporaryAppAllowlistType();
+        assertEquals(TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED, type);
+    }
+
+    @Test
     public void inexactAllowWhileIdleBinderCall() throws RemoteException {
         // Both permission and power exemption status don't matter for these alarms.
         // We only want to test that the flags and idleOptions are correct.
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilitySecurityPolicyTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilitySecurityPolicyTest.java
index eb6670e..8f0d014 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilitySecurityPolicyTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilitySecurityPolicyTest.java
@@ -146,7 +146,7 @@
     @Mock
     private PolicyWarningUIController mPolicyWarningUIController;
     @Mock
-    private PackageManagerInternal mPackageManagerInternal;
+    private PackageManagerInternal mMockPackageManagerInternal;
 
     @Before
     public void setUp() {
@@ -158,7 +158,8 @@
                 R.dimen.accessibility_focus_highlight_stroke_width, 1);
 
         mA11ySecurityPolicy = new AccessibilitySecurityPolicy(
-                mPolicyWarningUIController, mContext, mMockA11yUserManager);
+                mPolicyWarningUIController, mContext, mMockA11yUserManager,
+                mMockPackageManagerInternal);
         mA11ySecurityPolicy.setSendingNonA11yToolNotificationLocked(true);
         mA11ySecurityPolicy.setAccessibilityWindowManager(mMockA11yWindowManager);
         mA11ySecurityPolicy.setAppWidgetManager(mMockAppWidgetManager);
@@ -237,8 +238,8 @@
     @Test
     public void resolveValidReportedPackage_uidAndPkgNameMatched_returnPkgName()
             throws PackageManager.NameNotFoundException {
-        when(mMockPackageManager.getPackageUidAsUser(PACKAGE_NAME,
-                PackageManager.MATCH_ANY_USER, TEST_USER_ID)).thenReturn(APP_UID);
+        when(mMockPackageManagerInternal.isSameApp(PACKAGE_NAME, APP_UID, TEST_USER_ID))
+                .thenReturn(true);
 
         assertEquals(mA11ySecurityPolicy.resolveValidReportedPackageLocked(
                 PACKAGE_NAME, APP_UID, TEST_USER_ID, APP_PID),
@@ -257,8 +258,8 @@
 
         when(mMockAppWidgetManager.getHostedWidgetPackages(widgetHostUid))
                 .thenReturn(widgetPackages);
-        when(mMockPackageManager.getPackageUidAsUser(hostPackageName, TEST_USER_ID))
-                .thenReturn(widgetHostUid);
+        when(mMockPackageManagerInternal.isSameApp(hostPackageName, widgetHostUid, TEST_USER_ID))
+                .thenReturn(true);
 
         assertEquals(mA11ySecurityPolicy.resolveValidReportedPackageLocked(
                 widgetPackageName, widgetHostUid, TEST_USER_ID, widgetHostPid),
@@ -272,8 +273,8 @@
         final String[] uidPackages = {PACKAGE_NAME, PACKAGE_NAME2};
         when(mMockPackageManager.getPackagesForUid(APP_UID))
                 .thenReturn(uidPackages);
-        when(mMockPackageManager.getPackageUidAsUser(invalidPackageName, TEST_USER_ID))
-                .thenThrow(PackageManager.NameNotFoundException.class);
+        when(mMockPackageManagerInternal.isSameApp(invalidPackageName, APP_UID, TEST_USER_ID))
+                .thenReturn(false);
         when(mMockAppWidgetManager.getHostedWidgetPackages(APP_UID))
                 .thenReturn(new ArraySet<>());
         mContext.getTestablePermissions().setPermission(
@@ -292,8 +293,8 @@
         final String[] uidPackages = {PACKAGE_NAME};
         when(mMockPackageManager.getPackagesForUid(APP_UID))
                 .thenReturn(uidPackages);
-        when(mMockPackageManager.getPackageUidAsUser(wantedPackageName, TEST_USER_ID))
-                .thenReturn(wantedUid);
+        when(mMockPackageManagerInternal.isSameApp(wantedPackageName, wantedUid, TEST_USER_ID))
+                .thenReturn(true);
         when(mMockAppWidgetManager.getHostedWidgetPackages(APP_UID))
                 .thenReturn(new ArraySet<>());
         mContext.getTestablePermissions().setPermission(
@@ -312,8 +313,8 @@
         final String[] uidPackages = {PACKAGE_NAME};
         when(mMockPackageManager.getPackagesForUid(APP_UID))
                 .thenReturn(uidPackages);
-        when(mMockPackageManager.getPackageUidAsUser(wantedPackageName, TEST_USER_ID))
-                .thenReturn(wantedUid);
+        when(mMockPackageManagerInternal.isSameApp(wantedPackageName, wantedUid, TEST_USER_ID))
+                .thenReturn(true);
         when(mMockAppWidgetManager.getHostedWidgetPackages(APP_UID))
                 .thenReturn(new ArraySet<>());
         mContext.getTestablePermissions().setPermission(
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index 0033e3e..47f32fe 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -851,7 +851,7 @@
                     }
 
                     @Override
-                    public void onAnimationCancelled(boolean isKeyguardOccluded) {
+                    public void onAnimationCancelled() {
                     }
                 }, 0, 0));
         activity.updateOptionsLocked(opts);
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppChangeTransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/AppChangeTransitionTests.java
index 6d13124..169968c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppChangeTransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppChangeTransitionTests.java
@@ -87,7 +87,7 @@
         }
 
         @Override
-        public void onAnimationCancelled(boolean isKeyguardOccluded) {
+        public void onAnimationCancelled() {
         }
 
         @Override
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
index 0ae579b..a11079b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
@@ -735,7 +735,7 @@
         }
 
         @Override
-        public void onAnimationCancelled(boolean isKeyguardOccluded) throws RemoteException {
+        public void onAnimationCancelled() throws RemoteException {
             mFinishedCallback = null;
         }
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
index 59cc4f5..ba8c94d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
@@ -549,7 +549,7 @@
         }
 
         @Override
-        public void onAnimationCancelled(boolean isKeyguardOccluded) {
+        public void onAnimationCancelled() {
             mCancelled = true;
         }
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
index eb26415..11d9629 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
@@ -211,7 +211,7 @@
         mController.goodToGo(TRANSIT_OLD_ACTIVITY_OPEN);
 
         adapter.onAnimationCancelled(mMockLeash);
-        verify(mMockRunner).onAnimationCancelled(anyBoolean());
+        verify(mMockRunner).onAnimationCancelled();
     }
 
     @Test
@@ -227,7 +227,7 @@
         mClock.fastForward(10500);
         mHandler.timeAdvance();
 
-        verify(mMockRunner).onAnimationCancelled(anyBoolean());
+        verify(mMockRunner).onAnimationCancelled();
         verify(mFinishedCallback).onAnimationFinished(eq(ANIMATION_TYPE_APP_TRANSITION),
                 eq(adapter));
     }
@@ -248,12 +248,12 @@
             mClock.fastForward(10500);
             mHandler.timeAdvance();
 
-            verify(mMockRunner, never()).onAnimationCancelled(anyBoolean());
+            verify(mMockRunner, never()).onAnimationCancelled();
 
             mClock.fastForward(52500);
             mHandler.timeAdvance();
 
-            verify(mMockRunner).onAnimationCancelled(anyBoolean());
+            verify(mMockRunner).onAnimationCancelled();
             verify(mFinishedCallback).onAnimationFinished(eq(ANIMATION_TYPE_APP_TRANSITION),
                     eq(adapter));
         } finally {
@@ -265,7 +265,7 @@
     public void testZeroAnimations() throws Exception {
         mController.goodToGo(TRANSIT_OLD_NONE);
         verify(mMockRunner, never()).onAnimationStart(anyInt(), any(), any(), any(), any());
-        verify(mMockRunner).onAnimationCancelled(anyBoolean());
+        verify(mMockRunner).onAnimationCancelled();
     }
 
     @Test
@@ -275,7 +275,7 @@
                 new Point(50, 100), null, new Rect(50, 100, 150, 150), null, false);
         mController.goodToGo(TRANSIT_OLD_ACTIVITY_OPEN);
         verify(mMockRunner, never()).onAnimationStart(anyInt(), any(), any(), any(), any());
-        verify(mMockRunner).onAnimationCancelled(anyBoolean());
+        verify(mMockRunner).onAnimationCancelled();
     }
 
     @Test
@@ -317,7 +317,7 @@
         win.mActivityRecord.removeImmediately();
         mController.goodToGo(TRANSIT_OLD_ACTIVITY_OPEN);
         verify(mMockRunner, never()).onAnimationStart(anyInt(), any(), any(), any(), any());
-        verify(mMockRunner).onAnimationCancelled(anyBoolean());
+        verify(mMockRunner).onAnimationCancelled();
         verify(mFinishedCallback).onAnimationFinished(eq(ANIMATION_TYPE_APP_TRANSITION),
                 eq(adapter));
     }
@@ -575,7 +575,7 @@
 
             // Cancel the wallpaper window animator and ensure the runner is not canceled
             wallpaperWindowToken.cancelAnimation();
-            verify(mMockRunner, never()).onAnimationCancelled(anyBoolean());
+            verify(mMockRunner, never()).onAnimationCancelled();
         } finally {
             mDisplayContent.mOpeningApps.clear();
         }
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
index a1ddd57..ad606cb 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
@@ -1112,7 +1112,7 @@
                     }
 
                     @Override
-                    public void onAnimationCancelled(boolean isKeyguardOccluded) {
+                    public void onAnimationCancelled() {
                     }
                 }, 0, 0, false);
         adapter.setCallingPidUid(123, 456);
diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java
index 9dd2a61..26590c4 100644
--- a/telecomm/java/android/telecom/TelecomManager.java
+++ b/telecomm/java/android/telecom/TelecomManager.java
@@ -744,10 +744,6 @@
      * state of calls in the self-managed {@link ConnectionService}.  An example use-case is
      * exposing these calls to an automotive device via its companion app.
      * <p>
-     * This meta-data can only be set for an {@link InCallService} which also sets
-     * {@link #METADATA_IN_CALL_SERVICE_UI}. Only the default phone/dialer app, or a car-mode
-     * {@link InCallService} can see self-managed calls.
-     * <p>
      * See also {@link Connection#PROPERTY_SELF_MANAGED}.
      */
     public static final String METADATA_INCLUDE_SELF_MANAGED_CALLS =