Merge "[multi-shade] Launcher touch integration" into udc-dev
diff --git a/core/java/android/content/IntentFilter.java b/core/java/android/content/IntentFilter.java
index 6ff4271..f946754 100644
--- a/core/java/android/content/IntentFilter.java
+++ b/core/java/android/content/IntentFilter.java
@@ -531,8 +531,9 @@
         mInstantAppVisibility = o.mInstantAppVisibility;
     }
 
-    /** {@inheritDoc} */
-    public String toString() {
+    /** @hide */
+    public String toLongString() {
+        // Not implemented directly as toString() due to potential memory regression
         final StringBuilder sb = new StringBuilder();
         sb.append("IntentFilter {");
         sb.append(" pri=");
diff --git a/core/java/android/credentials/ui/CancelUiRequest.java b/core/java/android/credentials/ui/CancelUiRequest.java
index 6bd9de4..d4c249e 100644
--- a/core/java/android/credentials/ui/CancelUiRequest.java
+++ b/core/java/android/credentials/ui/CancelUiRequest.java
@@ -40,24 +40,50 @@
     @NonNull
     private final IBinder mToken;
 
+    private final boolean mShouldShowCancellationUi;
+
+    @NonNull
+    private final String mAppPackageName;
+
     /** Returns the request token matching the user request that should be cancelled. */
     @NonNull
     public IBinder getToken() {
         return mToken;
     }
 
-    public CancelUiRequest(@NonNull IBinder token) {
+    @NonNull
+    public String getAppPackageName() {
+        return mAppPackageName;
+    }
+
+    /**
+     * Returns whether the UI should render a cancellation UI upon the request. If false, the UI
+     * will be silently cancelled.
+     */
+    public boolean shouldShowCancellationUi() {
+        return mShouldShowCancellationUi;
+    }
+
+    public CancelUiRequest(@NonNull IBinder token, boolean shouldShowCancellationUi,
+            @NonNull String appPackageName) {
         mToken = token;
+        mShouldShowCancellationUi = shouldShowCancellationUi;
+        mAppPackageName = appPackageName;
     }
 
     private CancelUiRequest(@NonNull Parcel in) {
         mToken = in.readStrongBinder();
         AnnotationValidations.validate(NonNull.class, null, mToken);
+        mShouldShowCancellationUi = in.readBoolean();
+        mAppPackageName = in.readString8();
+        AnnotationValidations.validate(NonNull.class, null, mAppPackageName);
     }
 
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
         dest.writeStrongBinder(mToken);
+        dest.writeBoolean(mShouldShowCancellationUi);
+        dest.writeString8(mAppPackageName);
     }
 
     @Override
diff --git a/core/java/android/credentials/ui/IntentFactory.java b/core/java/android/credentials/ui/IntentFactory.java
index dcfef56..5e8372d 100644
--- a/core/java/android/credentials/ui/IntentFactory.java
+++ b/core/java/android/credentials/ui/IntentFactory.java
@@ -72,7 +72,8 @@
      * @hide
      */
     @NonNull
-    public static Intent createCancelUiIntent(@NonNull IBinder requestToken) {
+    public static Intent createCancelUiIntent(@NonNull IBinder requestToken,
+            boolean shouldShowCancellationUi, @NonNull String appPackageName) {
         Intent intent = new Intent();
         ComponentName componentName =
                 ComponentName.unflattenFromString(
@@ -81,7 +82,8 @@
                                         com.android.internal.R.string
                                                 .config_credentialManagerDialogComponent));
         intent.setComponent(componentName);
-        intent.putExtra(CancelUiRequest.EXTRA_CANCEL_UI_REQUEST, new CancelUiRequest(requestToken));
+        intent.putExtra(CancelUiRequest.EXTRA_CANCEL_UI_REQUEST,
+                new CancelUiRequest(requestToken, shouldShowCancellationUi, appPackageName));
         return intent;
     }
 
diff --git a/core/java/android/service/remotelockscreenvalidation/RemoteLockscreenValidationClientImpl.java b/core/java/android/service/remotelockscreenvalidation/RemoteLockscreenValidationClientImpl.java
index 140ef39..a5acf54 100644
--- a/core/java/android/service/remotelockscreenvalidation/RemoteLockscreenValidationClientImpl.java
+++ b/core/java/android/service/remotelockscreenvalidation/RemoteLockscreenValidationClientImpl.java
@@ -179,7 +179,7 @@
                     PackageManager.ComponentInfoFlags.of(PackageManager.GET_META_DATA));
         } catch (PackageManager.NameNotFoundException e) {
             Log.w(TAG, TextUtils.formatSimple("Cannot resolve service %s",
-                    serviceComponent.getClass().getName()));
+                    serviceComponent.getClassName()));
             return null;
         }
     }
diff --git a/core/proto/android/providers/settings.proto b/core/proto/android/providers/settings.proto
index e62af74..bab4b6e 100644
--- a/core/proto/android/providers/settings.proto
+++ b/core/proto/android/providers/settings.proto
@@ -21,6 +21,7 @@
 option java_outer_classname = "SettingsServiceProto";
 
 import "frameworks/base/core/proto/android/providers/settings/config.proto";
+import "frameworks/base/core/proto/android/providers/settings/generation.proto";
 import "frameworks/base/core/proto/android/providers/settings/global.proto";
 import "frameworks/base/core/proto/android/providers/settings/secure.proto";
 import "frameworks/base/core/proto/android/providers/settings/system.proto";
@@ -37,6 +38,9 @@
 
     // Config settings
     optional ConfigSettingsProto config_settings = 3;
+
+    // Generation registry stats
+    optional GenerationRegistryProto generation_registry = 4;
 }
 
 message UserSettingsProto {
diff --git a/core/proto/android/providers/settings/generation.proto b/core/proto/android/providers/settings/generation.proto
new file mode 100644
index 0000000..9dcbad2
--- /dev/null
+++ b/core/proto/android/providers/settings/generation.proto
@@ -0,0 +1,41 @@
+/*
+ * 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.
+ */
+
+syntax = "proto2";
+package android.providers.settings;
+
+option java_multiple_files = true;
+
+import "frameworks/base/core/proto/android/privacy.proto";
+
+message GenerationRegistryProto {
+  option (android.msg_privacy).dest = DEST_EXPLICIT;
+  optional int32 num_backing_stores = 1;
+  optional int32 num_max_backing_stores = 2;
+  repeated BackingStoreProto backing_stores = 3;
+}
+
+message BackingStoreProto {
+  optional int32 key = 1;
+  optional int32 backing_store_size = 2;
+  optional int32 num_cached_entries = 3;
+  repeated CacheEntryProto cache_entries = 4;
+}
+
+message CacheEntryProto {
+  optional string name = 1;
+  optional int32 generation = 2;
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/values/strings.xml b/libs/WindowManager/Shell/res/values/strings.xml
index 563fb4d..87a7c3e 100644
--- a/libs/WindowManager/Shell/res/values/strings.xml
+++ b/libs/WindowManager/Shell/res/values/strings.xml
@@ -228,9 +228,10 @@
          the screen. This time the double-tap can happen on the top or bottom of the screen.
          To teach the user about this feature, we display an education explaining how the double-tap
          works and how the app can be moved on the screen.
-         This is the text we show to the user below an animated icon visualizing the double-tap
-         action. [CHAR LIMIT=NONE] -->
-    <string name="letterbox_reachability_reposition_text">Double-tap to move this app</string>
+         This is the text we show to the user below an icon visualizing the double-tap
+         action. The description should be split in two lines separated by "\n" like for this
+         locale. [CHAR LIMIT=NONE] -->
+    <string name="letterbox_reachability_reposition_text">Double-tap to\nmove this app</string>
 
     <!-- Freeform window caption strings -->
     <!-- Accessibility text for the maximize window button [CHAR LIMIT=NONE] -->
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java
index 015d5c1..fb0a91f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java
@@ -16,10 +16,13 @@
 
 package com.android.wm.shell.desktopmode;
 
+import static com.android.wm.shell.desktopmode.EnterDesktopTaskTransitionHandler.FINAL_FREEFORM_SCALE;
+
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.RectEvaluator;
 import android.animation.ValueAnimator;
+import android.annotation.NonNull;
 import android.app.ActivityManager;
 import android.content.Context;
 import android.content.res.Resources;
@@ -32,12 +35,12 @@
 import android.view.WindowManager;
 import android.view.WindowlessWindowManager;
 import android.view.animation.DecelerateInterpolator;
-import android.widget.ImageView;
 
 import com.android.wm.shell.R;
 import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.common.DisplayLayout;
 import com.android.wm.shell.common.SyncTransactionQueue;
 
 /**
@@ -56,6 +59,9 @@
     private final SyncTransactionQueue mSyncQueue;
     private SurfaceControlViewHost mViewHost;
 
+    private View mView;
+    private boolean mIsFullscreen;
+
     public DesktopModeVisualIndicator(SyncTransactionQueue syncQueue,
             ActivityManager.RunningTaskInfo taskInfo, DisplayController displayController,
             Context context, SurfaceControl taskSurface, ShellTaskOrganizer taskOrganizer,
@@ -67,21 +73,19 @@
         mTaskSurface = taskSurface;
         mTaskOrganizer = taskOrganizer;
         mRootTdaOrganizer = taskDisplayAreaOrganizer;
+        createView();
     }
 
     /**
-     * Create and animate the indicator for the exit desktop mode transition.
+     * Create a fullscreen indicator with no animation
      */
-    public void createFullscreenIndicator() {
+    private void createView() {
         final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
         final Resources resources = mContext.getResources();
         final DisplayMetrics metrics = resources.getDisplayMetrics();
         final int screenWidth = metrics.widthPixels;
         final int screenHeight = metrics.heightPixels;
-        final int padding = mDisplayController
-                .getDisplayLayout(mTaskInfo.displayId).stableInsets().top;
-        final ImageView v = new ImageView(mContext);
-        v.setImageResource(R.drawable.desktop_windowing_transition_background);
+        mView = new View(mContext);
         final SurfaceControl.Builder builder = new SurfaceControl.Builder();
         mRootTdaOrganizer.attachToDisplayArea(mTaskInfo.displayId, builder);
         mLeash = builder
@@ -101,7 +105,7 @@
         mViewHost = new SurfaceControlViewHost(mContext,
                 mDisplayController.getDisplay(mTaskInfo.displayId), windowManager,
                 "FullscreenVisualIndicator");
-        mViewHost.setView(v, lp);
+        mViewHost.setView(mView, lp);
         // We want this indicator to be behind the dragged task, but in front of all others.
         t.setRelativeLayer(mLeash, mTaskSurface, -1);
 
@@ -109,17 +113,56 @@
             transaction.merge(t);
             t.close();
         });
-        final Rect startBounds = new Rect(padding, padding,
-                screenWidth - padding, screenHeight - padding);
-        final VisualIndicatorAnimator animator = VisualIndicatorAnimator.fullscreenIndicator(v,
-                startBounds);
+    }
+
+    /**
+     * Create fullscreen indicator and fades it in.
+     */
+    public void createFullscreenIndicator() {
+        mIsFullscreen = true;
+        mView.setBackgroundResource(R.drawable.desktop_windowing_transition_background);
+        final VisualIndicatorAnimator animator = VisualIndicatorAnimator.toFullscreenAnimator(
+                mView, mDisplayController.getDisplayLayout(mTaskInfo.displayId));
+        animator.start();
+    }
+
+    /**
+     * Create a fullscreen indicator. Animator fades it in while expanding the bounds outwards.
+     */
+    public void createFullscreenIndicatorWithAnimatedBounds() {
+        mIsFullscreen = true;
+        mView.setBackgroundResource(R.drawable.desktop_windowing_transition_background);
+        final VisualIndicatorAnimator animator = VisualIndicatorAnimator
+                .toFullscreenAnimatorWithAnimatedBounds(mView,
+                        mDisplayController.getDisplayLayout(mTaskInfo.displayId));
+        animator.start();
+    }
+
+    /**
+     * Takes existing fullscreen indicator and animates it to freeform bounds
+     */
+    public void transitionFullscreenIndicatorToFreeform() {
+        mIsFullscreen = false;
+        final VisualIndicatorAnimator animator = VisualIndicatorAnimator.toFreeformAnimator(
+                mView, mDisplayController.getDisplayLayout(mTaskInfo.displayId));
+        animator.start();
+    }
+
+    /**
+     * Takes the existing freeform indicator and animates it to fullscreen
+     */
+    public void transitionFreeformIndicatorToFullscreen() {
+        mIsFullscreen = true;
+        final VisualIndicatorAnimator animator =
+                VisualIndicatorAnimator.toFullscreenAnimatorWithAnimatedBounds(
+                mView, mDisplayController.getDisplayLayout(mTaskInfo.displayId));
         animator.start();
     }
 
     /**
      * Release the indicator and its components when it is no longer needed.
      */
-    public void releaseFullscreenIndicator() {
+    public void releaseVisualIndicator() {
         if (mViewHost == null) return;
         if (mViewHost != null) {
             mViewHost.release();
@@ -136,20 +179,28 @@
             });
         }
     }
+
+    /**
+     * Returns true if visual indicator is fullscreen
+     */
+    public boolean isFullscreen() {
+        return mIsFullscreen;
+    }
+
     /**
      * Animator for Desktop Mode transitions which supports bounds and alpha animation.
      */
     private static class VisualIndicatorAnimator extends ValueAnimator {
         private static final int FULLSCREEN_INDICATOR_DURATION = 200;
-        private static final float SCALE_ADJUSTMENT_PERCENT = 0.015f;
+        private static final float FULLSCREEN_SCALE_ADJUSTMENT_PERCENT = 0.015f;
         private static final float INDICATOR_FINAL_OPACITY = 0.7f;
 
-        private final ImageView mView;
+        private final View mView;
         private final Rect mStartBounds;
         private final Rect mEndBounds;
         private final RectEvaluator mRectEvaluator;
 
-        private VisualIndicatorAnimator(ImageView view, Rect startBounds,
+        private VisualIndicatorAnimator(View view, Rect startBounds,
                 Rect endBounds) {
             mView = view;
             mStartBounds = new Rect(startBounds);
@@ -161,30 +212,66 @@
         /**
          * Create animator for visual indicator of fullscreen transition
          *
-         * @param view        the view for this indicator
-         * @param startBounds the starting bounds of the fullscreen indicator
+         * @param view the view for this indicator
+         * @param displayLayout information about the display the transitioning task is currently on
          */
-        public static VisualIndicatorAnimator fullscreenIndicator(ImageView view,
-                Rect startBounds) {
-            view.getDrawable().setBounds(startBounds);
-            int width = startBounds.width();
-            int height = startBounds.height();
-            Rect endBounds = new Rect((int) (startBounds.left - (SCALE_ADJUSTMENT_PERCENT * width)),
-                    (int) (startBounds.top - (SCALE_ADJUSTMENT_PERCENT * height)),
-                    (int) (startBounds.right + (SCALE_ADJUSTMENT_PERCENT * width)),
-                    (int) (startBounds.bottom + (SCALE_ADJUSTMENT_PERCENT * height)));
-            VisualIndicatorAnimator animator = new VisualIndicatorAnimator(
-                    view, startBounds, endBounds);
+        public static VisualIndicatorAnimator toFullscreenAnimator(@NonNull View view,
+                @NonNull DisplayLayout displayLayout) {
+            final Rect bounds = getMaxBounds(displayLayout);
+            final VisualIndicatorAnimator animator = new VisualIndicatorAnimator(
+                    view, bounds, bounds);
             animator.setInterpolator(new DecelerateInterpolator());
-            setupFullscreenIndicatorAnimation(animator);
+            setupIndicatorAnimation(animator);
+            return animator;
+        }
+
+
+        /**
+         * Create animator for visual indicator of fullscreen transition
+         *
+         * @param view the view for this indicator
+         * @param displayLayout information about the display the transitioning task is currently on
+         */
+        public static VisualIndicatorAnimator toFullscreenAnimatorWithAnimatedBounds(
+                @NonNull View view, @NonNull DisplayLayout displayLayout) {
+            final int padding = displayLayout.stableInsets().top;
+            Rect startBounds = new Rect(padding, padding,
+                    displayLayout.width() - padding, displayLayout.height() - padding);
+            view.getBackground().setBounds(startBounds);
+
+            final VisualIndicatorAnimator animator = new VisualIndicatorAnimator(
+                    view, startBounds, getMaxBounds(displayLayout));
+            animator.setInterpolator(new DecelerateInterpolator());
+            setupIndicatorAnimation(animator);
             return animator;
         }
 
         /**
-         * Add necessary listener for animation of fullscreen indicator
+         * Create animator for visual indicator of freeform transition
+         *
+         * @param view the view for this indicator
+         * @param displayLayout information about the display the transitioning task is currently on
          */
-        private static void setupFullscreenIndicatorAnimation(
-                VisualIndicatorAnimator animator) {
+        public static VisualIndicatorAnimator toFreeformAnimator(@NonNull View view,
+                @NonNull DisplayLayout displayLayout) {
+            final float adjustmentPercentage = 1f - FINAL_FREEFORM_SCALE;
+            final int width = displayLayout.width();
+            final int height = displayLayout.height();
+            Rect endBounds = new Rect((int) (adjustmentPercentage * width / 2),
+                    (int) (adjustmentPercentage * height / 2),
+                    (int) (displayLayout.width() - (adjustmentPercentage * width / 2)),
+                    (int) (displayLayout.height() - (adjustmentPercentage * height / 2)));
+            final VisualIndicatorAnimator animator = new VisualIndicatorAnimator(
+                    view, getMaxBounds(displayLayout), endBounds);
+            animator.setInterpolator(new DecelerateInterpolator());
+            setupIndicatorAnimation(animator);
+            return animator;
+        }
+
+        /**
+         * Add necessary listener for animation of indicator
+         */
+        private static void setupIndicatorAnimation(@NonNull VisualIndicatorAnimator animator) {
             animator.addUpdateListener(a -> {
                 if (animator.mView != null) {
                     animator.updateBounds(a.getAnimatedFraction(), animator.mView);
@@ -196,7 +283,7 @@
             animator.addListener(new AnimatorListenerAdapter() {
                 @Override
                 public void onAnimationEnd(Animator animation) {
-                    animator.mView.getDrawable().setBounds(animator.mEndBounds);
+                    animator.mView.getBackground().setBounds(animator.mEndBounds);
                 }
             });
             animator.setDuration(FULLSCREEN_INDICATOR_DURATION);
@@ -210,9 +297,12 @@
          * @param fraction fraction to use, compared against previous fraction
          * @param view     the view to update
          */
-        private void updateBounds(float fraction, ImageView view) {
+        private void updateBounds(float fraction, View view) {
+            if (mStartBounds.equals(mEndBounds)) {
+                return;
+            }
             Rect currentBounds = mRectEvaluator.evaluate(fraction, mStartBounds, mEndBounds);
-            view.getDrawable().setBounds(currentBounds);
+            view.getBackground().setBounds(currentBounds);
         }
 
         /**
@@ -223,5 +313,23 @@
         private void updateIndicatorAlpha(float fraction, View view) {
             view.setAlpha(fraction * INDICATOR_FINAL_OPACITY);
         }
+
+        /**
+         * Return the max bounds of a fullscreen indicator
+         */
+        private static Rect getMaxBounds(@NonNull DisplayLayout displayLayout) {
+            final int padding = displayLayout.stableInsets().top;
+            final int width = displayLayout.width() - 2 * padding;
+            final int height = displayLayout.height() - 2 * padding;
+            Rect endBounds = new Rect((int) (padding
+                            - (FULLSCREEN_SCALE_ADJUSTMENT_PERCENT * width)),
+                    (int) (padding
+                            - (FULLSCREEN_SCALE_ADJUSTMENT_PERCENT * height)),
+                    (int) (displayLayout.width() - padding
+                            + (FULLSCREEN_SCALE_ADJUSTMENT_PERCENT * width)),
+                    (int) (displayLayout.height() - padding
+                            + (FULLSCREEN_SCALE_ADJUSTMENT_PERCENT * height)));
+            return endBounds;
+        }
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index c35cd5a..670b24c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -122,7 +122,7 @@
     }
 
     /** Move a task to desktop */
-    fun moveToDesktop(task: ActivityManager.RunningTaskInfo) {
+    fun moveToDesktop(task: RunningTaskInfo) {
         ProtoLog.v(WM_SHELL_DESKTOP_MODE, "moveToDesktop: %d", task.taskId)
 
         val wct = WindowContainerTransaction()
@@ -181,7 +181,7 @@
     }
 
     /** Move a task to fullscreen */
-    fun moveToFullscreen(task: ActivityManager.RunningTaskInfo) {
+    fun moveToFullscreen(task: RunningTaskInfo) {
         ProtoLog.v(WM_SHELL_DESKTOP_MODE, "moveToFullscreen: %d", task.taskId)
 
         val wct = WindowContainerTransaction()
@@ -206,7 +206,7 @@
     }
 
     /** Move a task to the front **/
-    fun moveTaskToFront(taskInfo: ActivityManager.RunningTaskInfo) {
+    fun moveTaskToFront(taskInfo: RunningTaskInfo) {
         val wct = WindowContainerTransaction()
         wct.reorder(taskInfo.token, true)
         if (Transitions.ENABLE_SHELL_TRANSITIONS) {
@@ -273,7 +273,7 @@
         request: TransitionRequestInfo
     ): WindowContainerTransaction? {
         // Check if we should skip handling this transition
-        val task: ActivityManager.RunningTaskInfo? = request.triggerTask
+        val task: RunningTaskInfo? = request.triggerTask
         val shouldHandleRequest =
             when {
                 // Only handle open or to front transitions
@@ -382,16 +382,15 @@
             taskSurface: SurfaceControl,
             y: Float
     ) {
-        val statusBarHeight = displayController
-                .getDisplayLayout(taskInfo.displayId)?.stableInsets()?.top ?: 0
         if (taskInfo.windowingMode == WINDOWING_MODE_FREEFORM) {
+            val statusBarHeight = getStatusBarHeight(taskInfo)
             if (y <= statusBarHeight && visualIndicator == null) {
                 visualIndicator = DesktopModeVisualIndicator(syncQueue, taskInfo,
                         displayController, context, taskSurface, shellTaskOrganizer,
                         rootTaskDisplayAreaOrganizer)
-                visualIndicator?.createFullscreenIndicator()
+                visualIndicator?.createFullscreenIndicatorWithAnimatedBounds()
             } else if (y > statusBarHeight && visualIndicator != null) {
-                visualIndicator?.releaseFullscreenIndicator()
+                visualIndicator?.releaseVisualIndicator()
                 visualIndicator = null
             }
         }
@@ -407,16 +406,73 @@
             taskInfo: RunningTaskInfo,
             y: Float
     ) {
-        val statusBarHeight = displayController
-                .getDisplayLayout(taskInfo.displayId)?.stableInsets()?.top ?: 0
+        val statusBarHeight = getStatusBarHeight(taskInfo)
         if (y <= statusBarHeight && taskInfo.windowingMode == WINDOWING_MODE_FREEFORM) {
-            visualIndicator?.releaseFullscreenIndicator()
+            visualIndicator?.releaseVisualIndicator()
             visualIndicator = null
             moveToFullscreenWithAnimation(taskInfo)
         }
     }
 
     /**
+     * Perform checks required on drag move. Create/release fullscreen indicator and transitions
+     * indicator to freeform or fullscreen dimensions as needed.
+     *
+     * @param taskInfo the task being dragged.
+     * @param taskSurface SurfaceControl of dragged task.
+     * @param y coordinate of dragged task. Used for checks against status bar height.
+     */
+    fun onDragPositioningMoveThroughStatusBar(
+            taskInfo: RunningTaskInfo,
+            taskSurface: SurfaceControl,
+            y: Float
+    ) {
+        if (visualIndicator == null) {
+            visualIndicator = DesktopModeVisualIndicator(syncQueue, taskInfo,
+                    displayController, context, taskSurface, shellTaskOrganizer,
+                    rootTaskDisplayAreaOrganizer)
+            visualIndicator?.createFullscreenIndicator()
+        }
+        val indicator = visualIndicator ?: return
+        if (y >= getFreeformTransitionStatusBarDragThreshold(taskInfo)) {
+            if (indicator.isFullscreen) {
+                indicator.transitionFullscreenIndicatorToFreeform()
+            }
+        } else if (!indicator.isFullscreen) {
+            indicator.transitionFreeformIndicatorToFullscreen()
+        }
+    }
+
+    /**
+     * Perform checks required when drag ends under status bar area.
+     *
+     * @param taskInfo the task being dragged.
+     * @param y height of drag, to be checked against status bar height.
+     */
+    fun onDragPositioningEndThroughStatusBar(
+            taskInfo: RunningTaskInfo,
+            freeformBounds: Rect
+    ) {
+        moveToDesktopWithAnimation(taskInfo, freeformBounds)
+        visualIndicator?.releaseVisualIndicator()
+        visualIndicator = null
+    }
+
+
+    private fun getStatusBarHeight(taskInfo: RunningTaskInfo): Int {
+        return displayController.getDisplayLayout(taskInfo.displayId)?.stableInsets()?.top ?: 0
+    }
+
+    /**
+     * Returns the threshold at which we transition a task into freeform when dragging a
+     * fullscreen task down from the status bar
+     */
+    private fun getFreeformTransitionStatusBarDragThreshold(taskInfo: RunningTaskInfo): Int {
+        return 2 * getStatusBarHeight(taskInfo)
+    }
+
+
+    /**
      * Adds a listener to find out about changes in the visibility of freeform tasks.
      *
      * @param listener the listener to add.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
index 4c53f60..f70df83 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
@@ -188,6 +188,11 @@
         return mCurrentAnimator;
     }
 
+    /** Reset animator state to prevent it from being used after its lifetime. */
+    public void resetAnimatorState() {
+        mCurrentAnimator = null;
+    }
+
     private PipTransitionAnimator setupPipTransitionAnimator(PipTransitionAnimator animator) {
         animator.setSurfaceTransactionHelper(mSurfaceTransactionHelper);
         animator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
index ec0e770..a939212 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
@@ -1854,6 +1854,7 @@
                         animator::clearContentOverlay);
             }
             PipAnimationController.quietCancel(animator);
+            mPipAnimationController.resetAnimatorState();
         }
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index c0dcd0b..f998217 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
@@ -79,6 +79,7 @@
 
 public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
     private static final String TAG = "DesktopModeWindowDecorViewModel";
+
     private final DesktopModeWindowDecoration.Factory mDesktopModeWindowDecorFactory;
     private final ActivityTaskManager mActivityTaskManager;
     private final ShellTaskOrganizer mTaskOrganizer;
@@ -542,7 +543,7 @@
                     mTransitionDragActive = false;
                     final int statusBarHeight = getStatusBarHeight(
                             relevantDecor.mTaskInfo.displayId);
-                    if (ev.getY() > statusBarHeight) {
+                    if (ev.getY() > 2 * statusBarHeight) {
                         if (DesktopModeStatus.isProto2Enabled()) {
                             mPauseRelayoutForTask = relevantDecor.mTaskInfo.taskId;
                             centerAndMoveToDesktopWithAnimation(relevantDecor, ev);
@@ -567,9 +568,11 @@
                     return;
                 }
                 if (mTransitionDragActive) {
-                    final int statusBarHeight = mDisplayController
-                            .getDisplayLayout(
-                                    relevantDecor.mTaskInfo.displayId).stableInsets().top;
+                    mDesktopTasksController.ifPresent(
+                            c -> c.onDragPositioningMoveThroughStatusBar(relevantDecor.mTaskInfo,
+                            relevantDecor.mTaskSurface, ev.getY()));
+                    final int statusBarHeight = getStatusBarHeight(
+                            relevantDecor.mTaskInfo.displayId);
                     if (ev.getY() > statusBarHeight) {
                         if (!mDragToDesktopAnimationStarted) {
                             mDragToDesktopAnimationStarted = true;
@@ -644,7 +647,8 @@
             @Override
             public void onAnimationEnd(Animator animation) {
                 mDesktopTasksController.ifPresent(
-                        c -> c.moveToDesktopWithAnimation(relevantDecor.mTaskInfo,
+                        c -> c.onDragPositioningEndThroughStatusBar(
+                                relevantDecor.mTaskInfo,
                                 calculateFreeformBounds(FINAL_FREEFORM_SCALE)));
             }
         });
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
index 28f9453..452455c 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
@@ -110,6 +110,11 @@
             ResultReceiver::class.java
         )
 
+        val cancellationRequest = getCancelUiRequest(intent)
+        val cancelUiRequestState = cancellationRequest?.let {
+            CancelUiRequestState(getAppLabel(context.getPackageManager(), it.appPackageName))
+        }
+
         initialUiState = when (requestInfo.type) {
             RequestInfo.TYPE_CREATE -> {
                 val defaultProviderId = userConfigRepo.getDefaultProviderId()
@@ -128,6 +133,7 @@
                         isPasskeyFirstUse
                     )!!,
                     getCredentialUiState = null,
+                    cancelRequestState = cancelUiRequestState
                 )
             }
             RequestInfo.TYPE_GET -> {
@@ -142,6 +148,7 @@
                     if (autoSelectEntry == null) ProviderActivityState.NOT_APPLICABLE
                     else ProviderActivityState.READY_TO_LAUNCH,
                     isAutoSelectFlow = autoSelectEntry != null,
+                    cancelRequestState = cancelUiRequestState
                 )
             }
             else -> throw IllegalStateException("Unrecognized request type: ${requestInfo.type}")
@@ -238,12 +245,12 @@
             }
         }
 
-        /** Return the request token whose UI should be cancelled, or null otherwise. */
-        fun getCancelUiRequestToken(intent: Intent): IBinder? {
+        /** Return the cancellation request if present. */
+        fun getCancelUiRequest(intent: Intent): CancelUiRequest? {
             return intent.extras?.getParcelable(
                 CancelUiRequest.EXTRA_CANCEL_UI_REQUEST,
                 CancelUiRequest::class.java
-            )?.token
+            )
         }
     }
 
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt
index 5d72424..2efe1be 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt
@@ -30,11 +30,13 @@
 import androidx.compose.material.ExperimentalMaterialApi
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.ui.res.stringResource
 import androidx.lifecycle.viewmodel.compose.viewModel
 import com.android.credentialmanager.common.Constants
 import com.android.credentialmanager.common.DialogState
 import com.android.credentialmanager.common.ProviderActivityResult
 import com.android.credentialmanager.common.StartBalIntentSenderForResultContract
+import com.android.credentialmanager.common.ui.Snackbar
 import com.android.credentialmanager.createflow.CreateCredentialScreen
 import com.android.credentialmanager.createflow.hasContentToDisplay
 import com.android.credentialmanager.getflow.GetCredentialScreen
@@ -49,10 +51,9 @@
         super.onCreate(savedInstanceState)
         Log.d(Constants.LOG_TAG, "Creating new CredentialSelectorActivity")
         try {
-            if (CredentialManagerRepo.getCancelUiRequestToken(intent) != null) {
-                Log.d(
-                    Constants.LOG_TAG, "Received UI cancellation intent; cancelling the activity.")
-                this.finish()
+            val (isCancellationRequest, shouldShowCancellationUi, _) =
+                maybeCancelUIUponRequest(intent)
+            if (isCancellationRequest && !shouldShowCancellationUi) {
                 return
             }
             val userConfigRepo = UserConfigRepo(this)
@@ -75,14 +76,15 @@
         setIntent(intent)
         Log.d(Constants.LOG_TAG, "Existing activity received new intent")
         try {
-            val cancelUiRequestToken = CredentialManagerRepo.getCancelUiRequestToken(intent)
             val viewModel: CredentialSelectorViewModel by viewModels()
-            if (cancelUiRequestToken != null &&
-                viewModel.shouldCancelCurrentUi(cancelUiRequestToken)) {
-                Log.d(
-                    Constants.LOG_TAG, "Received UI cancellation intent; cancelling the activity.")
-                this.finish()
-                return
+            val (isCancellationRequest, shouldShowCancellationUi, appDisplayName) =
+                maybeCancelUIUponRequest(intent, viewModel)
+            if (isCancellationRequest) {
+                if (shouldShowCancellationUi) {
+                    viewModel.onCancellationUiRequested(appDisplayName)
+                } else {
+                    return
+                }
             } else {
                 val userConfigRepo = UserConfigRepo(this)
                 val credManRepo = CredentialManagerRepo(this, intent, userConfigRepo)
@@ -93,11 +95,41 @@
         }
     }
 
+    /**
+     * Cancels the UI activity if requested by the backend. Different from the other finishing
+     * helpers, this does not report anything back to the Credential Manager service backend.
+     *
+     * Can potentially show a transient snackbar before finishing, if the request specifies so.
+     *
+     * Returns <isCancellationRequest, shouldShowCancellationUi, appDisplayName>.
+     */
+    private fun maybeCancelUIUponRequest(
+        intent: Intent,
+        viewModel: CredentialSelectorViewModel? = null
+    ): Triple<Boolean, Boolean, String?> {
+        val cancelUiRequest = CredentialManagerRepo.getCancelUiRequest(intent)
+            ?: return Triple(false, false, null)
+        if (viewModel != null && !viewModel.shouldCancelCurrentUi(cancelUiRequest.token)) {
+            // Cancellation was for a different request, don't cancel the current UI.
+            return Triple(false, false, null)
+        }
+        val shouldShowCancellationUi = cancelUiRequest.shouldShowCancellationUi()
+        Log.d(
+            Constants.LOG_TAG, "Received UI cancellation intent. Should show cancellation" +
+            " ui = $shouldShowCancellationUi")
+        val appDisplayName = getAppLabel(packageManager, cancelUiRequest.appPackageName)
+        if (!shouldShowCancellationUi) {
+            this.finish()
+        }
+        return Triple(true, shouldShowCancellationUi, appDisplayName)
+    }
+
+
     @ExperimentalMaterialApi
     @Composable
-    fun CredentialManagerBottomSheet(
+    private fun CredentialManagerBottomSheet(
         credManRepo: CredentialManagerRepo,
-        userConfigRepo: UserConfigRepo
+        userConfigRepo: UserConfigRepo,
     ) {
         val viewModel: CredentialSelectorViewModel = viewModel {
             CredentialSelectorViewModel(credManRepo, userConfigRepo)
@@ -113,7 +145,17 @@
 
         val createCredentialUiState = viewModel.uiState.createCredentialUiState
         val getCredentialUiState = viewModel.uiState.getCredentialUiState
-        if (createCredentialUiState != null && hasContentToDisplay(createCredentialUiState)) {
+        val cancelRequestState = viewModel.uiState.cancelRequestState
+        if (cancelRequestState != null) {
+            if (cancelRequestState.appDisplayName == null) {
+                Log.d(Constants.LOG_TAG, "Received UI cancel request with an invalid package name.")
+                this.finish()
+                return
+            } else {
+                UiCancellationScreen(cancelRequestState.appDisplayName)
+            }
+        } else if (
+            createCredentialUiState != null && hasContentToDisplay(createCredentialUiState)) {
             CreateCredentialScreen(
                 viewModel = viewModel,
                 createCredentialUiState = createCredentialUiState,
@@ -122,15 +164,15 @@
         } else if (getCredentialUiState != null && hasContentToDisplay(getCredentialUiState)) {
             if (isFallbackScreen(getCredentialUiState)) {
                 GetGenericCredentialScreen(
-                        viewModel = viewModel,
-                        getCredentialUiState = getCredentialUiState,
-                        providerActivityLauncher = launcher
+                    viewModel = viewModel,
+                    getCredentialUiState = getCredentialUiState,
+                    providerActivityLauncher = launcher
                 )
             } else {
                 GetCredentialScreen(
-                        viewModel = viewModel,
-                        getCredentialUiState = getCredentialUiState,
-                        providerActivityLauncher = launcher
+                    viewModel = viewModel,
+                    getCredentialUiState = getCredentialUiState,
+                    providerActivityLauncher = launcher
                 )
             }
         } else {
@@ -172,4 +214,13 @@
         )
         this.finish()
     }
+
+    @Composable
+    private fun UiCancellationScreen(appDisplayName: String) {
+        Snackbar(
+            contentText = stringResource(R.string.request_cancelled_by, appDisplayName),
+            onDismiss = { this@CredentialSelectorActivity.finish() },
+            dismissOnTimeout = true,
+        )
+    }
 }
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
index e49e3f1..7eb3bf4 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
@@ -51,6 +51,11 @@
     // True if the UI has one and only one auto selectable entry. Its provider activity will be
     // launched immediately, and canceling it will cancel the whole UI flow.
     val isAutoSelectFlow: Boolean = false,
+    val cancelRequestState: CancelUiRequestState?,
+)
+
+data class CancelUiRequestState(
+    val appDisplayName: String?,
 )
 
 class CredentialSelectorViewModel(
@@ -76,6 +81,10 @@
         uiState = uiState.copy(dialogState = DialogState.COMPLETE)
     }
 
+    fun onCancellationUiRequested(appDisplayName: String?) {
+        uiState = uiState.copy(cancelRequestState = CancelUiRequestState(appDisplayName))
+    }
+
     /** Close the activity and don't report anything to the backend.
      *  Example use case is the no-auth-info snackbar where the activity should simply display the
      *  UI and then be dismissed. */
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
index 783cf3b..43da980 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
@@ -64,7 +64,7 @@
 import org.json.JSONObject
 
 // TODO: remove all !! checks
-private fun getAppLabel(
+fun getAppLabel(
     pm: PackageManager,
     appPackageName: String
 ): String? {
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/SnackBar.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/SnackBar.kt
index 514ff90..dfff3d6 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/SnackBar.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/SnackBar.kt
@@ -30,20 +30,24 @@
 import androidx.compose.material3.IconButton
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.platform.LocalAccessibilityManager
 import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.unit.dp
 import com.android.credentialmanager.R
 import com.android.credentialmanager.common.material.Scrim
 import com.android.credentialmanager.ui.theme.Shapes
+import kotlinx.coroutines.delay
 
 @Composable
 fun Snackbar(
     contentText: String,
     action: (@Composable () -> Unit)? = null,
     onDismiss: () -> Unit,
+    dismissOnTimeout: Boolean = false,
 ) {
     BoxWithConstraints {
         Box(Modifier.fillMaxSize()) {
@@ -89,4 +93,20 @@
             }
         }
     }
+    val accessibilityManager = LocalAccessibilityManager.current
+    LaunchedEffect(true) {
+        if (dismissOnTimeout) {
+            // Same as SnackbarDuration.Short
+            val originalDuration = 4000L
+            val duration = if (accessibilityManager == null) originalDuration else
+                accessibilityManager.calculateRecommendedTimeoutMillis(
+                    originalDuration,
+                    containsIcons = true,
+                    containsText = true,
+                    containsControls = action != null,
+                )
+            delay(duration)
+            onDismiss()
+        }
+    }
 }
\ No newline at end of file
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/GenerationRegistry.java b/packages/SettingsProvider/src/com/android/providers/settings/GenerationRegistry.java
index 80030f7..02ec486 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/GenerationRegistry.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/GenerationRegistry.java
@@ -20,14 +20,19 @@
 import android.os.Bundle;
 import android.os.UserHandle;
 import android.provider.Settings;
+import android.providers.settings.BackingStoreProto;
+import android.providers.settings.CacheEntryProto;
+import android.providers.settings.GenerationRegistryProto;
 import android.util.ArrayMap;
 import android.util.MemoryIntArray;
 import android.util.Slog;
+import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 
 import java.io.IOException;
+import java.io.PrintWriter;
 
 /**
  * This class tracks changes for config/global/secure/system tables
@@ -292,4 +297,94 @@
     int getMaxNumBackingStores() {
         return mMaxNumBackingStore;
     }
+
+    public void dumpProto(ProtoOutputStream proto) {
+        synchronized (mLock) {
+            final int numBackingStores = mKeyToBackingStoreMap.size();
+            proto.write(GenerationRegistryProto.NUM_BACKING_STORES, numBackingStores);
+            proto.write(GenerationRegistryProto.NUM_MAX_BACKING_STORES, getMaxNumBackingStores());
+
+            for (int i = 0; i < numBackingStores; i++) {
+                final long token = proto.start(GenerationRegistryProto.BACKING_STORES);
+                final int key = mKeyToBackingStoreMap.keyAt(i);
+                proto.write(BackingStoreProto.KEY, key);
+                try {
+                    proto.write(BackingStoreProto.BACKING_STORE_SIZE,
+                            mKeyToBackingStoreMap.valueAt(i).size());
+                } catch (IOException ignore) {
+                }
+                proto.write(BackingStoreProto.NUM_CACHED_ENTRIES,
+                        mKeyToIndexMapMap.get(key).size());
+                final ArrayMap<String, Integer> indexMap = mKeyToIndexMapMap.get(key);
+                final MemoryIntArray backingStore = getBackingStoreLocked(key,
+                        /* createIfNotExist= */ false);
+                if (indexMap == null || backingStore == null) {
+                    continue;
+                }
+                for (String setting : indexMap.keySet()) {
+                    try {
+                        final int index = getKeyIndexLocked(key, setting, mKeyToIndexMapMap,
+                                backingStore, /* createIfNotExist= */ false);
+                        if (index < 0) {
+                            continue;
+                        }
+                        final long cacheEntryToken = proto.start(
+                                BackingStoreProto.CACHE_ENTRIES);
+                        final int generation = backingStore.get(index);
+                        proto.write(CacheEntryProto.NAME,
+                                setting.equals(DEFAULT_MAP_KEY_FOR_UNSET_SETTINGS)
+                                        ? "UNSET" : setting);
+                        proto.write(CacheEntryProto.GENERATION, generation);
+                        proto.end(cacheEntryToken);
+                    } catch (IOException e) {
+                        throw new RuntimeException(e);
+                    }
+                }
+                proto.end(token);
+            }
+
+        }
+    }
+
+    public void dump(PrintWriter pw) {
+        pw.println("GENERATION REGISTRY");
+        pw.println("Maximum number of backing stores:" + getMaxNumBackingStores());
+        synchronized (mLock) {
+            final int numBackingStores = mKeyToBackingStoreMap.size();
+            pw.println("Number of backing stores:" + numBackingStores);
+            for (int i = 0; i < numBackingStores; i++) {
+                final int key = mKeyToBackingStoreMap.keyAt(i);
+                pw.print("_Backing store for type:"); pw.print(SettingsState.settingTypeToString(
+                        SettingsState.getTypeFromKey(key)));
+                pw.print(" user:"); pw.print(SettingsState.getUserIdFromKey(key));
+                try {
+                    pw.print(" size:" + mKeyToBackingStoreMap.valueAt(i).size());
+                } catch (IOException ignore) {
+                }
+                pw.println(" cachedEntries:" + mKeyToIndexMapMap.get(key).size());
+                final ArrayMap<String, Integer> indexMap = mKeyToIndexMapMap.get(key);
+                final MemoryIntArray backingStore = getBackingStoreLocked(key,
+                        /* createIfNotExist= */ false);
+                if (indexMap == null || backingStore == null) {
+                    continue;
+                }
+                for (String setting : indexMap.keySet()) {
+                    try {
+                        final int index = getKeyIndexLocked(key, setting, mKeyToIndexMapMap,
+                                backingStore, /* createIfNotExist= */ false);
+                        if (index < 0) {
+                            continue;
+                        }
+                        final int generation = backingStore.get(index);
+                        pw.print("  setting: "); pw.print(
+                                setting.equals(DEFAULT_MAP_KEY_FOR_UNSET_SETTINGS)
+                                        ? "UNSET" : setting);
+                        pw.println(" generation:" + generation);
+                    } catch (IOException e) {
+                        throw new RuntimeException(e);
+                    }
+                }
+            }
+        }
+    }
 }
\ No newline at end of file
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index d49627e..d3a9e91 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -120,6 +120,17 @@
             dumpProtoUserSettingsLocked(proto, SettingsServiceDumpProto.USER_SETTINGS,
                     settingsRegistry, UserHandle.of(users.keyAt(i)));
         }
+
+        // Generation registry
+        dumpProtoGenerationRegistryLocked(proto, SettingsServiceDumpProto.GENERATION_REGISTRY,
+                settingsRegistry);
+    }
+
+    private static void dumpProtoGenerationRegistryLocked(@NonNull ProtoOutputStream proto,
+            long fieldId, SettingsProvider.SettingsRegistry settingsRegistry) {
+        final long token = proto.start(fieldId);
+        settingsRegistry.getGenerationRegistry().dumpProto(proto);
+        proto.end(token);
     }
 
     /**
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index 3e1b597..7a97b78 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -900,6 +900,7 @@
             } finally {
                 Binder.restoreCallingIdentity(identity);
             }
+            mSettingsRegistry.mGenerationRegistry.dump(pw);
         }
     }
 
@@ -6024,5 +6025,10 @@
             return !a11yButtonTargetsSettings.isNull()
                     && !TextUtils.isEmpty(a11yButtonTargetsSettings.getValue());
         }
+
+        @NonNull
+        public GenerationRegistry getGenerationRegistry() {
+            return mGenerationRegistry;
+        }
     }
 }
diff --git a/packages/SystemUI/res/layout/notification_conversation_info.xml b/packages/SystemUI/res/layout/notification_conversation_info.xml
index 79948da..4f6e88c 100644
--- a/packages/SystemUI/res/layout/notification_conversation_info.xml
+++ b/packages/SystemUI/res/layout/notification_conversation_info.xml
@@ -170,11 +170,11 @@
             android:layout_width="@dimen/notification_importance_toggle_size"
             android:layout_height="@dimen/notification_importance_toggle_size"
             android:layout_centerVertical="true"
-            android:background="@drawable/ripple_drawable"
             android:contentDescription="@string/notification_more_settings"
+            android:background="@drawable/ripple_drawable_20dp"
             android:src="@drawable/ic_settings"
-            android:layout_alignParentEnd="true"
-            android:tint="@color/notification_guts_link_icon_tint"/>
+            android:tint="?android:attr/colorAccent"
+            android:layout_alignParentEnd="true" />
 
     </LinearLayout>
 
diff --git a/packages/SystemUI/res/layout/notification_info.xml b/packages/SystemUI/res/layout/notification_info.xml
index 4d6c2022..852db1b 100644
--- a/packages/SystemUI/res/layout/notification_info.xml
+++ b/packages/SystemUI/res/layout/notification_info.xml
@@ -108,11 +108,11 @@
             android:layout_width="@dimen/notification_importance_toggle_size"
             android:layout_height="@dimen/notification_importance_toggle_size"
             android:layout_centerVertical="true"
-            android:background="@android:color/transparent"
             android:contentDescription="@string/notification_more_settings"
-            android:src="@drawable/notif_settings_button"
-            android:layout_alignParentEnd="true"
-            android:tint="@color/notification_guts_link_icon_tint"/>
+            android:background="@drawable/ripple_drawable_20dp"
+            android:src="@drawable/ic_settings"
+            android:tint="?android:attr/colorAccent"
+            android:layout_alignParentEnd="true" />
 
     </LinearLayout>
 
diff --git a/packages/SystemUI/res/layout/partial_conversation_info.xml b/packages/SystemUI/res/layout/partial_conversation_info.xml
index 9ed3f92..4850b35 100644
--- a/packages/SystemUI/res/layout/partial_conversation_info.xml
+++ b/packages/SystemUI/res/layout/partial_conversation_info.xml
@@ -81,11 +81,11 @@
             android:layout_width="@dimen/notification_importance_toggle_size"
             android:layout_height="@dimen/notification_importance_toggle_size"
             android:layout_centerVertical="true"
-            android:background="@drawable/ripple_drawable"
             android:contentDescription="@string/notification_more_settings"
+            android:background="@drawable/ripple_drawable_20dp"
             android:src="@drawable/ic_settings"
-            android:layout_alignParentEnd="true"
-            android:tint="@color/notification_guts_link_icon_tint"/>
+            android:tint="?android:attr/colorAccent"
+            android:layout_alignParentEnd="true"/>
 
     </LinearLayout>
 
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index 8e5c76c..ac30311 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -618,9 +618,9 @@
         }
         logBiometricTouch(processedTouch.getEvent(), data);
 
-        // Always pilfer pointers that are within sensor area
-        if (isWithinSensorArea(mOverlay.getOverlayView(), event.getRawX(), event.getRawY(), true)) {
-            Log.d("Austin", "pilferTouch invalid overlap");
+        // Always pilfer pointers that are within sensor area or when alternate bouncer is showing
+        if (isWithinSensorArea(mOverlay.getOverlayView(), event.getRawX(), event.getRawY(), true)
+                || mAlternateBouncerInteractor.isVisibleState()) {
             mInputManager.pilferPointers(
                     mOverlay.getOverlayView().getViewRootImpl().getInputToken());
         }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
index 414c2ec..f876aff 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
@@ -353,10 +353,19 @@
             flags = flags or WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
         }
 
-        // Original sensorBounds assume portrait mode.
+        val isEnrollment = when (requestReason) {
+            REASON_ENROLL_FIND_SENSOR, REASON_ENROLL_ENROLLING -> true
+            else -> false
+        }
+
+        // Use expanded overlay unless touchExploration enabled
         var rotatedBounds =
             if (featureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)) {
-                Rect(overlayParams.overlayBounds)
+                if (accessibilityManager.isTouchExplorationEnabled && isEnrollment) {
+                    Rect(overlayParams.sensorBounds)
+                } else {
+                    Rect(overlayParams.overlayBounds)
+                }
             } else {
                 Rect(overlayParams.sensorBounds)
             }
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeBrightnessHostForwarder.java b/packages/SystemUI/src/com/android/systemui/doze/DozeBrightnessHostForwarder.java
index 0aeb128..cf0dcad 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeBrightnessHostForwarder.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeBrightnessHostForwarder.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.doze;
 
+import java.util.concurrent.Executor;
+
 /**
  * Forwards the currently used brightness to {@link DozeHost}.
  */
@@ -23,8 +25,9 @@
 
     private final DozeHost mHost;
 
-    public DozeBrightnessHostForwarder(DozeMachine.Service wrappedService, DozeHost host) {
-        super(wrappedService);
+    public DozeBrightnessHostForwarder(DozeMachine.Service wrappedService, DozeHost host,
+            Executor bgExecutor) {
+        super(wrappedService, bgExecutor);
         mHost = host;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java b/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java
index f0aefb5..7f0b16b 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java
@@ -39,6 +39,7 @@
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.concurrent.Executor;
 
 import javax.inject.Inject;
 
@@ -150,7 +151,6 @@
     private final DockManager mDockManager;
     private final Part[] mParts;
     private final UserTracker mUserTracker;
-
     private final ArrayList<State> mQueuedRequests = new ArrayList<>();
     private State mState = State.UNINITIALIZED;
     private int mPulseReason;
@@ -512,9 +512,11 @@
 
         class Delegate implements Service {
             private final Service mDelegate;
+            private final Executor mBgExecutor;
 
-            public Delegate(Service delegate) {
+            public Delegate(Service delegate, Executor bgExecutor) {
                 mDelegate = delegate;
+                mBgExecutor = bgExecutor;
             }
 
             @Override
@@ -534,7 +536,9 @@
 
             @Override
             public void setDozeScreenBrightness(int brightness) {
-                mDelegate.setDozeScreenBrightness(brightness);
+                mBgExecutor.execute(() -> {
+                    mDelegate.setDozeScreenBrightness(brightness);
+                });
             }
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenStatePreventingAdapter.java b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenStatePreventingAdapter.java
index 25c2c39..8d44472 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenStatePreventingAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenStatePreventingAdapter.java
@@ -22,14 +22,16 @@
 
 import com.android.systemui.statusbar.phone.DozeParameters;
 
+import java.util.concurrent.Executor;
+
 /**
  * Prevents usage of doze screen states on devices that don't support them.
  */
 public class DozeScreenStatePreventingAdapter extends DozeMachine.Service.Delegate {
 
     @VisibleForTesting
-    DozeScreenStatePreventingAdapter(DozeMachine.Service inner) {
-        super(inner);
+    DozeScreenStatePreventingAdapter(DozeMachine.Service inner, Executor bgExecutor) {
+        super(inner, bgExecutor);
     }
 
     @Override
@@ -47,8 +49,8 @@
      * return a new instance of {@link DozeScreenStatePreventingAdapter} wrapping {@code inner}.
      */
     public static DozeMachine.Service wrapIfNeeded(DozeMachine.Service inner,
-            DozeParameters params) {
-        return isNeeded(params) ? new DozeScreenStatePreventingAdapter(inner) : inner;
+            DozeParameters params, Executor bgExecutor) {
+        return isNeeded(params) ? new DozeScreenStatePreventingAdapter(inner, bgExecutor) : inner;
     }
 
     private static boolean isNeeded(DozeParameters params) {
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeSuspendScreenStatePreventingAdapter.java b/packages/SystemUI/src/com/android/systemui/doze/DozeSuspendScreenStatePreventingAdapter.java
index a0c490951..f7773f1 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeSuspendScreenStatePreventingAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeSuspendScreenStatePreventingAdapter.java
@@ -22,14 +22,16 @@
 
 import com.android.systemui.statusbar.phone.DozeParameters;
 
+import java.util.concurrent.Executor;
+
 /**
  * Prevents usage of doze screen states on devices that don't support them.
  */
 public class DozeSuspendScreenStatePreventingAdapter extends DozeMachine.Service.Delegate {
 
     @VisibleForTesting
-    DozeSuspendScreenStatePreventingAdapter(DozeMachine.Service inner) {
-        super(inner);
+    DozeSuspendScreenStatePreventingAdapter(DozeMachine.Service inner, Executor bgExecutor) {
+        super(inner, bgExecutor);
     }
 
     @Override
@@ -45,8 +47,9 @@
      * return a new instance of {@link DozeSuspendScreenStatePreventingAdapter} wrapping {@code inner}.
      */
     public static DozeMachine.Service wrapIfNeeded(DozeMachine.Service inner,
-            DozeParameters params) {
-        return isNeeded(params) ? new DozeSuspendScreenStatePreventingAdapter(inner) : inner;
+            DozeParameters params, Executor bgExecutor) {
+        return isNeeded(params) ? new DozeSuspendScreenStatePreventingAdapter(inner, bgExecutor)
+                : inner;
     }
 
     private static boolean isNeeded(DozeParameters params) {
diff --git a/packages/SystemUI/src/com/android/systemui/doze/dagger/DozeModule.java b/packages/SystemUI/src/com/android/systemui/doze/dagger/DozeModule.java
index 069344f..d408472 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/dagger/DozeModule.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/dagger/DozeModule.java
@@ -22,6 +22,7 @@
 
 import com.android.systemui.R;
 import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.dagger.qualifiers.UiBackground;
 import com.android.systemui.doze.DozeAuthRemover;
 import com.android.systemui.doze.DozeBrightnessHostForwarder;
 import com.android.systemui.doze.DozeDockHandler;
@@ -45,13 +46,14 @@
 import com.android.systemui.util.wakelock.DelayedWakeLock;
 import com.android.systemui.util.wakelock.WakeLock;
 
+import dagger.Module;
+import dagger.Provides;
+
 import java.util.Arrays;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Optional;
-
-import dagger.Module;
-import dagger.Provides;
+import java.util.concurrent.Executor;
 
 /** Dagger module for use with {@link com.android.systemui.doze.dagger.DozeComponent}. */
 @Module
@@ -60,13 +62,13 @@
     @DozeScope
     @WrappedService
     static DozeMachine.Service providesWrappedService(DozeMachine.Service dozeMachineService,
-            DozeHost dozeHost, DozeParameters dozeParameters) {
+            DozeHost dozeHost, DozeParameters dozeParameters, @UiBackground Executor bgExecutor) {
         DozeMachine.Service wrappedService = dozeMachineService;
-        wrappedService = new DozeBrightnessHostForwarder(wrappedService, dozeHost);
+        wrappedService = new DozeBrightnessHostForwarder(wrappedService, dozeHost, bgExecutor);
         wrappedService = DozeScreenStatePreventingAdapter.wrapIfNeeded(
-                wrappedService, dozeParameters);
+                wrappedService, dozeParameters, bgExecutor);
         wrappedService = DozeSuspendScreenStatePreventingAdapter.wrapIfNeeded(
-                wrappedService, dozeParameters);
+                wrappedService, dozeParameters, bgExecutor);
 
         return wrappedService;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt
index 927df55..172f922 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt
@@ -110,11 +110,11 @@
                     viewModel.setBouncerViewDelegate(delegate)
                     launch {
                         viewModel.isShowing.collect { isShowing ->
+                            view.visibility = if (isShowing) View.VISIBLE else View.INVISIBLE
                             if (isShowing) {
                                 // Reset Security Container entirely.
                                 securityContainerController.reinflateViewFlipper {
                                     // Reset Security Container entirely.
-                                    view.visibility = View.VISIBLE
                                     securityContainerController.onBouncerVisibilityChanged(
                                         /* isVisible= */ true
                                     )
@@ -128,7 +128,6 @@
                                     )
                                 }
                             } else {
-                                view.visibility = View.INVISIBLE
                                 securityContainerController.onBouncerVisibilityChanged(
                                     /* isVisible= */ false
                                 )
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index fda2277..765c93e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -869,12 +869,9 @@
         // Walk down a precedence-ordered list of what indication
         // should be shown based on device state
         if (mDozing) {
+            boolean useMisalignmentColor = false;
             mLockScreenIndicationView.setVisibility(View.GONE);
             mTopIndicationView.setVisibility(VISIBLE);
-            // When dozing we ignore any text color and use white instead, because
-            // colors can be hard to read in low brightness.
-            mTopIndicationView.setTextColor(Color.WHITE);
-
             CharSequence newIndication;
             if (!TextUtils.isEmpty(mBiometricMessage)) {
                 newIndication = mBiometricMessage; // note: doesn't show mBiometricMessageFollowUp
@@ -885,8 +882,8 @@
                 mIndicationArea.setVisibility(GONE);
                 return;
             } else if (!TextUtils.isEmpty(mAlignmentIndication)) {
+                useMisalignmentColor = true;
                 newIndication = mAlignmentIndication;
-                mTopIndicationView.setTextColor(mContext.getColor(R.color.misalignment_text_color));
             } else if (mPowerPluggedIn || mEnableBatteryDefender) {
                 newIndication = computePowerIndication();
             } else {
@@ -896,7 +893,14 @@
 
             if (!TextUtils.equals(mTopIndicationView.getText(), newIndication)) {
                 mWakeLock.setAcquired(true);
-                mTopIndicationView.switchIndication(newIndication, null,
+                mTopIndicationView.switchIndication(newIndication,
+                        new KeyguardIndication.Builder()
+                                .setMessage(newIndication)
+                                .setTextColor(ColorStateList.valueOf(
+                                        useMisalignmentColor
+                                                ? mContext.getColor(R.color.misalignment_text_color)
+                                                : Color.WHITE))
+                                .build(),
                         true, () -> mWakeLock.setAcquired(false));
             }
             return;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
index bb037642..b2c2ae7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
@@ -21,6 +21,7 @@
 import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_KEYGUARD
 import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_OTHER
 import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_SETTINGS
+import android.hardware.biometrics.BiometricOverlayConstants.REASON_ENROLL_ENROLLING
 import android.hardware.biometrics.BiometricOverlayConstants.ShowReason
 import android.hardware.fingerprint.FingerprintManager
 import android.hardware.fingerprint.IUdfpsOverlayControllerCallback
@@ -29,6 +30,7 @@
 import android.view.LayoutInflater
 import android.view.MotionEvent
 import android.view.Surface
+import android.view.Surface.ROTATION_0
 import android.view.Surface.Rotation
 import android.view.View
 import android.view.WindowManager
@@ -42,6 +44,7 @@
 import com.android.systemui.animation.ActivityLaunchAnimator
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor
 import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor
 import com.android.systemui.plugins.statusbar.StatusBarStateController
@@ -159,9 +162,10 @@
     private fun withRotation(@Rotation rotation: Int, block: () -> Unit) {
         // Sensor that's in the top left corner of the display in natural orientation.
         val sensorBounds = Rect(0, 0, SENSOR_WIDTH, SENSOR_HEIGHT)
+        val overlayBounds = Rect(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT)
         overlayParams = UdfpsOverlayParams(
             sensorBounds,
-            sensorBounds,
+            overlayBounds,
             DISPLAY_WIDTH,
             DISPLAY_HEIGHT,
             scaleFactor = 1f,
@@ -314,4 +318,24 @@
         assertThat(controllerOverlay.matchesRequestId(REQUEST_ID)).isTrue()
         assertThat(controllerOverlay.matchesRequestId(REQUEST_ID + 1)).isFalse()
     }
+
+    @Test
+    fun smallOverlayOnEnrollmentWithA11y() = withRotation(ROTATION_0) {
+        withReason(REASON_ENROLL_ENROLLING) {
+            // When a11y enabled during enrollment
+            whenever(accessibilityManager.isTouchExplorationEnabled).thenReturn(true)
+            whenever(featureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)).thenReturn(true)
+
+            controllerOverlay.show(udfpsController, overlayParams)
+            verify(windowManager).addView(
+                eq(controllerOverlay.overlayView),
+                layoutParamsCaptor.capture()
+            )
+
+            // Layout params should use sensor bounds
+            val lp = layoutParamsCaptor.value
+            assertThat(lp.width).isEqualTo(overlayParams.sensorBounds.width())
+            assertThat(lp.height).isEqualTo(overlayParams.sensorBounds.height())
+        }
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
index 445cc87..edee3f1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
@@ -1344,6 +1344,46 @@
     }
 
     @Test
+    public void onTouch_withNewTouchDetection_pilferPointerWhenAltBouncerShowing()
+            throws RemoteException {
+        final NormalizedTouchData touchData = new NormalizedTouchData(0, 0f, 0f, 0f, 0f, 0f, 0L,
+                0L);
+        final TouchProcessorResult processorResultUnchanged =
+                new TouchProcessorResult.ProcessedTouch(InteractionEvent.UNCHANGED,
+                        1 /* pointerId */, touchData);
+
+        // Enable new touch detection.
+        when(mFeatureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)).thenReturn(true);
+
+        // Configure UdfpsController to use FingerprintManager as opposed to AlternateTouchProvider.
+        initUdfpsController(mOpticalProps, false /* hasAlternateTouchProvider */);
+
+        // Configure UdfpsView to not accept the ACTION_DOWN event
+        when(mUdfpsView.isDisplayConfigured()).thenReturn(false);
+        when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(false);
+
+        // GIVEN that the alternate bouncer is showing and a11y touch exploration NOT enabled
+        when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(false);
+        when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true);
+        mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId,
+                BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
+        mFgExecutor.runAllReady();
+
+        verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture());
+
+        // WHEN ACTION_DOWN is received and touch is not within sensor
+        when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn(
+                processorResultUnchanged);
+        MotionEvent downEvent = MotionEvent.obtain(0, 0, ACTION_DOWN, 0, 0, 0);
+        mTouchListenerCaptor.getValue().onTouch(mUdfpsView, downEvent);
+        mBiometricExecutor.runAllReady();
+        downEvent.recycle();
+
+        // THEN the touch is pilfered
+        verify(mInputManager, times(1)).pilferPointers(any());
+    }
+
+    @Test
     public void onAodInterrupt_onAcquiredGood_fingerNoLongerDown() throws RemoteException {
         // GIVEN UDFPS overlay is showing
         mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenStatePreventingAdapterTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenStatePreventingAdapterTest.java
index 6b3ec68..a7d7b93 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenStatePreventingAdapterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenStatePreventingAdapterTest.java
@@ -28,20 +28,29 @@
 
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.statusbar.phone.DozeParameters;
+import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.time.FakeSystemClock;
 
 import org.junit.Before;
 import org.junit.Test;
 
+import java.util.concurrent.Executor;
+
 @SmallTest
 public class DozeScreenStatePreventingAdapterTest extends SysuiTestCase {
 
+    private Executor mExecutor;
     private DozeMachine.Service mInner;
     private DozeScreenStatePreventingAdapter mWrapper;
 
     @Before
     public void setup() throws Exception {
+        mExecutor = new FakeExecutor(new FakeSystemClock());
         mInner = mock(DozeMachine.Service.class);
-        mWrapper = new DozeScreenStatePreventingAdapter(mInner);
+        mWrapper = new DozeScreenStatePreventingAdapter(
+                mInner,
+                mExecutor
+        );
     }
 
     @Test
@@ -86,7 +95,8 @@
         when(params.getDisplayStateSupported()).thenReturn(false);
 
         assertEquals(DozeScreenStatePreventingAdapter.class,
-                DozeScreenStatePreventingAdapter.wrapIfNeeded(mInner, params).getClass());
+                DozeScreenStatePreventingAdapter.wrapIfNeeded(mInner, params, mExecutor)
+                        .getClass());
     }
 
     @Test
@@ -94,6 +104,7 @@
         DozeParameters params = mock(DozeParameters.class);
         when(params.getDisplayStateSupported()).thenReturn(true);
 
-        assertSame(mInner, DozeScreenStatePreventingAdapter.wrapIfNeeded(mInner, params));
+        assertSame(mInner, DozeScreenStatePreventingAdapter.wrapIfNeeded(mInner, params,
+                mExecutor));
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSuspendScreenStatePreventingAdapterTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSuspendScreenStatePreventingAdapterTest.java
index 9ae7217..240d2d7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSuspendScreenStatePreventingAdapterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSuspendScreenStatePreventingAdapterTest.java
@@ -28,20 +28,26 @@
 
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.statusbar.phone.DozeParameters;
+import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.time.FakeSystemClock;
 
 import org.junit.Before;
 import org.junit.Test;
 
+import java.util.concurrent.Executor;
+
 @SmallTest
 public class DozeSuspendScreenStatePreventingAdapterTest extends SysuiTestCase {
 
+    private Executor mExecutor;
     private DozeMachine.Service mInner;
     private DozeSuspendScreenStatePreventingAdapter mWrapper;
 
     @Before
     public void setup() throws Exception {
+        mExecutor = new FakeExecutor(new FakeSystemClock());
         mInner = mock(DozeMachine.Service.class);
-        mWrapper = new DozeSuspendScreenStatePreventingAdapter(mInner);
+        mWrapper = new DozeSuspendScreenStatePreventingAdapter(mInner, mExecutor);
     }
 
     @Test
@@ -92,7 +98,8 @@
         when(params.getDozeSuspendDisplayStateSupported()).thenReturn(false);
 
         assertEquals(DozeSuspendScreenStatePreventingAdapter.class,
-                DozeSuspendScreenStatePreventingAdapter.wrapIfNeeded(mInner, params).getClass());
+                DozeSuspendScreenStatePreventingAdapter.wrapIfNeeded(mInner, params, mExecutor)
+                        .getClass());
     }
 
     @Test
@@ -100,6 +107,7 @@
         DozeParameters params = mock(DozeParameters.class);
         when(params.getDozeSuspendDisplayStateSupported()).thenReturn(true);
 
-        assertSame(mInner, DozeSuspendScreenStatePreventingAdapter.wrapIfNeeded(mInner, params));
+        assertSame(mInner, DozeSuspendScreenStatePreventingAdapter.wrapIfNeeded(mInner, params,
+                mExecutor));
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/TestUnfoldProgressListener.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/TestUnfoldProgressListener.kt
index 39ea46a..e2aef31 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/TestUnfoldProgressListener.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/TestUnfoldProgressListener.kt
@@ -126,7 +126,7 @@
         }
 
         fun assertLastProgress(progress: Float) {
-            waitForCondition { progress == progressHistory.last() }
+            waitForCondition { progress == progressHistory.lastOrNull() }
         }
     }
 
diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
index e159f18..7463061 100644
--- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
@@ -271,6 +271,14 @@
          */
         void onClientChangeLocked(boolean serviceInfoChanged);
 
+        /**
+         * Called back to notify the system the proxy client for a device has changed.
+         *
+         * Changes include if the proxy is unregistered, if its service info list has changed, or if
+         * its focus appearance has changed.
+         */
+        void onProxyChanged(int deviceId);
+
         int getCurrentUserIdLocked();
 
         Pair<float[], MagnificationSpec> getWindowTransformationMatrixAndMagnificationSpec(
@@ -315,8 +323,6 @@
 
         void attachAccessibilityOverlayToDisplay(int displayId, SurfaceControl sc);
 
-        void setCurrentUserFocusAppearance(int strokeWidth, int color);
-
     }
 
     public AbstractAccessibilityServiceConnection(Context context, ComponentName componentName,
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 5417009..51325e7 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -25,6 +25,9 @@
 import static android.accessibilityservice.AccessibilityTrace.FLAGS_USER_BROADCAST_RECEIVER;
 import static android.accessibilityservice.AccessibilityTrace.FLAGS_WINDOW_MAGNIFICATION_CONNECTION;
 import static android.accessibilityservice.AccessibilityTrace.FLAGS_WINDOW_MANAGER_INTERNAL;
+import static android.companion.virtual.VirtualDeviceManager.ACTION_VIRTUAL_DEVICE_REMOVED;
+import static android.companion.virtual.VirtualDeviceManager.EXTRA_VIRTUAL_DEVICE_ID;
+import static android.content.Context.DEVICE_ID_DEFAULT;
 import static android.provider.Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED;
 import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_BUTTON;
 import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_SHORTCUT_KEY;
@@ -189,7 +192,7 @@
         AccessibilityUserState.ServiceInfoChangeListener,
         AccessibilityWindowManager.AccessibilityEventSender,
         AccessibilitySecurityPolicy.AccessibilityUserManager,
-        SystemActionPerformer.SystemActionsChangedListener {
+        SystemActionPerformer.SystemActionsChangedListener, ProxyManager.SystemSupport{
 
     private static final boolean DEBUG = false;
 
@@ -471,7 +474,8 @@
                 new MagnificationScaleProvider(mContext));
         mMagnificationProcessor = new MagnificationProcessor(mMagnificationController);
         mCaptioningManagerImpl = new CaptioningManagerImpl(mContext);
-        mProxyManager = new ProxyManager(mLock, mA11yWindowManager, mContext);
+        mProxyManager = new ProxyManager(mLock, mA11yWindowManager, mContext, mMainHandler,
+                mUiAutomationManager, this);
         mFlashNotificationsController = new FlashNotificationsController(mContext);
         init();
     }
@@ -862,6 +866,19 @@
         };
         mContext.registerReceiverAsUser(receiver, UserHandle.ALL, filter, null, mMainHandler,
                 Context.RECEIVER_EXPORTED);
+
+        final BroadcastReceiver virtualDeviceReceiver = new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                final int deviceId = intent.getIntExtra(
+                        EXTRA_VIRTUAL_DEVICE_ID, DEVICE_ID_DEFAULT);
+                mProxyManager.clearConnections(deviceId);
+            }
+        };
+
+        final IntentFilter virtualDeviceFilter = new IntentFilter(ACTION_VIRTUAL_DEVICE_REMOVED);
+        mContext.registerReceiver(virtualDeviceReceiver, virtualDeviceFilter,
+                Context.RECEIVER_NOT_EXPORTED);
     }
 
     /**
@@ -940,21 +957,42 @@
             final int resolvedUserId = mSecurityPolicy
                     .resolveCallingUserIdEnforcingPermissionsLocked(userId);
 
+            AccessibilityUserState userState = getUserStateLocked(resolvedUserId);
+            // Support a process moving from the default device to a single virtual
+            // device.
+            final int deviceId = mProxyManager.getFirstDeviceIdForUidLocked(
+                    Binder.getCallingUid());
+            Client client = new Client(callback, Binder.getCallingUid(), userState, deviceId);
             // If the client is from a process that runs across users such as
             // the system UI or the system we add it to the global state that
             // is shared across users.
-            AccessibilityUserState userState = getUserStateLocked(resolvedUserId);
-            Client client = new Client(callback, Binder.getCallingUid(), userState);
             if (mSecurityPolicy.isCallerInteractingAcrossUsers(userId)) {
+                if (mProxyManager.isProxyedDeviceId(deviceId)) {
+                    if (DEBUG) {
+                        Slog.v(LOG_TAG, "Added global client for proxy-ed pid: "
+                                + Binder.getCallingPid() + " for device id " + deviceId
+                                + " with package names " + Arrays.toString(client.mPackageNames));
+                    }
+                    return IntPair.of(mProxyManager.getStateLocked(deviceId,
+                                    mUiAutomationManager.isUiAutomationRunningLocked()),
+                            client.mLastSentRelevantEventTypes);
+                }
                 mGlobalClients.register(callback, client);
                 if (DEBUG) {
                     Slog.i(LOG_TAG, "Added global client for pid:" + Binder.getCallingPid());
                 }
-                return IntPair.of(
-                        combineUserStateAndProxyState(getClientStateLocked(userState),
-                                mProxyManager.getStateLocked()),
-                        client.mLastSentRelevantEventTypes);
             } else {
+                // If the display belongs to a proxy connections
+                if (mProxyManager.isProxyedDeviceId(deviceId)) {
+                    if (DEBUG) {
+                        Slog.v(LOG_TAG, "Added user client for proxy-ed pid: "
+                                + Binder.getCallingPid() + " for device id " + deviceId
+                                + " with package names " + Arrays.toString(client.mPackageNames));
+                    }
+                    return IntPair.of(mProxyManager.getStateLocked(deviceId,
+                                    mUiAutomationManager.isUiAutomationRunningLocked()),
+                            client.mLastSentRelevantEventTypes);
+                }
                 userState.mUserClients.register(callback, client);
                 // If this client is not for the current user we do not
                 // return a state since it is not for the foreground user.
@@ -963,12 +1001,10 @@
                     Slog.i(LOG_TAG, "Added user client for pid:" + Binder.getCallingPid()
                             + " and userId:" + mCurrentUserId);
                 }
-                return IntPair.of(
-                        (resolvedUserId == mCurrentUserId) ? combineUserStateAndProxyState(
-                                getClientStateLocked(userState), mProxyManager.getStateLocked())
-                                : 0,
-                        client.mLastSentRelevantEventTypes);
             }
+            return IntPair.of(
+                    (resolvedUserId == mCurrentUserId) ? getClientStateLocked(userState) : 0,
+                    client.mLastSentRelevantEventTypes);
         }
     }
 
@@ -1094,7 +1130,7 @@
     }
 
     private void dispatchAccessibilityEventLocked(AccessibilityEvent event) {
-        if (mProxyManager.isProxyed(event.getDisplayId())) {
+        if (mProxyManager.isProxyedDisplay(event.getDisplayId())) {
             mProxyManager.sendAccessibilityEventLocked(event);
         } else {
             notifyAccessibilityServicesDelayedLocked(event, false);
@@ -1160,6 +1196,12 @@
         final int resolvedUserId;
         final List<AccessibilityServiceInfo> serviceInfos;
         synchronized (mLock) {
+            final int deviceId = mProxyManager.getFirstDeviceIdForUidLocked(
+                    Binder.getCallingUid());
+            if (mProxyManager.isProxyedDeviceId(deviceId)) {
+                return mProxyManager.getInstalledAndEnabledServiceInfosLocked(
+                        AccessibilityServiceInfo.FEEDBACK_ALL_MASK, deviceId);
+            }
             // We treat calls from a profile as if made by its parent as profiles
             // share the accessibility state of the parent. The call below
             // performs the current profile parent resolution.
@@ -1195,6 +1237,12 @@
         }
 
         synchronized (mLock) {
+            final int deviceId = mProxyManager.getFirstDeviceIdForUidLocked(
+                    Binder.getCallingUid());
+            if (mProxyManager.isProxyedDeviceId(deviceId)) {
+                return mProxyManager.getInstalledAndEnabledServiceInfosLocked(feedbackType,
+                        deviceId);
+            }
             // We treat calls from a profile as if made by its parent as profiles
             // share the accessibility state of the parent. The call below
             // performs the current profile parent resolution.
@@ -1239,19 +1287,25 @@
             if (resolvedUserId != mCurrentUserId) {
                 return;
             }
-            List<AccessibilityServiceConnection> services =
-                    getUserStateLocked(resolvedUserId).mBoundServices;
-            int numServices = services.size() + mProxyManager.getNumProxysLocked();
-            interfacesToInterrupt = new ArrayList<>(numServices);
-            for (int i = 0; i < services.size(); i++) {
-                AccessibilityServiceConnection service = services.get(i);
-                IBinder a11yServiceBinder = service.mService;
-                IAccessibilityServiceClient a11yServiceInterface = service.mServiceInterface;
-                if ((a11yServiceBinder != null) && (a11yServiceInterface != null)) {
-                    interfacesToInterrupt.add(a11yServiceInterface);
+
+            final int deviceId = mProxyManager.getFirstDeviceIdForUidLocked(
+                    Binder.getCallingUid());
+            if (mProxyManager.isProxyedDeviceId(deviceId)) {
+                interfacesToInterrupt = new ArrayList<>();
+                mProxyManager.addServiceInterfacesLocked(interfacesToInterrupt, deviceId);
+            } else {
+                List<AccessibilityServiceConnection> services =
+                        getUserStateLocked(resolvedUserId).mBoundServices;
+                interfacesToInterrupt = new ArrayList<>(services.size());
+                for (int i = 0; i < services.size(); i++) {
+                    AccessibilityServiceConnection service = services.get(i);
+                    IBinder a11yServiceBinder = service.mService;
+                    IAccessibilityServiceClient a11yServiceInterface = service.mServiceInterface;
+                    if ((a11yServiceBinder != null) && (a11yServiceInterface != null)) {
+                        interfacesToInterrupt.add(a11yServiceInterface);
+                    }
                 }
             }
-            mProxyManager.addServiceInterfacesLocked(interfacesToInterrupt);
         }
         for (int i = 0, count = interfacesToInterrupt.size(); i < count; i++) {
             try {
@@ -1827,7 +1881,8 @@
         return result;
     }
 
-    private void notifyClearAccessibilityCacheLocked() {
+    @Override
+    public void notifyClearAccessibilityCacheLocked() {
         AccessibilityUserState state = getCurrentUserStateLocked();
         for (int i = state.mBoundServices.size() - 1; i >= 0; i--) {
             AccessibilityServiceConnection service = state.mBoundServices.get(i);
@@ -2031,18 +2086,15 @@
         mMainHandler.post(() -> {
             broadcastToClients(userState, ignoreRemoteException(client -> {
                 int relevantEventTypes;
-                boolean changed = false;
                 synchronized (mLock) {
                     relevantEventTypes = computeRelevantEventTypesLocked(userState, client);
-
-                    if (client.mLastSentRelevantEventTypes != relevantEventTypes) {
-                        client.mLastSentRelevantEventTypes = relevantEventTypes;
-                        changed = true;
+                    if (!mProxyManager.isProxyedDeviceId(client.mDeviceId)) {
+                        if (client.mLastSentRelevantEventTypes != relevantEventTypes) {
+                            client.mLastSentRelevantEventTypes = relevantEventTypes;
+                            client.mCallback.setRelevantEventTypes(relevantEventTypes);
+                        }
                     }
                 }
-                if (changed) {
-                    client.mCallback.setRelevantEventTypes(relevantEventTypes);
-                }
             }));
         });
     }
@@ -2062,7 +2114,6 @@
                 mUiAutomationManager.getServiceInfo(), client)
                 ? mUiAutomationManager.getRelevantEventTypes()
                 : 0;
-        relevantEventTypes |= mProxyManager.getRelevantEventTypesLocked();
         return relevantEventTypes;
     }
 
@@ -2116,7 +2167,7 @@
         }
     }
 
-    private static boolean isClientInPackageAllowlist(
+    static boolean isClientInPackageAllowlist(
             @Nullable AccessibilityServiceInfo serviceInfo, Client client) {
         if (serviceInfo == null) return false;
 
@@ -2309,24 +2360,20 @@
         updateAccessibilityEnabledSettingLocked(userState);
     }
 
-    private int combineUserStateAndProxyState(int userState, int proxyState) {
-        return userState | proxyState;
+    void scheduleUpdateClientsIfNeededLocked(AccessibilityUserState userState) {
+        scheduleUpdateClientsIfNeededLocked(userState, false);
     }
 
-    void scheduleUpdateClientsIfNeededLocked(AccessibilityUserState userState) {
+    void scheduleUpdateClientsIfNeededLocked(AccessibilityUserState userState,
+            boolean forceUpdate) {
         final int clientState = getClientStateLocked(userState);
-        final int proxyState = mProxyManager.getStateLocked();
-        if ((userState.getLastSentClientStateLocked() != clientState
-                || mProxyManager.getLastSentStateLocked() != proxyState)
+        if (((userState.getLastSentClientStateLocked() != clientState || forceUpdate))
                 && (mGlobalClients.getRegisteredCallbackCount() > 0
                 || userState.mUserClients.getRegisteredCallbackCount() > 0)) {
             userState.setLastSentClientStateLocked(clientState);
-            mProxyManager.setLastStateLocked(proxyState);
-            // Send both the user and proxy state to the app for now.
-            // TODO(b/250929565): Send proxy state to proxy clients
             mMainHandler.sendMessage(obtainMessage(
                     AccessibilityManagerService::sendStateToAllClients,
-                    this, combineUserStateAndProxyState(clientState, proxyState),
+                    this, clientState,
                     userState.mUserId));
         }
     }
@@ -2346,8 +2393,13 @@
             mTraceManager.logTrace(LOG_TAG + ".sendStateToClients",
                     FLAGS_ACCESSIBILITY_MANAGER_CLIENT, "clientState=" + clientState);
         }
-        clients.broadcast(ignoreRemoteException(
-                client -> client.setState(clientState)));
+        clients.broadcastForEachCookie(ignoreRemoteException(
+                client -> {
+                    Client managerClient = ((Client) client);
+                    if (!mProxyManager.isProxyedDeviceId(managerClient.mDeviceId)) {
+                        managerClient.mCallback.setState(clientState);
+                    }
+                }));
     }
 
     private void scheduleNotifyClientsOfServicesStateChangeLocked(
@@ -2370,8 +2422,14 @@
             mTraceManager.logTrace(LOG_TAG + ".notifyClientsOfServicesStateChange",
                     FLAGS_ACCESSIBILITY_MANAGER_CLIENT, "uiTimeout=" + uiTimeout);
         }
-        clients.broadcast(ignoreRemoteException(
-                client -> client.notifyServicesStateChanged(uiTimeout)));
+
+        clients.broadcastForEachCookie(ignoreRemoteException(
+                client -> {
+                    Client managerClient = ((Client) client);
+                    if (!mProxyManager.isProxyedDeviceId(managerClient.mDeviceId)) {
+                        managerClient.mCallback.notifyServicesStateChanged(uiTimeout);
+                    }
+                }));
     }
 
     private void scheduleUpdateInputFilter(AccessibilityUserState userState) {
@@ -2444,7 +2502,6 @@
                     }
                     inputFilter = mInputFilter;
                     setInputFilter = true;
-                    mProxyManager.setAccessibilityInputFilter(mInputFilter);
                 }
                 mInputFilter.setUserAndEnabledFeatures(userState.mUserId, flags);
                 mInputFilter.setCombinedGenericMotionEventSources(
@@ -2477,6 +2534,7 @@
                         "inputFilter=" + inputFilter);
             }
             mWindowManagerService.setInputFilter(inputFilter);
+            mProxyManager.setAccessibilityInputFilter(inputFilter);
         }
     }
 
@@ -2541,6 +2599,20 @@
      * @param userState the new user state
      */
     private void onUserStateChangedLocked(AccessibilityUserState userState) {
+        onUserStateChangedLocked(userState, false);
+    }
+
+    /**
+     * Called when any property of the user state has changed.
+     *
+     * @param userState the new user state
+     * @param forceUpdate whether to force an update of the app Clients.
+     */
+    private void onUserStateChangedLocked(AccessibilityUserState userState, boolean forceUpdate) {
+        if (DEBUG) {
+            Slog.v(LOG_TAG, "onUserStateChangedLocked for user " + userState.mUserId + " with "
+                    + "forceUpdate: " + forceUpdate);
+        }
         // TODO: Remove this hack
         mInitialized = true;
         updateLegacyCapabilitiesLocked(userState);
@@ -2553,7 +2625,7 @@
         scheduleUpdateFingerprintGestureHandling(userState);
         scheduleUpdateInputFilter(userState);
         updateRelevantEventsLocked(userState);
-        scheduleUpdateClientsIfNeededLocked(userState);
+        scheduleUpdateClientsIfNeededLocked(userState, forceUpdate);
         updateAccessibilityShortcutKeyTargetsLocked(userState);
         updateAccessibilityButtonTargetsLocked(userState);
         // Update the capabilities before the mode because we will check the current mode is
@@ -2600,7 +2672,7 @@
             if (display != null) {
                 if (observingWindows) {
                     mA11yWindowManager.startTrackingWindows(display.getDisplayId(),
-                            mProxyManager.isProxyed(display.getDisplayId()));
+                            mProxyManager.isProxyedDisplay(display.getDisplayId()));
                 } else {
                     mA11yWindowManager.stopTrackingWindows(display.getDisplayId());
                 }
@@ -2867,6 +2939,8 @@
                 mContext.getContentResolver(),
                 Settings.Secure.ACCESSIBILITY_INTERACTIVE_UI_TIMEOUT_MS, 0,
                 userState.mUserId);
+
+        mProxyManager.updateTimeoutsIfNeeded(nonInteractiveUiTimeout, interactiveUiTimeout);
         if (nonInteractiveUiTimeout != userState.getUserNonInteractiveUiTimeoutLocked()
                 || interactiveUiTimeout != userState.getUserInteractiveUiTimeoutLocked()) {
             userState.setUserNonInteractiveUiTimeoutLocked(nonInteractiveUiTimeout);
@@ -3632,8 +3706,14 @@
         }
 
         synchronized(mLock) {
-            final AccessibilityUserState userState = getCurrentUserStateLocked();
-            return getRecommendedTimeoutMillisLocked(userState);
+            final int deviceId = mProxyManager.getFirstDeviceIdForUidLocked(
+                    Binder.getCallingUid());
+            if (mProxyManager.isProxyedDeviceId(deviceId)) {
+                return mProxyManager.getRecommendedTimeoutMillisLocked(deviceId);
+            } else {
+                final AccessibilityUserState userState = getCurrentUserStateLocked();
+                return getRecommendedTimeoutMillisLocked(userState);
+            }
         }
     }
 
@@ -3712,6 +3792,11 @@
             mTraceManager.logTrace(LOG_TAG + ".getFocusStrokeWidth", FLAGS_ACCESSIBILITY_MANAGER);
         }
         synchronized (mLock) {
+            final int deviceId = mProxyManager.getFirstDeviceIdForUidLocked(
+                    Binder.getCallingUid());
+            if (mProxyManager.isProxyedDeviceId(deviceId)) {
+                return mProxyManager.getFocusStrokeWidthLocked(deviceId);
+            }
             final AccessibilityUserState userState = getCurrentUserStateLocked();
 
             return userState.getFocusStrokeWidthLocked();
@@ -3728,6 +3813,11 @@
             mTraceManager.logTrace(LOG_TAG + ".getFocusColor", FLAGS_ACCESSIBILITY_MANAGER);
         }
         synchronized (mLock) {
+            final int deviceId = mProxyManager.getFirstDeviceIdForUidLocked(
+                    Binder.getCallingUid());
+            if (mProxyManager.isProxyedDeviceId(deviceId)) {
+                return mProxyManager.getFocusColorLocked(deviceId);
+            }
             final AccessibilityUserState userState = getCurrentUserStateLocked();
 
             return userState.getFocusColorLocked();
@@ -3814,9 +3904,9 @@
             throw new IllegalArgumentException("The display " + displayId + " does not exist or is"
                     + " not tracked by accessibility.");
         }
-        if (mProxyManager.isProxyed(displayId)) {
+        if (mProxyManager.isProxyedDisplay(displayId)) {
             throw new IllegalArgumentException("The display " + displayId + " is already being"
-                    + "proxy-ed");
+                    + " proxy-ed");
         }
 
         final long identity = Binder.clearCallingIdentity();
@@ -3847,7 +3937,7 @@
     }
 
     boolean isDisplayProxyed(int displayId) {
-        return mProxyManager.isProxyed(displayId);
+        return mProxyManager.isProxyedDisplay(displayId);
     }
 
     @Override
@@ -3995,13 +4085,71 @@
 
     @Override
     public void onClientChangeLocked(boolean serviceInfoChanged) {
+        onClientChangeLocked(serviceInfoChanged, false);
+    }
+
+    /**
+     * Called when the state of a service or proxy has changed
+     * @param serviceInfoChanged if the service info has changed
+     * @param forceUpdate whether to force an update of state for app clients
+     */
+    public void onClientChangeLocked(boolean serviceInfoChanged, boolean forceUpdate) {
         AccessibilityUserState userState = getUserStateLocked(mCurrentUserId);
-        onUserStateChangedLocked(userState);
+        onUserStateChangedLocked(userState, forceUpdate);
         if (serviceInfoChanged) {
             scheduleNotifyClientsOfServicesStateChangeLocked(userState);
         }
     }
 
+
+    @Override
+    public void onProxyChanged(int deviceId) {
+        mProxyManager.onProxyChanged(deviceId);
+    }
+
+    /**
+     * Removes the device from tracking. This will reset any AccessibilityManagerClients to be
+     * associated with the default user id.
+     */
+    @Override
+    public void removeDeviceIdLocked(int deviceId) {
+        resetClientsLocked(deviceId, getCurrentUserStateLocked().mUserClients);
+        resetClientsLocked(deviceId, mGlobalClients);
+        // Force an update of A11yManagers if the state was previously a proxy state and needs to be
+        // returned to the default device state.
+        onClientChangeLocked(true, true);
+    }
+
+    private void resetClientsLocked(int deviceId,
+            RemoteCallbackList<IAccessibilityManagerClient> clients) {
+        if (clients == null || clients.getRegisteredCallbackCount() == 0) {
+            return;
+        }
+        synchronized (mLock) {
+            for (int i = 0; i < clients.getRegisteredCallbackCount(); i++) {
+                final Client appClient = ((Client) clients.getRegisteredCallbackCookie(i));
+                if (appClient.mDeviceId == deviceId) {
+                    appClient.mDeviceId = DEVICE_ID_DEFAULT;
+                }
+            }
+        }
+    }
+
+    @Override
+    public void updateWindowsForAccessibilityCallbackLocked() {
+        updateWindowsForAccessibilityCallbackLocked(getUserStateLocked(mCurrentUserId));
+    }
+
+    @Override
+    public RemoteCallbackList<IAccessibilityManagerClient> getGlobalClientsLocked() {
+        return mGlobalClients;
+    }
+
+    @Override
+    public RemoteCallbackList<IAccessibilityManagerClient> getCurrentUserClientsLocked() {
+        return getCurrentUserState().mUserClients;
+    }
+
     @Override
     public void onShellCommand(FileDescriptor in, FileDescriptor out,
             FileDescriptor err, String[] args, ShellCallback callback,
@@ -4313,13 +4461,22 @@
         final IAccessibilityManagerClient mCallback;
         final String[] mPackageNames;
         int mLastSentRelevantEventTypes;
+        int mUid;
+        int mDeviceId = DEVICE_ID_DEFAULT;
 
         private Client(IAccessibilityManagerClient callback, int clientUid,
-                AccessibilityUserState userState) {
+                AccessibilityUserState userState, int deviceId) {
             mCallback = callback;
             mPackageNames = mPackageManager.getPackagesForUid(clientUid);
+            mUid = clientUid;
+            mDeviceId = deviceId;
             synchronized (mLock) {
-                mLastSentRelevantEventTypes = computeRelevantEventTypesLocked(userState, this);
+                if (mProxyManager.isProxyedDeviceId(deviceId)) {
+                    mLastSentRelevantEventTypes =
+                            mProxyManager.computeRelevantEventTypesLocked(this);
+                } else {
+                    mLastSentRelevantEventTypes = computeRelevantEventTypesLocked(userState, this);
+                }
             }
         }
     }
@@ -4805,8 +4962,10 @@
         }
         mMainHandler.post(() -> {
             broadcastToClients(userState, ignoreRemoteException(client -> {
-                client.mCallback.setFocusAppearance(userState.getFocusStrokeWidthLocked(),
-                        userState.getFocusColorLocked());
+                if (!mProxyManager.isProxyedDeviceId(client.mDeviceId)) {
+                    client.mCallback.setFocusAppearance(userState.getFocusStrokeWidthLocked(),
+                            userState.getFocusColorLocked());
+                }
             }));
         });
 
@@ -5041,11 +5200,4 @@
         transaction.apply();
         transaction.close();
     }
-
-    @Override
-    public void setCurrentUserFocusAppearance(int strokeWidth, int color) {
-        synchronized (mLock) {
-            getCurrentUserStateLocked().setFocusAppearanceLocked(strokeWidth, color);
-        }
-    }
 }
diff --git a/services/accessibility/java/com/android/server/accessibility/ProxyAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/ProxyAccessibilityServiceConnection.java
index b19a502..ab01fc3 100644
--- a/services/accessibility/java/com/android/server/accessibility/ProxyAccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/ProxyAccessibilityServiceConnection.java
@@ -67,6 +67,7 @@
 public class ProxyAccessibilityServiceConnection extends AccessibilityServiceConnection {
     private static final String LOG_TAG = "ProxyAccessibilityServiceConnection";
 
+    private int mDeviceId;
     private int mDisplayId;
     private List<AccessibilityServiceInfo> mInstalledAndEnabledServices;
 
@@ -75,6 +76,9 @@
     /** The color of the focus rectangle */
     private int mFocusColor;
 
+    private int mInteractiveTimeout;
+    private int mNonInteractiveTimeout;
+
     ProxyAccessibilityServiceConnection(
             Context context,
             ComponentName componentName,
@@ -83,7 +87,7 @@
             AccessibilitySecurityPolicy securityPolicy,
             SystemSupport systemSupport, AccessibilityTrace trace,
             WindowManagerInternal windowManagerInternal,
-            AccessibilityWindowManager awm, int displayId) {
+            AccessibilityWindowManager awm, int displayId, int deviceId) {
         super(/* userState= */null, context, componentName, accessibilityServiceInfo, id,
                 mainHandler, lock, securityPolicy, systemSupport, trace, windowManagerInternal,
                 /* systemActionPerformer= */ null, awm, /* activityTaskManagerService= */ null);
@@ -93,6 +97,14 @@
                 R.dimen.accessibility_focus_highlight_stroke_width);
         mFocusColor = mContext.getResources().getColor(
                 R.color.accessibility_focus_highlight_color);
+        mDeviceId = deviceId;
+    }
+
+    int getDisplayId() {
+        return mDisplayId;
+    }
+    int getDeviceId() {
+        return mDeviceId;
     }
 
     /**
@@ -155,6 +167,8 @@
                 proxyInfo.setAccessibilityTool(isAccessibilityTool);
                 proxyInfo.setInteractiveUiTimeoutMillis(interactiveUiTimeout);
                 proxyInfo.setNonInteractiveUiTimeoutMillis(nonInteractiveUiTimeout);
+                mInteractiveTimeout = interactiveUiTimeout;
+                mNonInteractiveTimeout = nonInteractiveUiTimeout;
 
                 // If any one service info doesn't set package names, i.e. if it's interested in all
                 // apps, the proxy shouldn't filter by package name even if some infos specify this.
@@ -167,7 +181,7 @@
                 // Update connection with mAccessibilityServiceInfo values.
                 setDynamicallyConfigurableProperties(proxyInfo);
                 // Notify manager service.
-                mSystemSupport.onClientChangeLocked(true);
+                mSystemSupport.onProxyChanged(mDeviceId);
             }
         } finally {
             Binder.restoreCallingIdentity(identity);
@@ -235,12 +249,7 @@
 
             mFocusStrokeWidth = strokeWidth;
             mFocusColor = color;
-            // Sets the appearance data in the A11yUserState for now, since the A11yManagers are not
-            // separated.
-            // TODO(254545943): Separate proxy and non-proxy states so the focus appearance on the
-            // phone is not affected by the appearance of a proxy-ed app.
-            mSystemSupport.setCurrentUserFocusAppearance(mFocusStrokeWidth, mFocusColor);
-            mSystemSupport.onClientChangeLocked(false);
+            mSystemSupport.onProxyChanged(mDeviceId);
         }
     }
 
@@ -572,17 +581,51 @@
         throw new UnsupportedOperationException("setAnimationScale is not supported");
     }
 
+    public int getInteractiveTimeout() {
+        return mInteractiveTimeout;
+    }
+
+    public int getNonInteractiveTimeout() {
+        return mNonInteractiveTimeout;
+    }
+
+    /**
+     * Returns true if a timeout was updated.
+     */
+    public boolean updateTimeouts(int nonInteractiveUiTimeout, int interactiveUiTimeout) {
+        final int newInteractiveUiTimeout = interactiveUiTimeout != 0
+                ? interactiveUiTimeout
+                : mAccessibilityServiceInfo.getInteractiveUiTimeoutMillis();
+        final int newNonInteractiveUiTimeout = nonInteractiveUiTimeout != 0
+                ? nonInteractiveUiTimeout
+                : mAccessibilityServiceInfo.getNonInteractiveUiTimeoutMillis();
+        boolean updated = false;
+
+        if (mInteractiveTimeout != newInteractiveUiTimeout) {
+            mInteractiveTimeout =  newInteractiveUiTimeout;
+            updated = true;
+        }
+        if (mNonInteractiveTimeout != newNonInteractiveUiTimeout) {
+            mNonInteractiveTimeout = newNonInteractiveUiTimeout;
+            updated = true;
+        }
+        return updated;
+    }
+
     @Override
     public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) {
         if (!DumpUtils.checkDumpPermission(mContext, LOG_TAG, pw)) return;
         synchronized (mLock) {
             pw.append("Proxy[displayId=" + mDisplayId);
+            pw.append(", deviceId=" + mDeviceId);
             pw.append(", feedbackType"
                     + AccessibilityServiceInfo.feedbackTypeToString(mFeedbackType));
             pw.append(", capabilities=" + mAccessibilityServiceInfo.getCapabilities());
             pw.append(", eventTypes="
                     + AccessibilityEvent.eventTypeToString(mEventTypes));
             pw.append(", notificationTimeout=" + mNotificationTimeout);
+            pw.append(", nonInteractiveUiTimeout=").append(String.valueOf(mNonInteractiveTimeout));
+            pw.append(", interactiveUiTimeout=").append(String.valueOf(mInteractiveTimeout));
             pw.append(", focusStrokeWidth=").append(String.valueOf(mFocusStrokeWidth));
             pw.append(", focusColor=").append(String.valueOf(mFocusColor));
             pw.append(", installedAndEnabledServiceCount=").append(String.valueOf(
diff --git a/services/accessibility/java/com/android/server/accessibility/ProxyManager.java b/services/accessibility/java/com/android/server/accessibility/ProxyManager.java
index e258de1..d417197 100644
--- a/services/accessibility/java/com/android/server/accessibility/ProxyManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/ProxyManager.java
@@ -14,26 +14,45 @@
  * limitations under the License.
  */
 package com.android.server.accessibility;
+
+import static android.content.Context.DEVICE_ID_DEFAULT;
+import static android.content.Context.DEVICE_ID_INVALID;
+
+import static com.android.internal.util.FunctionalUtils.ignoreRemoteException;
+
 import android.accessibilityservice.AccessibilityServiceInfo;
 import android.accessibilityservice.AccessibilityTrace;
 import android.accessibilityservice.IAccessibilityServiceClient;
+import android.annotation.NonNull;
+import android.companion.virtual.VirtualDeviceManager;
 import android.content.ComponentName;
 import android.content.Context;
 import android.hardware.display.DisplayManager;
 import android.os.Handler;
 import android.os.IBinder;
+import android.os.RemoteCallbackList;
 import android.os.RemoteException;
+import android.util.IntArray;
 import android.util.Slog;
 import android.util.SparseArray;
+import android.util.SparseIntArray;
 import android.view.Display;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.IAccessibilityManagerClient;
 
+import com.android.internal.util.IntPair;
+import com.android.server.LocalServices;
+import com.android.server.companion.virtual.VirtualDeviceManagerInternal;
 import com.android.server.wm.WindowManagerInternal;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
+import java.util.Set;
+import java.util.function.Consumer;
 
 /**
  * Manages proxy connections.
@@ -53,26 +72,72 @@
     static final String PROXY_COMPONENT_PACKAGE_NAME = "ProxyPackage";
     static final String PROXY_COMPONENT_CLASS_NAME = "ProxyClass";
 
+    // AMS#mLock
     private final Object mLock;
 
     private final Context mContext;
+    private final Handler mMainHandler;
 
-    // Used to determine if we should notify AccessibilityManager clients of updates.
-    // TODO(254545943): Separate this so each display id has its own state. Currently there is no
-    // way to identify from AccessibilityManager which proxy state should be returned.
-    private int mLastState = -1;
+    private final UiAutomationManager mUiAutomationManager;
 
-    private SparseArray<ProxyAccessibilityServiceConnection> mProxyA11yServiceConnections =
+    // Device Id -> state. Used to determine if we should notify AccessibilityManager clients of
+    // updates.
+    private final SparseIntArray mLastStates = new SparseIntArray();
+
+    // Each display id entry in a SparseArray represents a proxy a11y user.
+    private final SparseArray<ProxyAccessibilityServiceConnection> mProxyA11yServiceConnections =
             new SparseArray<>();
 
-    private AccessibilityWindowManager mA11yWindowManager;
+    private final AccessibilityWindowManager mA11yWindowManager;
 
     private AccessibilityInputFilter mA11yInputFilter;
 
-    ProxyManager(Object lock, AccessibilityWindowManager awm, Context context) {
+    private VirtualDeviceManagerInternal mLocalVdm;
+
+    private final SystemSupport mSystemSupport;
+
+    /**
+     * Callbacks into AccessibilityManagerService.
+     */
+    public interface SystemSupport {
+        /**
+         * Removes the device id from tracking.
+         */
+        void removeDeviceIdLocked(int deviceId);
+
+        /**
+         * Updates the windows tracking for the current user.
+         */
+        void updateWindowsForAccessibilityCallbackLocked();
+
+        /**
+         * Clears all caches.
+         */
+        void notifyClearAccessibilityCacheLocked();
+
+        /**
+         * Gets the clients for all users.
+         */
+        @NonNull
+        RemoteCallbackList<IAccessibilityManagerClient> getGlobalClientsLocked();
+
+        /**
+         * Gets the clients for the current user.
+         */
+        @NonNull
+        RemoteCallbackList<IAccessibilityManagerClient> getCurrentUserClientsLocked();
+    }
+
+    ProxyManager(Object lock, AccessibilityWindowManager awm,
+            Context context, Handler mainHandler, UiAutomationManager uiAutomationManager,
+            SystemSupport systemSupport) {
         mLock = lock;
         mA11yWindowManager = awm;
         mContext = context;
+        mMainHandler = mainHandler;
+        mUiAutomationManager = uiAutomationManager;
+        mSystemSupport = systemSupport;
+        mLocalVdm = LocalServices.getService(VirtualDeviceManagerInternal.class);
     }
 
     /**
@@ -89,6 +154,12 @@
             Slog.v(LOG_TAG, "Register proxy for display id: " + displayId);
         }
 
+        VirtualDeviceManager vdm = mContext.getSystemService(VirtualDeviceManager.class);
+        if (vdm == null) {
+            return;
+        }
+        final int deviceId = vdm.getDeviceIdForDisplayId(displayId);
+
         // Set a default AccessibilityServiceInfo that is used before the proxy's info is
         // populated. A proxy has the touch exploration and window capabilities.
         AccessibilityServiceInfo info = new AccessibilityServiceInfo();
@@ -101,7 +172,7 @@
                 new ProxyAccessibilityServiceConnection(context, info.getComponentName(), info,
                         id, mainHandler, mLock, securityPolicy, systemSupport, trace,
                         windowManagerInternal,
-                        mA11yWindowManager, displayId);
+                        mA11yWindowManager, displayId, deviceId);
 
         synchronized (mLock) {
             mProxyA11yServiceConnections.put(displayId, connection);
@@ -113,20 +184,16 @@
                     @Override
                     public void binderDied() {
                         client.asBinder().unlinkToDeath(this, 0);
-                        clearConnection(displayId);
+                        clearConnectionAndUpdateState(displayId);
                     }
                 };
         client.asBinder().linkToDeath(deathRecipient, 0);
 
-        // Notify apps that the service state has changed.
-        // A11yManager#A11yServicesStateChangeListener
-        synchronized (mLock) {
-            connection.mSystemSupport.onClientChangeLocked(true);
-        }
-
-        if (mA11yInputFilter != null) {
-            mA11yInputFilter.disableFeaturesForDisplayIfInstalled(displayId);
-        }
+        mMainHandler.post(() -> {
+            if (mA11yInputFilter != null) {
+                mA11yInputFilter.disableFeaturesForDisplayIfInstalled(displayId);
+            }
+        });
         connection.initializeServiceInterface(client);
     }
 
@@ -134,38 +201,101 @@
      * Unregister the proxy based on display id.
      */
     public boolean unregisterProxy(int displayId) {
-        return clearConnection(displayId);
-    }
-
-    private boolean clearConnection(int displayId) {
-        boolean removed = false;
-        synchronized (mLock) {
-            if (mProxyA11yServiceConnections.contains(displayId)) {
-                mProxyA11yServiceConnections.remove(displayId);
-                removed = true;
-                if (DEBUG) {
-                    Slog.v(LOG_TAG, "Unregister proxy for display id " + displayId);
-                }
-            }
-        }
-        if (removed) {
-            mA11yWindowManager.stopTrackingDisplayProxy(displayId);
-            if (mA11yInputFilter != null) {
-                final DisplayManager displayManager = (DisplayManager)
-                        mContext.getSystemService(Context.DISPLAY_SERVICE);
-                final Display proxyDisplay = displayManager.getDisplay(displayId);
-                if (proxyDisplay != null) {
-                    mA11yInputFilter.enableFeaturesForDisplayIfInstalled(proxyDisplay);
-                }
-            }
-        }
-        return removed;
+        return clearConnectionAndUpdateState(displayId);
     }
 
     /**
-     * Checks if a display id is being proxy-ed.
+     * Clears all proxy connections belonging to {@code deviceId}.
      */
-    public boolean isProxyed(int displayId) {
+    public void clearConnections(int deviceId) {
+        final IntArray displaysToClear = new IntArray();
+        synchronized (mLock) {
+            for (int i = 0; i < mProxyA11yServiceConnections.size(); i++) {
+                final ProxyAccessibilityServiceConnection proxy =
+                        mProxyA11yServiceConnections.valueAt(i);
+                if (proxy != null && proxy.getDeviceId() == deviceId) {
+                    displaysToClear.add(proxy.getDisplayId());
+                }
+            }
+        }
+        for (int i = 0; i < displaysToClear.size(); i++) {
+            clearConnectionAndUpdateState(displaysToClear.get(i));
+        }
+    }
+
+    /**
+     * Removes the system connection of an AccessibilityDisplayProxy.
+     *
+     * This will:
+     * <ul>
+     * <li> Reset Clients to belong to the default device if appropriate.
+     * <li> Stop identifying the display's a11y windows as belonging to a proxy.
+     * <li> Re-enable any input filters for the display.
+     * <li> Notify AMS that a proxy has been removed.
+     * </ul>
+     *
+     * @param displayId the display id of the connection to be cleared.
+     * @return whether the proxy was removed.
+     */
+    private boolean clearConnectionAndUpdateState(int displayId) {
+        boolean removedFromConnections = false;
+        int deviceId = DEVICE_ID_INVALID;
+        synchronized (mLock) {
+            if (mProxyA11yServiceConnections.contains(displayId)) {
+                deviceId = mProxyA11yServiceConnections.get(displayId).getDeviceId();
+                mProxyA11yServiceConnections.remove(displayId);
+                removedFromConnections = true;
+            }
+        }
+
+        if (removedFromConnections) {
+            updateStateForRemovedDisplay(displayId, deviceId);
+        }
+
+        if (DEBUG) {
+            Slog.v(LOG_TAG, "Unregistered proxy for display id " + displayId + ": "
+                    + removedFromConnections);
+        }
+        return removedFromConnections;
+    }
+
+    /**
+     * When the connection is removed from tracking in ProxyManager, propagate changes to other a11y
+     * system components like the input filter and IAccessibilityManagerClients.
+     */
+    public void updateStateForRemovedDisplay(int displayId, int deviceId) {
+        mA11yWindowManager.stopTrackingDisplayProxy(displayId);
+        // A11yInputFilter isn't thread-safe, so post on the system thread.
+        mMainHandler.post(
+                () -> {
+                    if (mA11yInputFilter != null) {
+                        final DisplayManager displayManager = (DisplayManager)
+                                mContext.getSystemService(Context.DISPLAY_SERVICE);
+                        final Display proxyDisplay = displayManager.getDisplay(displayId);
+                        if (proxyDisplay != null) {
+                            // A11yInputFilter isn't thread-safe, so post on the system thread.
+                            mA11yInputFilter.enableFeaturesForDisplayIfInstalled(proxyDisplay);
+                        }
+                    }
+                });
+        // If there isn't an existing proxy for the device id, reset clients. Resetting
+        // will usually happen, since in most cases there will only be one proxy for a
+        // device.
+        if (!isProxyedDeviceId(deviceId)) {
+            synchronized (mLock) {
+                mSystemSupport.removeDeviceIdLocked(deviceId);
+                mLastStates.delete(deviceId);
+            }
+        } else {
+            // Update with the states of the remaining proxies.
+            onProxyChanged(deviceId);
+        }
+    }
+
+    /**
+     * Returns {@code true} if {@code displayId} is being proxy-ed.
+     */
+    public boolean isProxyedDisplay(int displayId) {
         synchronized (mLock) {
             final boolean tracked = mProxyA11yServiceConnections.contains(displayId);
             if (DEBUG) {
@@ -176,6 +306,23 @@
     }
 
     /**
+     * Returns {@code true} if {@code deviceId} is being proxy-ed.
+     */
+    public boolean isProxyedDeviceId(int deviceId) {
+        if (deviceId == DEVICE_ID_DEFAULT && deviceId == DEVICE_ID_INVALID) {
+            return false;
+        }
+        boolean isTrackingDeviceId;
+        synchronized (mLock) {
+            isTrackingDeviceId = getFirstProxyForDeviceIdLocked(deviceId) != null;
+        }
+        if (DEBUG) {
+            Slog.v(LOG_TAG, "Tracking device " + deviceId + " : " + isTrackingDeviceId);
+        }
+        return isTrackingDeviceId;
+    }
+
+    /**
      * Sends AccessibilityEvents to a proxy given the event's displayId.
      */
     public void sendAccessibilityEventLocked(AccessibilityEvent event) {
@@ -213,15 +360,37 @@
     /**
      * If there is at least one proxy, accessibility is enabled.
      */
-    public int getStateLocked() {
+    public int getStateLocked(int deviceId, boolean automationRunning) {
         int clientState = 0;
-        final boolean a11yEnabled = mProxyA11yServiceConnections.size() > 0;
-        if (a11yEnabled) {
+        if (automationRunning) {
             clientState |= AccessibilityManager.STATE_FLAG_ACCESSIBILITY_ENABLED;
         }
         for (int i = 0; i < mProxyA11yServiceConnections.size(); i++) {
             final ProxyAccessibilityServiceConnection proxy =
                     mProxyA11yServiceConnections.valueAt(i);
+            if (proxy != null && proxy.getDeviceId() == deviceId) {
+                // Combine proxy states.
+                clientState |= getStateForDisplayIdLocked(proxy);
+            }
+        }
+
+        if (DEBUG) {
+            Slog.v(LOG_TAG, "For device id " + deviceId + " a11y is enabled: "
+                    + ((clientState & AccessibilityManager.STATE_FLAG_ACCESSIBILITY_ENABLED) != 0));
+            Slog.v(LOG_TAG, "For device id " + deviceId + " touch exploration is enabled: "
+                    + ((clientState & AccessibilityManager.STATE_FLAG_TOUCH_EXPLORATION_ENABLED)
+                            != 0));
+        }
+        return clientState;
+    }
+
+    /**
+     * If there is at least one proxy, accessibility is enabled.
+     */
+    public int getStateForDisplayIdLocked(ProxyAccessibilityServiceConnection proxy) {
+        int clientState = 0;
+        if (proxy != null) {
+            clientState |= AccessibilityManager.STATE_FLAG_ACCESSIBILITY_ENABLED;
             if (proxy.mRequestTouchExplorationMode) {
                 clientState |= AccessibilityManager.STATE_FLAG_TOUCH_EXPLORATION_ENABLED;
             }
@@ -235,61 +404,396 @@
                             != 0));
         }
         return clientState;
-        // TODO(b/254545943): When A11yManager is separated, include support for other properties.
     }
 
     /**
-     * Gets the last state.
+     * Gets the last state for a device.
      */
-    public int getLastSentStateLocked() {
-        return mLastState;
+    public int getLastSentStateLocked(int deviceId) {
+        return mLastStates.get(deviceId, 0);
     }
 
     /**
-     * Sets the last state.
+     * Sets the last state for a device.
      */
-    public void setLastStateLocked(int proxyState) {
-        mLastState = proxyState;
+    public void setLastStateLocked(int deviceId, int proxyState) {
+        mLastStates.put(deviceId, proxyState);
     }
 
     /**
-     * Returns the relevant event types of every proxy.
-     * TODO(254545943): When A11yManager is separated, return based on the A11yManager display.
+     * Updates the relevant event types of the app clients that are shown on a display owned by the
+     * specified device.
+     *
+     * A client belongs to a device id, so event types (and other state) is determined by the device
+     * id. In most cases, a device owns a single display. But if multiple displays may belong to one
+     * Virtual Device, the app clients will get the aggregated event types for all proxy-ed displays
+     * belonging to a VirtualDevice.
      */
-    public int getRelevantEventTypesLocked() {
+    public void updateRelevantEventTypesLocked(int deviceId) {
+        if (!isProxyedDeviceId(deviceId)) {
+            return;
+        }
+        mMainHandler.post(() -> {
+            synchronized (mLock) {
+                broadcastToClientsLocked(ignoreRemoteException(client -> {
+                    int relevantEventTypes;
+                    if (client.mDeviceId == deviceId) {
+                        relevantEventTypes = computeRelevantEventTypesLocked(client);
+                        if (client.mLastSentRelevantEventTypes != relevantEventTypes) {
+                            client.mLastSentRelevantEventTypes = relevantEventTypes;
+                            client.mCallback.setRelevantEventTypes(relevantEventTypes);
+                        }
+                    }
+                }));
+            }
+        });
+    }
+
+    /**
+     * Returns the relevant event types for a Client.
+     */
+    int computeRelevantEventTypesLocked(AccessibilityManagerService.Client client) {
         int relevantEventTypes = 0;
         for (int i = 0; i < mProxyA11yServiceConnections.size(); i++) {
-            ProxyAccessibilityServiceConnection proxy =
+            final ProxyAccessibilityServiceConnection proxy =
                     mProxyA11yServiceConnections.valueAt(i);
-            relevantEventTypes |= proxy.getRelevantEventTypes();
+            if (proxy != null && proxy.getDeviceId() == client.mDeviceId) {
+                relevantEventTypes |= proxy.getRelevantEventTypes();
+                relevantEventTypes |= AccessibilityManagerService.isClientInPackageAllowlist(
+                        mUiAutomationManager.getServiceInfo(), client)
+                        ? mUiAutomationManager.getRelevantEventTypes()
+                        : 0;
+            }
         }
         if (DEBUG) {
-            Slog.v(LOG_TAG, "Relevant event types for all proxies: "
-                    + AccessibilityEvent.eventTypeToString(relevantEventTypes));
+            Slog.v(LOG_TAG, "Relevant event types for device id " + client.mDeviceId
+                    + ": " + AccessibilityEvent.eventTypeToString(relevantEventTypes));
         }
         return relevantEventTypes;
     }
 
     /**
-     * Gets the number of current proxy connections.
-     * @return
-     */
-    public int getNumProxysLocked() {
-        return mProxyA11yServiceConnections.size();
-    }
-
-    /**
      * Adds the service interfaces to a list.
-     * @param interfaces
+     * @param interfaces the list to add to.
+     * @param deviceId the device id of the interested app client.
      */
-    public void addServiceInterfacesLocked(List<IAccessibilityServiceClient> interfaces) {
+    public void addServiceInterfacesLocked(@NonNull List<IAccessibilityServiceClient> interfaces,
+            int deviceId) {
         for (int i = 0; i < mProxyA11yServiceConnections.size(); i++) {
             final ProxyAccessibilityServiceConnection proxy =
                     mProxyA11yServiceConnections.valueAt(i);
-            final IBinder proxyBinder = proxy.mService;
-            final IAccessibilityServiceClient proxyInterface = proxy.mServiceInterface;
-            if ((proxyBinder != null) && (proxyInterface != null)) {
-                interfaces.add(proxyInterface);
+            if (proxy != null && proxy.getDeviceId() == deviceId) {
+                final IBinder proxyBinder = proxy.mService;
+                final IAccessibilityServiceClient proxyInterface = proxy.mServiceInterface;
+                if ((proxyBinder != null) && (proxyInterface != null)) {
+                    interfaces.add(proxyInterface);
+                }
+            }
+        }
+    }
+
+    /**
+     * Gets the list of installed and enabled services for a device id.
+     *
+     * Note: Multiple display proxies may belong to the same device.
+     */
+    public List<AccessibilityServiceInfo> getInstalledAndEnabledServiceInfosLocked(int feedbackType,
+            int deviceId) {
+        List<AccessibilityServiceInfo> serviceInfos = new ArrayList<>();
+        for (int i = 0; i < mProxyA11yServiceConnections.size(); i++) {
+            final ProxyAccessibilityServiceConnection proxy =
+                    mProxyA11yServiceConnections.valueAt(i);
+            if (proxy != null && proxy.getDeviceId() == deviceId) {
+                // Return all proxy infos for ALL mask.
+                if (feedbackType == AccessibilityServiceInfo.FEEDBACK_ALL_MASK) {
+                    serviceInfos.addAll(proxy.getInstalledAndEnabledServices());
+                } else if ((proxy.mFeedbackType & feedbackType) != 0) {
+                    List<AccessibilityServiceInfo> proxyInfos =
+                            proxy.getInstalledAndEnabledServices();
+                    // Iterate through each info in the proxy.
+                    for (AccessibilityServiceInfo info : proxyInfos) {
+                        if ((info.feedbackType & feedbackType) != 0) {
+                            serviceInfos.add(info);
+                        }
+                    }
+                }
+            }
+        }
+        return serviceInfos;
+    }
+
+    /**
+     * Handles proxy changes.
+     *
+     * <p>
+     * Changes include if the proxy is unregistered, its service info list has
+     * changed, or its focus appearance has changed.
+     * <p>
+     * Some responses may include updating app clients. A client belongs to a device id, so state is
+     * determined by the device id. In most cases, a device owns a single display. But if multiple
+     * displays belong to one Virtual Device, the app clients will get a difference in
+     * behavior depending on what is being updated.
+     *
+     * The following state methods are updated for AccessibilityManager clients belonging to a
+     * proxied device:
+     * <ul>
+     * <li> A11yManager#setRelevantEventTypes - The combined event types of all proxies belonging to
+     * a device id.
+     * <li> A11yManager#setState - The combined states of all proxies belonging to a device id.
+     * <li> A11yManager#notifyServicesStateChanged(timeout) - The highest of all proxies belonging
+     * to a device id.
+     * <li> A11yManager#setFocusAppearance - The appearance of the most recently updated display id
+     * belonging to the device.
+     * </ul>
+     * This is similar to onUserStateChangeLocked and onClientChangeLocked, but does not require an
+     * A11yUserState and only checks proxy-relevant settings.
+     */
+    public void onProxyChanged(int deviceId) {
+        if (DEBUG) {
+            Slog.v(LOG_TAG, "onProxyChanged called for deviceId: " + deviceId);
+        }
+        //The following state updates are excluded:
+        //  - Input-related state
+        //  - Primary-device / hardware-specific state
+        synchronized (mLock) {
+            // A proxy may be registered after the client has been initialized in #addClient.
+            // For example, a user does not turn on accessibility until after the app has launched.
+            // Or the process was started with a default id context and should shift to a device.
+            // Update device ids of the clients if necessary.
+            updateDeviceIdsIfNeededLocked(deviceId);
+            // Start tracking of all displays if necessary.
+            mSystemSupport.updateWindowsForAccessibilityCallbackLocked();
+            // Calls A11yManager#setRelevantEventTypes (test these)
+            updateRelevantEventTypesLocked(deviceId);
+            // Calls A11yManager#setState
+            scheduleUpdateProxyClientsIfNeededLocked(deviceId);
+            //Calls A11yManager#notifyServicesStateChanged(timeout)
+            scheduleNotifyProxyClientsOfServicesStateChangeLocked(deviceId);
+            // Calls A11yManager#setFocusAppearance
+            updateFocusAppearanceLocked(deviceId);
+            mSystemSupport.notifyClearAccessibilityCacheLocked();
+        }
+    }
+
+    /**
+     * Updates the states of the app AccessibilityManagers.
+     */
+    public void scheduleUpdateProxyClientsIfNeededLocked(int deviceId) {
+        final int proxyState = getStateLocked(deviceId,
+                mUiAutomationManager.isUiAutomationRunningLocked());
+        if (DEBUG) {
+            Slog.v(LOG_TAG, "State for device id " + deviceId + " is " + proxyState);
+            Slog.v(LOG_TAG, "Last state for device id " + deviceId + " is "
+                    + getLastSentStateLocked(deviceId));
+        }
+        if ((getLastSentStateLocked(deviceId)) != proxyState) {
+            setLastStateLocked(deviceId, proxyState);
+            mMainHandler.post(() -> {
+                synchronized (mLock) {
+                    broadcastToClientsLocked(ignoreRemoteException(client -> {
+                        if (client.mDeviceId == deviceId) {
+                            client.mCallback.setState(proxyState);
+                        }
+                    }));
+                }
+            });
+        }
+    }
+
+    /**
+     * Notifies AccessibilityManager of services state changes, which includes changes to the
+     * list of service infos and timeouts.
+     *
+     * @see AccessibilityManager.AccessibilityServicesStateChangeListener
+     */
+    public void scheduleNotifyProxyClientsOfServicesStateChangeLocked(int deviceId) {
+        if (DEBUG) {
+            Slog.v(LOG_TAG, "Notify services state change at device id " + deviceId);
+        }
+        mMainHandler.post(()-> {
+            broadcastToClientsLocked(ignoreRemoteException(client -> {
+                if (client.mDeviceId == deviceId) {
+                    synchronized (mLock) {
+                        client.mCallback.notifyServicesStateChanged(
+                                getRecommendedTimeoutMillisLocked(deviceId));
+                    }
+                }
+            }));
+        });
+    }
+
+    /**
+     * Updates the focus appearance of AccessibilityManagerClients.
+     */
+    public void updateFocusAppearanceLocked(int deviceId) {
+        if (DEBUG) {
+            Slog.v(LOG_TAG, "Update proxy focus appearance at device id " + deviceId);
+        }
+        // Reasonably assume that all proxies belonging to a virtual device should have the
+        // same focus appearance, and if they should be different these should belong to different
+        // virtual devices.
+        final ProxyAccessibilityServiceConnection proxy = getFirstProxyForDeviceIdLocked(deviceId);
+        if (proxy != null) {
+            mMainHandler.post(()-> {
+                broadcastToClientsLocked(ignoreRemoteException(client -> {
+                    if (client.mDeviceId == proxy.getDeviceId()) {
+                        client.mCallback.setFocusAppearance(
+                                proxy.getFocusStrokeWidthLocked(),
+                                proxy.getFocusColorLocked());
+                    }
+                }));
+            });
+        }
+    }
+
+    private ProxyAccessibilityServiceConnection getFirstProxyForDeviceIdLocked(int deviceId) {
+        for (int i = 0; i < mProxyA11yServiceConnections.size(); i++) {
+            final ProxyAccessibilityServiceConnection proxy =
+                    mProxyA11yServiceConnections.valueAt(i);
+            if (proxy != null && proxy.getDeviceId() == deviceId) {
+                return proxy;
+            }
+        }
+        return null;
+    }
+
+    private void broadcastToClientsLocked(
+            @NonNull Consumer<AccessibilityManagerService.Client> clientAction) {
+        final RemoteCallbackList<IAccessibilityManagerClient> userClients =
+                mSystemSupport.getCurrentUserClientsLocked();
+        final RemoteCallbackList<IAccessibilityManagerClient> globalClients =
+                mSystemSupport.getGlobalClientsLocked();
+        userClients.broadcastForEachCookie(clientAction);
+        globalClients.broadcastForEachCookie(clientAction);
+    }
+
+    /**
+     * Updates the timeout and notifies app clients.
+     *
+     * For real users, timeouts are tracked in A11yUserState. For proxies, timeouts are in the
+     * service connection. The value in user state is preferred, but if this value is 0 the service
+     * info value is used.
+     *
+     * This follows the pattern in readUserRecommendedUiTimeoutSettingsLocked.
+     *
+     * TODO(b/250929565): ProxyUserState or similar should hold the timeouts
+     */
+    public void updateTimeoutsIfNeeded(int nonInteractiveUiTimeout, int interactiveUiTimeout) {
+        synchronized (mLock) {
+            for (int i = 0; i < mProxyA11yServiceConnections.size(); i++) {
+                final ProxyAccessibilityServiceConnection proxy =
+                        mProxyA11yServiceConnections.valueAt(i);
+                if (proxy != null) {
+                    if (proxy.updateTimeouts(nonInteractiveUiTimeout, interactiveUiTimeout)) {
+                        scheduleNotifyProxyClientsOfServicesStateChangeLocked(proxy.getDeviceId());
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Gets the recommended timeout belonging to a Virtual Device.
+     *
+     * This is the highest of all display proxies belonging to the virtual device.
+     */
+    public long getRecommendedTimeoutMillisLocked(int deviceId) {
+        int combinedInteractiveTimeout = 0;
+        int combinedNonInteractiveTimeout = 0;
+        for (int i = 0; i < mProxyA11yServiceConnections.size(); i++) {
+            final ProxyAccessibilityServiceConnection proxy =
+                    mProxyA11yServiceConnections.valueAt(i);
+            if (proxy != null && proxy.getDeviceId() == deviceId) {
+                final int proxyInteractiveUiTimeout =
+                        (proxy != null) ? proxy.getInteractiveTimeout() : 0;
+                final int nonInteractiveUiTimeout =
+                        (proxy != null) ? proxy.getNonInteractiveTimeout() : 0;
+                combinedInteractiveTimeout = Math.max(proxyInteractiveUiTimeout,
+                        combinedInteractiveTimeout);
+                combinedNonInteractiveTimeout = Math.max(nonInteractiveUiTimeout,
+                        combinedNonInteractiveTimeout);
+            }
+        }
+        return IntPair.of(combinedInteractiveTimeout, combinedNonInteractiveTimeout);
+    }
+
+    /**
+     * Gets the first focus stroke width belonging to the device.
+     */
+    public int getFocusStrokeWidthLocked(int deviceId) {
+        final ProxyAccessibilityServiceConnection proxy = getFirstProxyForDeviceIdLocked(deviceId);
+        if (proxy != null) {
+            return proxy.getFocusStrokeWidthLocked();
+        }
+        return 0;
+
+    }
+
+    /**
+     * Gets the first focus color belonging to the device.
+     */
+    public int getFocusColorLocked(int deviceId) {
+        final ProxyAccessibilityServiceConnection proxy = getFirstProxyForDeviceIdLocked(deviceId);
+        if (proxy != null) {
+            return proxy.getFocusColorLocked();
+        }
+        return 0;
+    }
+
+    /**
+     * Returns the first device id given a UID.
+     * @param callingUid the UID to check.
+     * @return the first matching device id, or DEVICE_ID_INVALID.
+     */
+    public int getFirstDeviceIdForUidLocked(int callingUid) {
+        int firstDeviceId = DEVICE_ID_INVALID;
+        final VirtualDeviceManagerInternal localVdm = getLocalVdm();
+        if (localVdm == null) {
+            return firstDeviceId;
+        }
+        final Set<Integer> deviceIds = localVdm.getDeviceIdsForUid(callingUid);
+        for (Integer uidDeviceId : deviceIds) {
+            if (uidDeviceId != DEVICE_ID_DEFAULT && uidDeviceId != DEVICE_ID_INVALID) {
+                firstDeviceId = uidDeviceId;
+                break;
+            }
+        }
+        return firstDeviceId;
+    }
+
+    /**
+     * Sets a Client device id if the app uid belongs to the virtual device.
+     */
+    public void updateDeviceIdsIfNeededLocked(int deviceId) {
+        final RemoteCallbackList<IAccessibilityManagerClient> userClients =
+                mSystemSupport.getCurrentUserClientsLocked();
+        final RemoteCallbackList<IAccessibilityManagerClient> globalClients =
+                mSystemSupport.getGlobalClientsLocked();
+
+        updateDeviceIdsIfNeededLocked(deviceId, userClients);
+        updateDeviceIdsIfNeededLocked(deviceId, globalClients);
+    }
+
+    /**
+     * Updates the device ids of IAccessibilityManagerClients if needed.
+     */
+    public void updateDeviceIdsIfNeededLocked(int deviceId,
+            @NonNull RemoteCallbackList<IAccessibilityManagerClient> clients) {
+        final VirtualDeviceManagerInternal localVdm = getLocalVdm();
+        if (localVdm == null) {
+            return;
+        }
+
+        for (int i = 0; i < clients.getRegisteredCallbackCount(); i++) {
+            final AccessibilityManagerService.Client client =
+                    ((AccessibilityManagerService.Client) clients.getRegisteredCallbackCookie(i));
+            if (deviceId != DEVICE_ID_DEFAULT && deviceId != DEVICE_ID_INVALID
+                    && localVdm.getDeviceIdsForUid(client.mUid).contains(deviceId)) {
+                if (DEBUG) {
+                    Slog.v(LOG_TAG, "Packages moved to device id " + deviceId + " are "
+                            + Arrays.toString(client.mPackageNames));
+                }
+                client.mDeviceId = deviceId;
             }
         }
     }
@@ -306,9 +810,18 @@
     }
 
     void setAccessibilityInputFilter(AccessibilityInputFilter filter) {
+        if (DEBUG) {
+            Slog.v(LOG_TAG, "Set proxy input filter to " + filter);
+        }
         mA11yInputFilter = filter;
     }
 
+    VirtualDeviceManagerInternal getLocalVdm() {
+        if (mLocalVdm == null) {
+            mLocalVdm =  LocalServices.getService(VirtualDeviceManagerInternal.class);
+        }
+        return mLocalVdm;
+    }
 
     /**
      * Prints information belonging to each display that is controlled by an
@@ -320,13 +833,38 @@
             pw.println("Proxy manager state:");
             pw.println("    Number of proxy connections: " + mProxyA11yServiceConnections.size());
             pw.println("    Registered proxy connections:");
+            final RemoteCallbackList<IAccessibilityManagerClient> userClients =
+                    mSystemSupport.getCurrentUserClientsLocked();
+            final RemoteCallbackList<IAccessibilityManagerClient> globalClients =
+                    mSystemSupport.getGlobalClientsLocked();
             for (int i = 0; i < mProxyA11yServiceConnections.size(); i++) {
                 final ProxyAccessibilityServiceConnection proxy =
                         mProxyA11yServiceConnections.valueAt(i);
                 if (proxy != null) {
                     proxy.dump(fd, pw, args);
                 }
+                pw.println();
+                pw.println("        User clients for proxy's virtual device id");
+                printClientsForDeviceId(pw, userClients, proxy.getDeviceId());
+                pw.println();
+                pw.println("        Global clients for proxy's virtual device id");
+                printClientsForDeviceId(pw, globalClients, proxy.getDeviceId());
+
             }
         }
     }
-}
\ No newline at end of file
+
+    private void printClientsForDeviceId(PrintWriter pw,
+            RemoteCallbackList<IAccessibilityManagerClient> clients, int deviceId) {
+        if (clients != null) {
+            for (int j = 0; j < clients.getRegisteredCallbackCount(); j++) {
+                final AccessibilityManagerService.Client client =
+                        (AccessibilityManagerService.Client)
+                                clients.getRegisteredCallbackCookie(j);
+                if (client.mDeviceId == deviceId) {
+                    pw.println("            " + Arrays.toString(client.mPackageNames) + "\n");
+                }
+            }
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 0cd5577..ef7d5ae 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -13649,8 +13649,9 @@
                                 || action.startsWith("android.intent.action.UID_")
                                 || action.startsWith("android.intent.action.EXTERNAL_")) {
                             if (DEBUG_BROADCAST) {
-                                Slog.wtf(TAG, "System internals registering for " + filter
-                                        + " with app priority; this will race with apps!",
+                                Slog.wtf(TAG,
+                                        "System internals registering for " + filter.toLongString()
+                                                + " with app priority; this will race with apps!",
                                         new Throwable());
                             }
 
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index 6c6ac1e..350ac3b 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -1095,7 +1095,7 @@
                 synchronized (mInternal.mProcLock) {
                     mInternal.mOomAdjuster.mCachedAppOptimizer.compactApp(app,
                             CachedAppOptimizer.CompactProfile.FULL,
-                            CachedAppOptimizer.CompactSource.APP, true);
+                            CachedAppOptimizer.CompactSource.SHELL, true);
                 }
                 pw.println("Finished full compaction for " + app.mPid);
             } else if (isSomeCompact) {
@@ -1103,7 +1103,7 @@
                 synchronized (mInternal.mProcLock) {
                     mInternal.mOomAdjuster.mCachedAppOptimizer.compactApp(app,
                             CachedAppOptimizer.CompactProfile.SOME,
-                            CachedAppOptimizer.CompactSource.APP, true);
+                            CachedAppOptimizer.CompactSource.SHELL, true);
                 }
                 pw.println("Finished some compaction for " + app.mPid);
             }
diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java
index 192a2ba..9c15463 100644
--- a/services/core/java/com/android/server/am/CachedAppOptimizer.java
+++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java
@@ -226,8 +226,8 @@
         FULL // File+anon compaction
     }
 
-    // This indicates the process OOM memory state that initiated the compaction request
-    public enum CompactSource { APP, PERSISTENT, BFGS }
+    // This indicates who initiated the compaction request
+    public enum CompactSource { APP, SHELL }
 
     public enum CancelCompactReason {
         SCREEN_ON, // screen was turned on which cancels all compactions.
@@ -373,10 +373,6 @@
     @GuardedBy("mPhenotypeFlagLock")
     @VisibleForTesting volatile long mCompactThrottleFullFull = DEFAULT_COMPACT_THROTTLE_4;
     @GuardedBy("mPhenotypeFlagLock")
-    @VisibleForTesting volatile long mCompactThrottleBFGS = DEFAULT_COMPACT_THROTTLE_5;
-    @GuardedBy("mPhenotypeFlagLock")
-    @VisibleForTesting volatile long mCompactThrottlePersistent = DEFAULT_COMPACT_THROTTLE_6;
-    @GuardedBy("mPhenotypeFlagLock")
     @VisibleForTesting volatile long mCompactThrottleMinOomAdj =
             DEFAULT_COMPACT_THROTTLE_MIN_OOM_ADJ;
     @GuardedBy("mPhenotypeFlagLock")
@@ -635,8 +631,6 @@
             pw.println("  " + KEY_COMPACT_THROTTLE_2 + "=" + mCompactThrottleSomeFull);
             pw.println("  " + KEY_COMPACT_THROTTLE_3 + "=" + mCompactThrottleFullSome);
             pw.println("  " + KEY_COMPACT_THROTTLE_4 + "=" + mCompactThrottleFullFull);
-            pw.println("  " + KEY_COMPACT_THROTTLE_5 + "=" + mCompactThrottleBFGS);
-            pw.println("  " + KEY_COMPACT_THROTTLE_6 + "=" + mCompactThrottlePersistent);
             pw.println("  " + KEY_COMPACT_THROTTLE_MIN_OOM_ADJ + "=" + mCompactThrottleMinOomAdj);
             pw.println("  " + KEY_COMPACT_THROTTLE_MAX_OOM_ADJ + "=" + mCompactThrottleMaxOomAdj);
             pw.println("  " + KEY_COMPACT_STATSD_SAMPLE_RATE + "=" + mCompactStatsdSampleRate);
@@ -728,32 +722,6 @@
         }
     }
 
-    // This method returns true only if requirements are met. Note, that requirements are different
-    // from throttles applied at the time a compaction is trying to be executed in the sense that
-    // these are not subject to change dependent on time or memory as throttles usually do.
-    @GuardedBy("mProcLock")
-    boolean meetsCompactionRequirements(ProcessRecord proc) {
-        if (mAm.mInternal.isPendingTopUid(proc.uid)) {
-            // In case the OOM Adjust has not yet been propagated we see if this is
-            // pending on becoming top app in which case we should not compact.
-            if (DEBUG_COMPACTION) {
-                Slog.d(TAG_AM, "Skip compaction since UID is active for  " + proc.processName);
-            }
-            return false;
-        }
-
-        if (proc.mState.hasForegroundActivities()) {
-            if (DEBUG_COMPACTION) {
-                Slog.e(TAG_AM,
-                        "Skip compaction as process " + proc.processName
-                                + " has foreground activities");
-            }
-            return false;
-        }
-
-        return true;
-    }
-
     @GuardedBy("mProcLock")
     boolean compactApp(
             ProcessRecord app, CompactProfile compactProfile, CompactSource source, boolean force) {
@@ -777,7 +745,7 @@
                 return false;
         }
 
-        if (!app.mOptRecord.hasPendingCompact() && (meetsCompactionRequirements(app) || force)) {
+        if (!app.mOptRecord.hasPendingCompact()) {
             final String processName = (app.processName != null ? app.processName : "");
             if (DEBUG_COMPACTION) {
                 Slog.d(TAG_AM,
@@ -795,8 +763,7 @@
         if (DEBUG_COMPACTION) {
             Slog.d(TAG_AM,
                     " compactApp Skipped for " + app.processName + " pendingCompact= "
-                            + app.mOptRecord.hasPendingCompact() + " meetsCompactionRequirements="
-                            + meetsCompactionRequirements(app) + ". Requested compact profile: "
+                            + app.mOptRecord.hasPendingCompact() + ". Requested compact profile: "
                             + app.mOptRecord.getReqCompactProfile().name() + ". Compact source "
                             + app.mOptRecord.getReqCompactSource().name());
         }
@@ -831,18 +798,6 @@
         return stats;
     }
 
-    @GuardedBy("mProcLock")
-    boolean shouldCompactPersistent(ProcessRecord app, long now) {
-        return (app.mOptRecord.getLastCompactTime() == 0
-                || (now - app.mOptRecord.getLastCompactTime()) > mCompactThrottlePersistent);
-    }
-
-    @GuardedBy("mProcLock")
-    boolean shouldCompactBFGS(ProcessRecord app, long now) {
-        return (app.mOptRecord.getLastCompactTime() == 0
-                || (now - app.mOptRecord.getLastCompactTime()) > mCompactThrottleBFGS);
-    }
-
     void compactAllSystem() {
         if (useCompaction()) {
             if (DEBUG_COMPACTION) {
@@ -1130,8 +1085,6 @@
                 mCompactThrottleSomeFull = Integer.parseInt(throttleSomeFullFlag);
                 mCompactThrottleFullSome = Integer.parseInt(throttleFullSomeFlag);
                 mCompactThrottleFullFull = Integer.parseInt(throttleFullFullFlag);
-                mCompactThrottleBFGS = Integer.parseInt(throttleBFGSFlag);
-                mCompactThrottlePersistent = Integer.parseInt(throttlePersistentFlag);
                 mCompactThrottleMinOomAdj = Long.parseLong(throttleMinOomAdjFlag);
                 mCompactThrottleMaxOomAdj = Long.parseLong(throttleMaxOomAdjFlag);
             } catch (NumberFormatException e) {
@@ -1144,8 +1097,6 @@
             mCompactThrottleSomeFull = DEFAULT_COMPACT_THROTTLE_2;
             mCompactThrottleFullSome = DEFAULT_COMPACT_THROTTLE_3;
             mCompactThrottleFullFull = DEFAULT_COMPACT_THROTTLE_4;
-            mCompactThrottleBFGS = DEFAULT_COMPACT_THROTTLE_5;
-            mCompactThrottlePersistent = DEFAULT_COMPACT_THROTTLE_6;
             mCompactThrottleMinOomAdj = DEFAULT_COMPACT_THROTTLE_MIN_OOM_ADJ;
             mCompactThrottleMaxOomAdj = DEFAULT_COMPACT_THROTTLE_MAX_OOM_ADJ;
         }
@@ -1497,23 +1448,23 @@
 
     @GuardedBy({"mService", "mProcLock"})
     void onOomAdjustChanged(int oldAdj, int newAdj, ProcessRecord app) {
-        // Cancel any currently executing compactions
-        // if the process moved out of cached state
-        if (newAdj < oldAdj && newAdj < ProcessList.CACHED_APP_MIN_ADJ) {
-            cancelCompactionForProcess(app, CancelCompactReason.OOM_IMPROVEMENT);
-        }
-
-        if (oldAdj <= ProcessList.PERCEPTIBLE_APP_ADJ
-                && (newAdj == ProcessList.PREVIOUS_APP_ADJ || newAdj == ProcessList.HOME_APP_ADJ)) {
-            if (ENABLE_FILE_COMPACT) {
-                // Perform a minor compaction when a perceptible app becomes the prev/home app
-                compactApp(app, CompactProfile.SOME, CompactSource.APP, false);
+        if (useCompaction()) {
+            // Cancel any currently executing compactions
+            // if the process moved out of cached state
+            if (newAdj < oldAdj && newAdj < ProcessList.CACHED_APP_MIN_ADJ) {
+                cancelCompactionForProcess(app, CancelCompactReason.OOM_IMPROVEMENT);
             }
-        } else if (oldAdj < ProcessList.CACHED_APP_MIN_ADJ
-                && newAdj >= ProcessList.CACHED_APP_MIN_ADJ
-                && newAdj <= ProcessList.CACHED_APP_MAX_ADJ) {
-            // Perform a major compaction when any app enters cached
-            compactApp(app, CompactProfile.FULL, CompactSource.APP, false);
+        }
+    }
+
+    /**
+     * Callback received after a process has been frozen.
+     */
+    void onProcessFrozen(ProcessRecord frozenProc) {
+        if (useCompaction()) {
+            synchronized (mProcLock) {
+                compactApp(frozenProc, CompactProfile.FULL, CompactSource.APP, false);
+            }
         }
     }
 
@@ -1687,26 +1638,6 @@
                             return true;
                         }
                     }
-                } else if (source == CompactSource.PERSISTENT) {
-                    if (start - lastCompactTime < mCompactThrottlePersistent) {
-                        if (DEBUG_COMPACTION) {
-                            Slog.d(TAG_AM,
-                                    "Skipping persistent compaction for " + name
-                                            + ": too soon. throttle=" + mCompactThrottlePersistent
-                                            + " last=" + (start - lastCompactTime) + "ms ago");
-                        }
-                        return true;
-                    }
-                } else if (source == CompactSource.BFGS) {
-                    if (start - lastCompactTime < mCompactThrottleBFGS) {
-                        if (DEBUG_COMPACTION) {
-                            Slog.d(TAG_AM,
-                                    "Skipping bfgs compaction for " + name
-                                            + ": too soon. throttle=" + mCompactThrottleBFGS
-                                            + " last=" + (start - lastCompactTime) + "ms ago");
-                        }
-                        return true;
-                    }
                 }
             }
 
@@ -2021,6 +1952,9 @@
                             }
                         }
                     }
+                    if (proc.mOptRecord.isFrozen()) {
+                        onProcessFrozen(proc);
+                    }
                 }
                     break;
                 case REPORT_UNFREEZE_MSG:
@@ -2050,6 +1984,10 @@
             freezeAppAsyncLSP(proc);
         }
 
+        /**
+         * Freeze a process.
+         * @param proc process to be frozen
+         */
         @GuardedBy({"mAm"})
         private void freezeProcess(final ProcessRecord proc) {
             int pid = proc.getPid(); // Unlocked intentionally
@@ -2083,6 +2021,10 @@
                 if (pid == 0 || opt.isFrozen()) {
                     // Already frozen or not a real process, either one being
                     // launched or one being killed
+                    if (DEBUG_FREEZER) {
+                        Slog.d(TAG_AM, "Skipping freeze for process " + pid
+                                + " " + name + ". Already frozen or not a real process");
+                    }
                     return;
                 }
 
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index a86c02d..a98571b 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -2937,30 +2937,8 @@
 
         int changes = 0;
 
-        // don't compact during bootup
-        if (mCachedAppOptimizer.useCompaction() && mService.mBooted) {
-            // Cached and prev/home compaction
-            // reminder: here, setAdj is previous state, curAdj is upcoming state
-            if (state.getCurAdj() != state.getSetAdj()) {
-                mCachedAppOptimizer.onOomAdjustChanged(state.getSetAdj(), state.getCurAdj(), app);
-            } else if (mService.mWakefulness.get() != PowerManagerInternal.WAKEFULNESS_AWAKE) {
-                // See if we can compact persistent and bfgs services now that screen is off
-                if (state.getSetAdj() < FOREGROUND_APP_ADJ
-                        && !state.isRunningRemoteAnimation()
-                        // Because these can fire independent of oom_adj/procstate changes, we need
-                        // to throttle the actual dispatch of these requests in addition to the
-                        // processing of the requests. As a result, there is throttling both here
-                        // and in CachedAppOptimizer.
-                        && mCachedAppOptimizer.shouldCompactPersistent(app, now)) {
-                    mCachedAppOptimizer.compactApp(app, CachedAppOptimizer.CompactProfile.FULL,
-                            CachedAppOptimizer.CompactSource.PERSISTENT, false);
-                } else if (state.getCurProcState()
-                                == ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE
-                        && mCachedAppOptimizer.shouldCompactBFGS(app, now)) {
-                    mCachedAppOptimizer.compactApp(app, CachedAppOptimizer.CompactProfile.FULL,
-                            CachedAppOptimizer.CompactSource.BFGS, false);
-                }
-            }
+        if (state.getCurAdj() != state.getSetAdj()) {
+            mCachedAppOptimizer.onOomAdjustChanged(state.getSetAdj(), state.getCurAdj(), app);
         }
 
         if (state.getCurAdj() != state.getSetAdj()) {
diff --git a/services/core/java/com/android/server/power/hint/HintManagerService.java b/services/core/java/com/android/server/power/hint/HintManagerService.java
index a9a1d5e..1a22b89 100644
--- a/services/core/java/com/android/server/power/hint/HintManagerService.java
+++ b/services/core/java/com/android/server/power/hint/HintManagerService.java
@@ -48,7 +48,6 @@
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
-import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
 import java.util.Objects;
@@ -324,16 +323,7 @@
     private boolean checkTidValid(int uid, int tgid, int [] tids) {
         // Make sure all tids belongs to the same UID (including isolated UID),
         // tids can belong to different application processes.
-        List<Integer> eligiblePids = null;
-        // To avoid deadlock, do not call into AMS if the call is from system.
-        if (uid != Process.SYSTEM_UID) {
-            eligiblePids = mAmInternal.getIsolatedProcesses(uid);
-        }
-        if (eligiblePids == null) {
-            eligiblePids = new ArrayList<>();
-        }
-        eligiblePids.add(tgid);
-
+        List<Integer> isolatedPids = null;
         for (int threadId : tids) {
             final String[] procStatusKeys = new String[] {
                     "Uid:",
@@ -345,7 +335,21 @@
             int pidOfThreadId = (int) output[1];
 
             // use PID check for isolated processes, use UID check for non-isolated processes.
-            if (eligiblePids.contains(pidOfThreadId) || uidOfThreadId == uid) {
+            if (pidOfThreadId == tgid || uidOfThreadId == uid) {
+                continue;
+            }
+            // Only call into AM if the tid is either isolated or invalid
+            if (isolatedPids == null) {
+                // To avoid deadlock, do not call into AMS if the call is from system.
+                if (uid == Process.SYSTEM_UID) {
+                    return false;
+                }
+                isolatedPids = mAmInternal.getIsolatedProcesses(uid);
+                if (isolatedPids == null) {
+                    return false;
+                }
+            }
+            if (isolatedPids.contains(pidOfThreadId)) {
                 continue;
             }
             return false;
diff --git a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
index b2f48d9..433d807 100644
--- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
+++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
@@ -154,7 +154,6 @@
 import android.security.metrics.KeystoreAtom;
 import android.security.metrics.KeystoreAtomPayload;
 import android.security.metrics.RkpErrorStats;
-import android.security.metrics.RkpPoolStats;
 import android.security.metrics.StorageStats;
 import android.stats.storage.StorageEnums;
 import android.telephony.ModemActivityInfo;
@@ -730,7 +729,6 @@
                             return pullInstalledIncrementalPackagesLocked(atomTag, data);
                         }
                     case FrameworkStatsLog.KEYSTORE2_STORAGE_STATS:
-                    case FrameworkStatsLog.RKP_POOL_STATS:
                     case FrameworkStatsLog.KEYSTORE2_KEY_CREATION_WITH_GENERAL_INFO:
                     case FrameworkStatsLog.KEYSTORE2_KEY_CREATION_WITH_AUTH_INFO:
                     case FrameworkStatsLog.KEYSTORE2_KEY_CREATION_WITH_PURPOSE_AND_MODES_INFO:
@@ -938,7 +936,6 @@
         registerSettingsStats();
         registerInstalledIncrementalPackages();
         registerKeystoreStorageStats();
-        registerRkpPoolStats();
         registerKeystoreKeyCreationWithGeneralInfo();
         registerKeystoreKeyCreationWithAuthInfo();
         registerKeystoreKeyCreationWithPurposeModesInfo();
@@ -4258,14 +4255,6 @@
                 mStatsCallbackImpl);
     }
 
-    private void registerRkpPoolStats() {
-        mStatsManager.setPullAtomCallback(
-                FrameworkStatsLog.RKP_POOL_STATS,
-                null, // use default PullAtomMetadata values,
-                DIRECT_EXECUTOR,
-                mStatsCallbackImpl);
-    }
-
     private void registerKeystoreKeyCreationWithGeneralInfo() {
         mStatsManager.setPullAtomCallback(
                 FrameworkStatsLog.KEYSTORE2_KEY_CREATION_WITH_GENERAL_INFO,
@@ -4373,19 +4362,6 @@
         return StatsManager.PULL_SUCCESS;
     }
 
-    int parseRkpPoolStats(KeystoreAtom[] atoms, List<StatsEvent> pulledData) {
-        for (KeystoreAtom atomWrapper : atoms) {
-            if (atomWrapper.payload.getTag() != KeystoreAtomPayload.rkpPoolStats) {
-                return StatsManager.PULL_SKIP;
-            }
-            RkpPoolStats atom = atomWrapper.payload.getRkpPoolStats();
-            pulledData.add(FrameworkStatsLog.buildStatsEvent(
-                    FrameworkStatsLog.RKP_POOL_STATS, atom.security_level, atom.expiring,
-                    atom.unassigned, atom.attested, atom.total));
-        }
-        return StatsManager.PULL_SUCCESS;
-    }
-
     int parseKeystoreKeyCreationWithGeneralInfo(KeystoreAtom[] atoms, List<StatsEvent> pulledData) {
         for (KeystoreAtom atomWrapper : atoms) {
             if (atomWrapper.payload.getTag()
@@ -4518,8 +4494,6 @@
             switch (atomTag) {
                 case FrameworkStatsLog.KEYSTORE2_STORAGE_STATS:
                     return parseKeystoreStorageStats(atoms, pulledData);
-                case FrameworkStatsLog.RKP_POOL_STATS:
-                    return parseRkpPoolStats(atoms, pulledData);
                 case FrameworkStatsLog.KEYSTORE2_KEY_CREATION_WITH_GENERAL_INFO:
                     return parseKeystoreKeyCreationWithGeneralInfo(atoms, pulledData);
                 case FrameworkStatsLog.KEYSTORE2_KEY_CREATION_WITH_AUTH_INFO:
diff --git a/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/SdCardEjectionTests.kt b/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/SdCardEjectionTests.kt
index d813962..a849b66 100644
--- a/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/SdCardEjectionTests.kt
+++ b/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/SdCardEjectionTests.kt
@@ -22,6 +22,7 @@
 import com.google.common.truth.Truth.assertThat
 import org.junit.After
 import org.junit.Before
+import org.junit.Ignore
 import org.junit.Rule
 import org.junit.Test
 import org.junit.rules.TemporaryFolder
@@ -41,6 +42,7 @@
 @RunWith(DeviceJUnit4Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(
         DeviceJUnit4ClassRunnerWithParameters.RunnerFactory::class)
+@Ignore("b/275403538")
 class SdCardEjectionTests : BaseHostJUnit4Test() {
 
     companion object {
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java b/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java
index 1fbb8dd..eb6efd2 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java
@@ -166,10 +166,6 @@
                     CachedAppOptimizer.DEFAULT_STATSD_SAMPLE_RATE);
             assertThat(mCachedAppOptimizerUnderTest.mFreezerStatsdSampleRate).isEqualTo(
                     CachedAppOptimizer.DEFAULT_STATSD_SAMPLE_RATE);
-            assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleBFGS).isEqualTo(
-                    CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_5);
-            assertThat(mCachedAppOptimizerUnderTest.mCompactThrottlePersistent).isEqualTo(
-                    CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_6);
             assertThat(mCachedAppOptimizerUnderTest.mFullAnonRssThrottleKb).isEqualTo(
                     CachedAppOptimizer.DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB);
             assertThat(mCachedAppOptimizerUnderTest.mFullDeltaRssThrottleKb).isEqualTo(
@@ -261,10 +257,6 @@
                 CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_3 + 1);
         assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleFullFull).isEqualTo(
                 CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_4 + 1);
-        assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleBFGS).isEqualTo(
-                CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_5 + 1);
-        assertThat(mCachedAppOptimizerUnderTest.mCompactThrottlePersistent).isEqualTo(
-                CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_6 + 1);
         assertThat(mCachedAppOptimizerUnderTest.mFullDeltaRssThrottleKb).isEqualTo(
                 CachedAppOptimizer.DEFAULT_COMPACT_FULL_DELTA_RSS_THROTTLE_KB + 1);
         assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleMinOomAdj).isEqualTo(
@@ -275,10 +267,6 @@
                 CachedAppOptimizer.DEFAULT_STATSD_SAMPLE_RATE + 0.1f);
         assertThat(mCachedAppOptimizerUnderTest.mFreezerStatsdSampleRate).isEqualTo(
                 CachedAppOptimizer.DEFAULT_STATSD_SAMPLE_RATE + 0.1f);
-        assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleBFGS).isEqualTo(
-                CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_5 + 1);
-        assertThat(mCachedAppOptimizerUnderTest.mCompactThrottlePersistent).isEqualTo(
-                CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_6 + 1);
         assertThat(mCachedAppOptimizerUnderTest.mFullAnonRssThrottleKb).isEqualTo(
                 CachedAppOptimizer.DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB + 1);
         assertThat(mCachedAppOptimizerUnderTest.mProcStateThrottle).containsExactly(1, 2, 3);
@@ -425,10 +413,6 @@
                 CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_3 + 1);
         assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleFullFull).isEqualTo(
                 CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_4 + 1);
-        assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleBFGS).isEqualTo(
-                CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_5 + 1);
-        assertThat(mCachedAppOptimizerUnderTest.mCompactThrottlePersistent).isEqualTo(
-                CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_6 + 1);
         assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleMinOomAdj).isEqualTo(
                 CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_MIN_OOM_ADJ + 1);
         assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleMaxOomAdj).isEqualTo(
@@ -454,10 +438,6 @@
                 CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_3);
         assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleFullFull).isEqualTo(
                 CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_4);
-        assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleBFGS).isEqualTo(
-                CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_5);
-        assertThat(mCachedAppOptimizerUnderTest.mCompactThrottlePersistent).isEqualTo(
-                CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_6);
 
         // Repeat for each of the throttle keys.
         mCountDown = new CountDownLatch(1);
@@ -472,10 +452,6 @@
                 CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_3);
         assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleFullFull).isEqualTo(
                 CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_4);
-        assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleBFGS).isEqualTo(
-                CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_5);
-        assertThat(mCachedAppOptimizerUnderTest.mCompactThrottlePersistent).isEqualTo(
-                CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_6);
 
         mCountDown = new CountDownLatch(1);
         DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
@@ -489,10 +465,6 @@
                 CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_3);
         assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleFullFull).isEqualTo(
                 CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_4);
-        assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleBFGS).isEqualTo(
-                CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_5);
-        assertThat(mCachedAppOptimizerUnderTest.mCompactThrottlePersistent).isEqualTo(
-                CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_6);
 
         mCountDown = new CountDownLatch(1);
         DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
@@ -506,10 +478,6 @@
                 CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_3);
         assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleFullFull).isEqualTo(
                 CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_4);
-        assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleBFGS).isEqualTo(
-                CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_5);
-        assertThat(mCachedAppOptimizerUnderTest.mCompactThrottlePersistent).isEqualTo(
-                CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_6);
 
         mCountDown = new CountDownLatch(1);
         DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
@@ -523,10 +491,6 @@
                 CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_3);
         assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleFullFull).isEqualTo(
                 CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_4);
-        assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleBFGS).isEqualTo(
-                CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_5);
-        assertThat(mCachedAppOptimizerUnderTest.mCompactThrottlePersistent).isEqualTo(
-                CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_6);
 
         mCountDown = new CountDownLatch(1);
         DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
@@ -540,10 +504,6 @@
                 CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_3);
         assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleFullFull).isEqualTo(
                 CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_4);
-        assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleBFGS).isEqualTo(
-                CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_5);
-        assertThat(mCachedAppOptimizerUnderTest.mCompactThrottlePersistent).isEqualTo(
-                CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_6);
     }
 
     @Test
@@ -953,15 +913,7 @@
         mProcessDependencies.setRssAfterCompaction(rssAfter);
 
         // When moving within cached state
-        mCachedAppOptimizerUnderTest.onOomAdjustChanged(
-                ProcessList.CACHED_APP_MIN_ADJ, ProcessList.CACHED_APP_MIN_ADJ + 1, processRecord);
-        waitForHandler();
-        // THEN process IS NOT compacted.
-        assertThat(mCachedAppOptimizerUnderTest.mLastCompactionStats.get(pid)).isNull();
-
-        // When moving into cached state
-        mCachedAppOptimizerUnderTest.onOomAdjustChanged(ProcessList.CACHED_APP_MIN_ADJ - 1,
-                ProcessList.CACHED_APP_MIN_ADJ + 1, processRecord);
+        mCachedAppOptimizerUnderTest.onProcessFrozen(processRecord);
         waitForHandler();
         // THEN process IS compacted.
         assertThat(mCachedAppOptimizerUnderTest.mLastCompactionStats.get(pid)).isNotNull();
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/ProxyAccessibilityServiceConnectionTest.java b/services/tests/servicestests/src/com/android/server/accessibility/ProxyAccessibilityServiceConnectionTest.java
index b5e0e07..dd44a79 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/ProxyAccessibilityServiceConnectionTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/ProxyAccessibilityServiceConnectionTest.java
@@ -50,6 +50,7 @@
 
 public class ProxyAccessibilityServiceConnectionTest {
     private static final int DISPLAY_ID = 1000;
+    private static final int DEVICE_ID = 2000;
     private static final int CONNECTION_ID = 1000;
     private static final ComponentName COMPONENT_NAME = new ComponentName(
             "com.android.server.accessibility", ".ProxyAccessibilityServiceConnectionTest");
@@ -90,7 +91,7 @@
                 mAccessibilityServiceInfo, CONNECTION_ID , new Handler(
                         getInstrumentation().getContext().getMainLooper()),
                 mMockLock, mMockSecurityPolicy, mMockSystemSupport, mMockA11yTrace,
-                mMockWindowManagerInternal, mMockA11yWindowManager, DISPLAY_ID);
+                mMockWindowManagerInternal, mMockA11yWindowManager, DISPLAY_ID, DEVICE_ID);
     }
 
     @Test
@@ -101,7 +102,7 @@
 
         mProxyConnection.setInstalledAndEnabledServices(infos);
 
-        verify(mMockSystemSupport).onClientChangeLocked(true);
+        verify(mMockSystemSupport).onProxyChanged(DEVICE_ID);
     }
 
     @Test