Merge "Keep insets leash position before display transaction applies" into udc-qpr-dev
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobStore.java b/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
index 0a7bffc..4b4e512 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
@@ -1118,6 +1118,7 @@
             }
             boolean needFileMigration = false;
             long nowElapsed = sElapsedRealtimeClock.millis();
+            int numDuplicates = 0;
             synchronized (mLock) {
                 for (File file : files) {
                     final AtomicFile aFile = createJobFile(file);
@@ -1126,6 +1127,16 @@
                         if (jobs != null) {
                             for (int i = 0; i < jobs.size(); i++) {
                                 JobStatus js = jobs.get(i);
+                                final JobStatus existingJob = this.jobSet.get(
+                                        js.getUid(), js.getNamespace(), js.getJobId());
+                                if (existingJob != null) {
+                                    numDuplicates++;
+                                    // Jobs are meant to have unique uid-namespace-jobId
+                                    // combinations, but we've somehow read multiple jobs with the
+                                    // combination. Drop the latter one since keeping both will
+                                    // result in other issues.
+                                    continue;
+                                }
                                 js.prepareLocked();
                                 js.enqueueTime = nowElapsed;
                                 this.jobSet.add(js);
@@ -1174,6 +1185,10 @@
                 migrateJobFilesAsync();
             }
 
+            if (numDuplicates > 0) {
+                Slog.wtf(TAG, "Encountered " + numDuplicates + " duplicate persisted jobs");
+            }
+
             // Log the count immediately after loading from boot.
             mCurrentJobSetSize = numJobs;
             mScheduledJob30MinHighWaterMark = mCurrentJobSetSize;
diff --git a/core/java/android/appwidget/AppWidgetManager.java b/core/java/android/appwidget/AppWidgetManager.java
index b159321..85d669e 100644
--- a/core/java/android/appwidget/AppWidgetManager.java
+++ b/core/java/android/appwidget/AppWidgetManager.java
@@ -24,6 +24,7 @@
 import android.annotation.SdkConstant.SdkConstantType;
 import android.annotation.SystemService;
 import android.annotation.TestApi;
+import android.annotation.UiThread;
 import android.annotation.UserIdInt;
 import android.app.IServiceConnection;
 import android.app.PendingIntent;
@@ -39,6 +40,10 @@
 import android.os.Build;
 import android.os.Bundle;
 import android.os.Handler;
+import android.os.HandlerExecutor;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Process;
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.util.DisplayMetrics;
@@ -53,6 +58,7 @@
 import java.util.List;
 import java.util.Objects;
 import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.Executor;
 
 /**
  * Updates AppWidget state; gets information about installed AppWidget providers and other
@@ -475,6 +481,8 @@
 
     private static final String TAG = "AppWidgetManager";
 
+    private static Executor sUpdateExecutor;
+
     /**
      * An intent extra that contains multiple appWidgetIds.  These are id values as
      * they were provided to the application during a recent restore from backup.  It is
@@ -510,6 +518,8 @@
     private final IAppWidgetService mService;
     private final DisplayMetrics mDisplayMetrics;
 
+    private boolean mHasPostedLegacyLists = false;
+
     /**
      * Get the AppWidgetManager instance to use for the supplied {@link android.content.Context
      * Context} object.
@@ -552,6 +562,13 @@
         });
     }
 
+    private boolean isPostingTaskToBackground(@Nullable RemoteViews views) {
+        return Looper.myLooper() == Looper.getMainLooper()
+                && RemoteViews.isAdapterConversionEnabled()
+                && (mHasPostedLegacyLists = mHasPostedLegacyLists
+                        || (views != null && views.hasLegacyLists()));
+    }
+
     /**
      * Set the RemoteViews to use for the specified appWidgetIds.
      * <p>
@@ -575,6 +592,19 @@
         if (mService == null) {
             return;
         }
+
+        if (isPostingTaskToBackground(views)) {
+            createUpdateExecutorIfNull().execute(() -> {
+                try {
+                    mService.updateAppWidgetIds(mPackageName, appWidgetIds, views);
+                } catch (RemoteException e) {
+                    Log.e(TAG, "Error updating app widget views in background", e);
+                }
+            });
+
+            return;
+        }
+
         try {
             mService.updateAppWidgetIds(mPackageName, appWidgetIds, views);
         } catch (RemoteException e) {
@@ -683,6 +713,19 @@
         if (mService == null) {
             return;
         }
+
+        if (isPostingTaskToBackground(views)) {
+            createUpdateExecutorIfNull().execute(() -> {
+                try {
+                    mService.partiallyUpdateAppWidgetIds(mPackageName, appWidgetIds, views);
+                } catch (RemoteException e) {
+                    Log.e(TAG, "Error partially updating app widget views in background", e);
+                }
+            });
+
+            return;
+        }
+
         try {
             mService.partiallyUpdateAppWidgetIds(mPackageName, appWidgetIds, views);
         } catch (RemoteException e) {
@@ -738,6 +781,19 @@
         if (mService == null) {
             return;
         }
+
+        if (isPostingTaskToBackground(views)) {
+            createUpdateExecutorIfNull().execute(() -> {
+                try {
+                    mService.updateAppWidgetProvider(provider, views);
+                } catch (RemoteException e) {
+                    Log.e(TAG, "Error updating app widget view using provider in background", e);
+                }
+            });
+
+            return;
+        }
+
         try {
             mService.updateAppWidgetProvider(provider, views);
         } catch (RemoteException e) {
@@ -786,28 +842,45 @@
         if (mService == null) {
             return;
         }
-        try {
-            if (RemoteViews.isAdapterConversionEnabled()) {
-                List<CompletableFuture<Void>> updateFutures = new ArrayList<>();
-                for (int i = 0; i < appWidgetIds.length; i++) {
-                    final int widgetId = appWidgetIds[i];
-                    updateFutures.add(CompletableFuture.runAsync(() -> {
-                        try {
-                            RemoteViews views = mService.getAppWidgetViews(mPackageName, widgetId);
-                            if (views.replaceRemoteCollections(viewId)) {
-                                updateAppWidget(widgetId, views);
-                            }
-                        } catch (Exception e) {
-                            Log.e(TAG, "Error notifying changes in RemoteViews", e);
-                        }
-                    }));
-                }
-                CompletableFuture.allOf(updateFutures.toArray(CompletableFuture[]::new)).join();
-            } else {
+
+        if (!RemoteViews.isAdapterConversionEnabled()) {
+            try {
                 mService.notifyAppWidgetViewDataChanged(mPackageName, appWidgetIds, viewId);
+            } catch (RemoteException re) {
+                throw re.rethrowFromSystemServer();
             }
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
+
+            return;
+        }
+
+        if (Looper.myLooper() == Looper.getMainLooper()) {
+            mHasPostedLegacyLists = true;
+            createUpdateExecutorIfNull().execute(() -> notifyCollectionWidgetChange(appWidgetIds,
+                    viewId));
+        } else {
+            notifyCollectionWidgetChange(appWidgetIds, viewId);
+        }
+    }
+
+    private void notifyCollectionWidgetChange(int[] appWidgetIds, int viewId) {
+        try {
+            List<CompletableFuture<Void>> updateFutures = new ArrayList<>();
+            for (int i = 0; i < appWidgetIds.length; i++) {
+                final int widgetId = appWidgetIds[i];
+                updateFutures.add(CompletableFuture.runAsync(() -> {
+                    try {
+                        RemoteViews views = mService.getAppWidgetViews(mPackageName, widgetId);
+                        if (views.replaceRemoteCollections(viewId)) {
+                            updateAppWidget(widgetId, views);
+                        }
+                    } catch (Exception e) {
+                        Log.e(TAG, "Error notifying changes in RemoteViews", e);
+                    }
+                }));
+            }
+            CompletableFuture.allOf(updateFutures.toArray(CompletableFuture[]::new)).join();
+        } catch (Exception e) {
+            Log.e(TAG, "Error notifying changes for all widgets", e);
         }
     }
 
@@ -1338,4 +1411,20 @@
             throw e.rethrowFromSystemServer();
         }
     }
+
+    @UiThread
+    private static @NonNull Executor createUpdateExecutorIfNull() {
+        if (sUpdateExecutor == null) {
+            sUpdateExecutor = new HandlerExecutor(createAndStartNewHandler(
+                    "widget_manager_update_helper_thread", Process.THREAD_PRIORITY_FOREGROUND));
+        }
+
+        return sUpdateExecutor;
+    }
+
+    private static @NonNull Handler createAndStartNewHandler(@NonNull String name, int priority) {
+        HandlerThread thread = new HandlerThread(name, priority);
+        thread.start();
+        return thread.getThreadHandler();
+    }
 }
diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index 827600c..85f5395 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -247,6 +247,7 @@
         DEFAULT_FLAGS.put(SETTINGS_ENABLE_LOCKSCREEN_TRANSFER_API, "true");
         DEFAULT_FLAGS.put(SETTINGS_REMOTE_DEVICE_CREDENTIAL_VALIDATION, "true");
         DEFAULT_FLAGS.put(SETTINGS_BIOMETRICS2_FINGERPRINT_SETTINGS, "false");
+        DEFAULT_FLAGS.put("settings_press_hold_nav_handle_to_search", "false");
     }
 
     private static final Set<String> PERSISTENT_FLAGS;
diff --git a/core/java/android/view/HandwritingInitiator.java b/core/java/android/view/HandwritingInitiator.java
index 9b34007..72861db 100644
--- a/core/java/android/view/HandwritingInitiator.java
+++ b/core/java/android/view/HandwritingInitiator.java
@@ -351,13 +351,13 @@
     }
 
     private static boolean shouldTriggerStylusHandwritingForView(@NonNull View view) {
-        if (!view.isAutoHandwritingEnabled()) {
+        if (!view.shouldInitiateHandwriting()) {
             return false;
         }
-        // The view may be a handwriting initiation delegate, in which case it is not the editor
+        // The view may be a handwriting initiation delegator, in which case it is not the editor
         // view for which handwriting would be started. However, in almost all cases, the return
-        // values of View#isStylusHandwritingAvailable will be the same for the delegate view and
-        // the delegator editor view. So the delegate view can be used to decide whether handwriting
+        // values of View#isStylusHandwritingAvailable will be the same for the delegator view and
+        // the delegate editor view. So the delegator view can be used to decide whether handwriting
         // should be triggered.
         return view.isStylusHandwritingAvailable();
     }
@@ -682,7 +682,7 @@
     /** The helper method to check if the given view is still active for handwriting. */
     private static boolean isViewActive(@Nullable View view) {
         return view != null && view.isAttachedToWindow() && view.isAggregatedVisible()
-                && view.isAutoHandwritingEnabled();
+                && view.shouldInitiateHandwriting();
     }
 
     /**
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 2499be9..363e554 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -5488,7 +5488,6 @@
                 (TEXT_ALIGNMENT_DEFAULT << PFLAG2_TEXT_ALIGNMENT_MASK_SHIFT) |
                 (PFLAG2_TEXT_ALIGNMENT_RESOLVED_DEFAULT) |
                 (IMPORTANT_FOR_ACCESSIBILITY_DEFAULT << PFLAG2_IMPORTANT_FOR_ACCESSIBILITY_SHIFT);
-        mPrivateFlags4 = PFLAG4_AUTO_HANDWRITING_ENABLED;
 
         final ViewConfiguration configuration = ViewConfiguration.get(context);
         mTouchSlop = configuration.getScaledTouchSlop();
@@ -6213,7 +6212,7 @@
                     setPreferKeepClear(a.getBoolean(attr, false));
                     break;
                 case R.styleable.View_autoHandwritingEnabled:
-                    setAutoHandwritingEnabled(a.getBoolean(attr, true));
+                    setAutoHandwritingEnabled(a.getBoolean(attr, false));
                     break;
                 case R.styleable.View_handwritingBoundsOffsetLeft:
                     mHandwritingBoundsOffsetLeft = a.getDimension(attr, 0);
@@ -12078,7 +12077,7 @@
         if (getSystemGestureExclusionRects().isEmpty()
                 && collectPreferKeepClearRects().isEmpty()
                 && collectUnrestrictedPreferKeepClearRects().isEmpty()
-                && (info.mHandwritingArea == null || !isAutoHandwritingEnabled())) {
+                && (info.mHandwritingArea == null || !shouldInitiateHandwriting())) {
             if (info.mPositionUpdateListener != null) {
                 mRenderNode.removePositionUpdateListener(info.mPositionUpdateListener);
                 info.mPositionUpdateListener = null;
@@ -12445,7 +12444,7 @@
 
     void updateHandwritingArea() {
         // If autoHandwritingArea is not enabled, do nothing.
-        if (!isAutoHandwritingEnabled()) return;
+        if (!shouldInitiateHandwriting()) return;
         final AttachInfo ai = mAttachInfo;
         if (ai != null) {
             ai.mViewRootImpl.getHandwritingInitiator().updateHandwritingAreasForView(this);
@@ -12453,6 +12452,16 @@
     }
 
     /**
+     * Returns true if a stylus {@link MotionEvent} within this view's bounds should initiate
+     * handwriting mode, either for this view ({@link #isAutoHandwritingEnabled()} is {@code true})
+     * or for a handwriting delegate view ({@link #getHandwritingDelegatorCallback()} is not {@code
+     * null}).
+     */
+    boolean shouldInitiateHandwriting() {
+        return isAutoHandwritingEnabled() || getHandwritingDelegatorCallback() != null;
+    }
+
+    /**
      * Sets a callback which should be called when a stylus {@link MotionEvent} occurs within this
      * view's bounds. The callback will be called from the UI thread.
      *
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index 7903dd64..a740b65 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -801,6 +801,11 @@
                     mActions.set(i, new SetRemoteCollectionItemListAdapterAction(itemsAction.viewId,
                             itemsAction.mServiceIntent));
                     isActionReplaced = true;
+                } else if (action instanceof SetRemoteViewsAdapterIntent intentAction
+                        && intentAction.viewId == viewId) {
+                    mActions.set(i, new SetRemoteCollectionItemListAdapterAction(
+                            intentAction.viewId, intentAction.intent));
+                    isActionReplaced = true;
                 } else if (action instanceof ViewGroupActionAdd groupAction
                         && groupAction.mNestedViews != null) {
                     isActionReplaced |= groupAction.mNestedViews.replaceRemoteCollections(viewId);
@@ -822,6 +827,42 @@
         return isActionReplaced;
     }
 
+    /**
+     * @return True if has set remote adapter using service intent
+     * @hide
+     */
+    public boolean hasLegacyLists() {
+        if (mActions != null) {
+            for (int i = 0; i < mActions.size(); i++) {
+                Action action = mActions.get(i);
+                if ((action instanceof SetRemoteCollectionItemListAdapterAction itemsAction
+                        && itemsAction.mServiceIntent != null)
+                        || (action instanceof SetRemoteViewsAdapterIntent intentAction
+                                && intentAction.intent != null)
+                        || (action instanceof ViewGroupActionAdd groupAction
+                                && groupAction.mNestedViews != null
+                                && groupAction.mNestedViews.hasLegacyLists())) {
+                    return true;
+                }
+            }
+        }
+        if (mSizedRemoteViews != null) {
+            for (int i = 0; i < mSizedRemoteViews.size(); i++) {
+                if (mSizedRemoteViews.get(i).hasLegacyLists()) {
+                    return true;
+                }
+            }
+        }
+        if (mLandscape != null && mLandscape.hasLegacyLists()) {
+            return true;
+        }
+        if (mPortrait != null && mPortrait.hasLegacyLists()) {
+            return true;
+        }
+
+        return false;
+    }
+
     private static void visitIconUri(Icon icon, @NonNull Consumer<Uri> visitor) {
         if (icon != null && (icon.getType() == Icon.TYPE_URI
                 || icon.getType() == Icon.TYPE_URI_ADAPTIVE_BITMAP)) {
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 142df21..2ad3f74 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -1856,6 +1856,7 @@
         boolean clickable = canInputOrMove || isClickable();
         boolean longClickable = canInputOrMove || isLongClickable();
         int focusable = getFocusable();
+        boolean isAutoHandwritingEnabled = true;
 
         n = a.getIndexCount();
         for (int i = 0; i < n; i++) {
@@ -1878,6 +1879,10 @@
                 case com.android.internal.R.styleable.View_longClickable:
                     longClickable = a.getBoolean(attr, longClickable);
                     break;
+
+                case com.android.internal.R.styleable.View_autoHandwritingEnabled:
+                    isAutoHandwritingEnabled = a.getBoolean(attr, true);
+                    break;
             }
         }
         a.recycle();
@@ -1891,6 +1896,7 @@
         }
         setClickable(clickable);
         setLongClickable(longClickable);
+        setAutoHandwritingEnabled(isAutoHandwritingEnabled);
 
         if (mEditor != null) mEditor.prepareCursorControllers();
 
diff --git a/core/res/res/anim-ldrtl/activity_close_enter.xml b/core/res/res/anim-ldrtl/activity_close_enter.xml
index 6a699e7..0b48646 100644
--- a/core/res/res/anim-ldrtl/activity_close_enter.xml
+++ b/core/res/res/anim-ldrtl/activity_close_enter.xml
@@ -31,7 +31,7 @@
         android:duration="450" />
 
     <translate
-        android:fromXDelta="10%"
+        android:fromXDelta="96dp"
         android:toXDelta="0"
         android:fillEnabled="true"
         android:fillBefore="true"
@@ -41,11 +41,11 @@
         android:duration="450" />
 
     <extend
-        android:fromExtendLeft="10%"
+        android:fromExtendLeft="96dp"
         android:fromExtendTop="0"
         android:fromExtendRight="0"
         android:fromExtendBottom="0"
-        android:toExtendLeft="10%"
+        android:toExtendLeft="96dp"
         android:toExtendTop="0"
         android:toExtendRight="0"
         android:toExtendBottom="0"
diff --git a/core/res/res/anim-ldrtl/activity_close_exit.xml b/core/res/res/anim-ldrtl/activity_close_exit.xml
index 06a0d69..5277b9f 100644
--- a/core/res/res/anim-ldrtl/activity_close_exit.xml
+++ b/core/res/res/anim-ldrtl/activity_close_exit.xml
@@ -32,7 +32,7 @@
 
     <translate
         android:fromXDelta="0"
-        android:toXDelta="-10%"
+        android:toXDelta="-96dp"
         android:fillEnabled="true"
         android:fillBefore="true"
         android:fillAfter="true"
@@ -43,11 +43,11 @@
     <extend
         android:fromExtendLeft="0"
         android:fromExtendTop="0"
-        android:fromExtendRight="10%"
+        android:fromExtendRight="96dp"
         android:fromExtendBottom="0"
         android:toExtendLeft="0"
         android:toExtendTop="0"
-        android:toExtendRight="10%"
+        android:toExtendRight="96dp"
         android:toExtendBottom="0"
         android:interpolator="@interpolator/fast_out_extra_slow_in"
         android:startOffset="0"
diff --git a/core/res/res/anim-ldrtl/activity_open_enter.xml b/core/res/res/anim-ldrtl/activity_open_enter.xml
index 7b18294..97d2cf9 100644
--- a/core/res/res/anim-ldrtl/activity_open_enter.xml
+++ b/core/res/res/anim-ldrtl/activity_open_enter.xml
@@ -30,7 +30,7 @@
         android:duration="83" />
 
     <translate
-        android:fromXDelta="-10%"
+        android:fromXDelta="-96dp"
         android:toXDelta="0"
         android:fillEnabled="true"
         android:fillBefore="true"
@@ -41,11 +41,11 @@
     <extend
         android:fromExtendLeft="0"
         android:fromExtendTop="0"
-        android:fromExtendRight="10%"
+        android:fromExtendRight="96dp"
         android:fromExtendBottom="0"
         android:toExtendLeft="0"
         android:toExtendTop="0"
-        android:toExtendRight="10%"
+        android:toExtendRight="96dp"
         android:toExtendBottom="0"
         android:interpolator="@interpolator/fast_out_extra_slow_in"
         android:startOffset="0"
diff --git a/core/res/res/anim-ldrtl/activity_open_exit.xml b/core/res/res/anim-ldrtl/activity_open_exit.xml
index c29509e..2159029 100644
--- a/core/res/res/anim-ldrtl/activity_open_exit.xml
+++ b/core/res/res/anim-ldrtl/activity_open_exit.xml
@@ -31,7 +31,7 @@
 
     <translate
         android:fromXDelta="0"
-        android:toXDelta="10%"
+        android:toXDelta="96dp"
         android:fillEnabled="true"
         android:fillBefore="true"
         android:fillAfter="true"
@@ -40,11 +40,11 @@
         android:duration="450" />
 
     <extend
-        android:fromExtendLeft="10%"
+        android:fromExtendLeft="9dp"
         android:fromExtendTop="0"
         android:fromExtendRight="0"
         android:fromExtendBottom="0"
-        android:toExtendLeft="10%"
+        android:toExtendLeft="96dp"
         android:toExtendTop="0"
         android:toExtendRight="0"
         android:toExtendBottom="0"
diff --git a/core/res/res/anim-ldrtl/task_fragment_close_exit.xml b/core/res/res/anim-ldrtl/task_fragment_close_exit.xml
index c5a3654..58fcb1f 100644
--- a/core/res/res/anim-ldrtl/task_fragment_close_exit.xml
+++ b/core/res/res/anim-ldrtl/task_fragment_close_exit.xml
@@ -30,7 +30,7 @@
 
     <translate
         android:fromXDelta="0"
-        android:toXDelta="-10%"
+        android:toXDelta="-96dp"
         android:fillEnabled="true"
         android:fillBefore="true"
         android:fillAfter="true"
diff --git a/core/res/res/anim/activity_close_enter.xml b/core/res/res/anim/activity_close_enter.xml
index 0fefb51..22a1dd6 100644
--- a/core/res/res/anim/activity_close_enter.xml
+++ b/core/res/res/anim/activity_close_enter.xml
@@ -31,7 +31,7 @@
         android:duration="450" />
 
     <translate
-        android:fromXDelta="-10%"
+        android:fromXDelta="-96dp"
         android:toXDelta="0"
         android:fillEnabled="true"
         android:fillBefore="true"
@@ -43,11 +43,11 @@
     <extend
         android:fromExtendLeft="0"
         android:fromExtendTop="0"
-        android:fromExtendRight="10%"
+        android:fromExtendRight="96dp"
         android:fromExtendBottom="0"
         android:toExtendLeft="0"
         android:toExtendTop="0"
-        android:toExtendRight="10%"
+        android:toExtendRight="96dp"
         android:toExtendBottom="0"
         android:interpolator="@interpolator/fast_out_extra_slow_in"
         android:startOffset="0"
diff --git a/core/res/res/anim/activity_close_exit.xml b/core/res/res/anim/activity_close_exit.xml
index f807c26..a671049 100644
--- a/core/res/res/anim/activity_close_exit.xml
+++ b/core/res/res/anim/activity_close_exit.xml
@@ -32,7 +32,7 @@
 
     <translate
         android:fromXDelta="0"
-        android:toXDelta="10%"
+        android:toXDelta="96dp"
         android:fillEnabled="true"
         android:fillBefore="true"
         android:fillAfter="true"
@@ -41,11 +41,11 @@
         android:duration="450" />
 
     <extend
-        android:fromExtendLeft="10%"
+        android:fromExtendLeft="96dp"
         android:fromExtendTop="0"
         android:fromExtendRight="0"
         android:fromExtendBottom="0"
-        android:toExtendLeft="10%"
+        android:toExtendLeft="96dp"
         android:toExtendTop="0"
         android:toExtendRight="0"
         android:toExtendBottom="0"
diff --git a/core/res/res/anim/activity_open_enter.xml b/core/res/res/anim/activity_open_enter.xml
index 1674dab..f3172e4 100644
--- a/core/res/res/anim/activity_open_enter.xml
+++ b/core/res/res/anim/activity_open_enter.xml
@@ -30,7 +30,7 @@
         android:duration="83" />
 
     <translate
-        android:fromXDelta="10%"
+        android:fromXDelta="96dp"
         android:toXDelta="0"
         android:fillEnabled="true"
         android:fillBefore="true"
@@ -39,11 +39,11 @@
         android:duration="450" />
 
     <extend
-        android:fromExtendLeft="10%"
+        android:fromExtendLeft="96dp"
         android:fromExtendTop="0"
         android:fromExtendRight="0"
         android:fromExtendBottom="0"
-        android:toExtendLeft="10%"
+        android:toExtendLeft="96dp"
         android:toExtendTop="0"
         android:toExtendRight="0"
         android:toExtendBottom="0"
diff --git a/core/res/res/anim/activity_open_exit.xml b/core/res/res/anim/activity_open_exit.xml
index 372f2c8..d84827b 100644
--- a/core/res/res/anim/activity_open_exit.xml
+++ b/core/res/res/anim/activity_open_exit.xml
@@ -31,7 +31,7 @@
 
     <translate
         android:fromXDelta="0"
-        android:toXDelta="-10%"
+        android:toXDelta="-96dp"
         android:fillEnabled="true"
         android:fillBefore="true"
         android:fillAfter="true"
@@ -42,11 +42,11 @@
     <extend
         android:fromExtendLeft="0"
         android:fromExtendTop="0"
-        android:fromExtendRight="10%"
+        android:fromExtendRight="96dp"
         android:fromExtendBottom="0"
         android:toExtendLeft="0"
         android:toExtendTop="0"
-        android:toExtendRight="10%"
+        android:toExtendRight="96dp"
         android:toExtendBottom="0"
         android:interpolator="@interpolator/fast_out_extra_slow_in"
         android:startOffset="0"
diff --git a/core/res/res/anim/task_fragment_close_exit.xml b/core/res/res/anim/task_fragment_close_exit.xml
index 84d8b7e..6454085 100644
--- a/core/res/res/anim/task_fragment_close_exit.xml
+++ b/core/res/res/anim/task_fragment_close_exit.xml
@@ -30,7 +30,7 @@
 
     <translate
         android:fromXDelta="0"
-        android:toXDelta="10%"
+        android:toXDelta="96dp"
         android:fillEnabled="true"
         android:fillBefore="true"
         android:fillAfter="true"
diff --git a/core/res/res/anim/task_fragment_open_enter.xml b/core/res/res/anim/task_fragment_open_enter.xml
index 87ee179..5f57ed5 100644
--- a/core/res/res/anim/task_fragment_open_enter.xml
+++ b/core/res/res/anim/task_fragment_open_enter.xml
@@ -27,7 +27,7 @@
         android:startOffset="50"
         android:duration="83" />
     <translate
-        android:fromXDelta="10%"
+        android:fromXDelta="96dp"
         android:toXDelta="0"
         android:fillEnabled="true"
         android:fillBefore="true"
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 09d6795..31d6f16 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -5259,6 +5259,7 @@
         <item>1,1,1.0,0,1</item>
         <item>1,1,1.0,.4,1</item>
         <item>1,1,1.0,.15,15</item>
+        <item>0,0,0.7,0,1</item>
     </string-array>
 
     <!-- The integer index of the selected option in config_udfps_touch_detection_options -->
diff --git a/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java b/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java
index f1eef75..c46118d 100644
--- a/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java
+++ b/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java
@@ -245,7 +245,7 @@
 
     @Test
     public void onTouchEvent_tryAcceptDelegation_delegatorCallbackCreatesInputConnection() {
-        View delegateView = new View(mContext);
+        View delegateView = new EditText(mContext);
         delegateView.setIsHandwritingDelegate(true);
 
         mTestView1.setHandwritingDelegatorCallback(
@@ -266,7 +266,7 @@
 
     @Test
     public void onTouchEvent_tryAcceptDelegation_delegatorCallbackFocusesDelegate() {
-        View delegateView = new View(mContext);
+        View delegateView = new EditText(mContext);
         delegateView.setIsHandwritingDelegate(true);
         mHandwritingInitiator.onInputConnectionCreated(delegateView);
         reset(mHandwritingInitiator);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java
index 4640106..9b80063 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java
@@ -245,8 +245,8 @@
 
     private boolean shouldShowBackdrop(@NonNull TransitionInfo info,
             @NonNull TransitionInfo.Change change) {
-        final Animation a = loadAttributeAnimation(info, change, WALLPAPER_TRANSITION_NONE,
-                mTransitionAnimation, false);
+        final Animation a = loadAttributeAnimation(info.getType(), info, change,
+                WALLPAPER_TRANSITION_NONE, mTransitionAnimation, false);
         return a != null && a.getShowBackdrop();
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
index ff67110..9a2b812 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
@@ -851,7 +851,10 @@
         return mAppIntent;
     }
 
-    boolean isAppBubble() {
+    /**
+     * Returns whether this bubble is from an app versus a notification.
+     */
+    public boolean isAppBubble() {
         return mIsAppBubble;
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
index 2c10065..ea7053d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
@@ -653,14 +653,38 @@
     }
 
     /**
-     * @return the stack position to use if we don't have a saved location or if user education
-     * is being shown.
+     * Returns whether the {@link #getRestingPosition()} is equal to the default start position
+     * initialized for bubbles, if {@code true} this means the user hasn't moved the bubble
+     * from the initial start position (or they haven't received a bubble yet).
+     */
+    public boolean hasUserModifiedDefaultPosition() {
+        PointF defaultStart = getDefaultStartPosition();
+        return mRestingStackPosition != null
+                && !mRestingStackPosition.equals(defaultStart);
+    }
+
+    /**
+     * Returns the stack position to use if we don't have a saved location or if user education
+     * is being shown, for a normal bubble.
      */
     public PointF getDefaultStartPosition() {
-        // Start on the left if we're in LTR, right otherwise.
-        final boolean startOnLeft =
-                mContext.getResources().getConfiguration().getLayoutDirection()
-                        != LAYOUT_DIRECTION_RTL;
+        return getDefaultStartPosition(false /* isAppBubble */);
+    }
+
+    /**
+     * The stack position to use if we don't have a saved location or if user education
+     * is being shown.
+     *
+     * @param isAppBubble whether this start position is for an app bubble or not.
+     */
+    public PointF getDefaultStartPosition(boolean isAppBubble) {
+        final int layoutDirection = mContext.getResources().getConfiguration().getLayoutDirection();
+        // Normal bubbles start on the left if we're in LTR, right otherwise.
+        // TODO (b/294284894): update language around "app bubble" here
+        // App bubbles start on the right in RTL, left otherwise.
+        final boolean startOnLeft = isAppBubble
+                ? layoutDirection == LAYOUT_DIRECTION_RTL
+                : layoutDirection != LAYOUT_DIRECTION_RTL;
         final RectF allowableStackPositionRegion = getAllowableStackPositionRegion(
                 1 /* default starts with 1 bubble */);
         if (isLargeScreen()) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
index 681998d..52c9bf8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
@@ -1775,13 +1775,26 @@
             return;
         }
 
+        if (firstBubble && bubble.isAppBubble() && !mPositioner.hasUserModifiedDefaultPosition()) {
+            // TODO (b/294284894): update language around "app bubble" here
+            // If it's an app bubble and we don't have a previous resting position, update the
+            // controllers to use the default position for the app bubble (it'd be different from
+            // the position initialized with the controllers originally).
+            PointF startPosition =  mPositioner.getDefaultStartPosition(true /* isAppBubble */);
+            mStackOnLeftOrWillBe = mPositioner.isStackOnLeft(startPosition);
+            mStackAnimationController.setStackPosition(startPosition);
+            mExpandedAnimationController.setCollapsePoint(startPosition);
+            // Set the translation x so that this bubble will animate in from the same side they
+            // expand / collapse on.
+            bubble.getIconView().setTranslationX(startPosition.x);
+        } else if (firstBubble) {
+            mStackOnLeftOrWillBe = mStackAnimationController.isStackOnLeftSide();
+        }
+
         mBubbleContainer.addView(bubble.getIconView(), 0,
                 new FrameLayout.LayoutParams(mPositioner.getBubbleSize(),
                         mPositioner.getBubbleSize()));
 
-        if (firstBubble) {
-            mStackOnLeftOrWillBe = mStackAnimationController.isStackOnLeftSide();
-        }
         // Set the dot position to the opposite of the side the stack is resting on, since the stack
         // resting slightly off-screen would result in the dot also being off-screen.
         bubble.getIconView().setDotBadgeOnLeft(!mStackOnLeftOrWillBe /* onLeft */);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java
index c20733a..4d7042b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java
@@ -131,6 +131,16 @@
 
     private BubbleStackView mBubbleStackView;
 
+    /**
+     * Whether the individual bubble has been dragged out of the row of bubbles far enough to cause
+     * the rest of the bubbles to animate to fill the gap.
+     */
+    private boolean mBubbleDraggedOutEnough = false;
+
+    /** End action to run when the lead bubble's expansion animation completes. */
+    @Nullable
+    private Runnable mLeadBubbleEndAction;
+
     public ExpandedAnimationController(BubblePositioner positioner,
             Runnable onBubbleAnimatedOutAction, BubbleStackView stackView) {
         mPositioner = positioner;
@@ -141,14 +151,12 @@
     }
 
     /**
-     * Whether the individual bubble has been dragged out of the row of bubbles far enough to cause
-     * the rest of the bubbles to animate to fill the gap.
+     * Overrides the collapse location without actually collapsing the stack.
+     * @param point the new collapse location.
      */
-    private boolean mBubbleDraggedOutEnough = false;
-
-    /** End action to run when the lead bubble's expansion animation completes. */
-    @Nullable
-    private Runnable mLeadBubbleEndAction;
+    public void setCollapsePoint(PointF point) {
+        mCollapsePoint = point;
+    }
 
     /**
      * Animates expanding the bubbles into a row along the top of the screen, optionally running an
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java
index 4bb1ab4..aad2683 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java
@@ -297,9 +297,6 @@
 
     /** Whether the stack is on the left side of the screen. */
     public boolean isStackOnLeftSide() {
-        if (mLayout == null || !isStackPositionSet()) {
-            return true; // Default to left, which is where it starts by default.
-        }
         return mPositioner.isStackOnLeft(mStackPosition);
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubblePopupDrawable.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubblePopupDrawable.kt
index 8b5283d..1fd22d0a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubblePopupDrawable.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubblePopupDrawable.kt
@@ -85,7 +85,7 @@
         canvas.drawPath(path, paint)
     }
 
-    override fun onBoundsChange(bounds: Rect?) {
+    override fun onBoundsChange(bounds: Rect) {
         requestPathUpdate()
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipAppOpsListener.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipAppOpsListener.kt
index a141ff9..4abb35c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipAppOpsListener.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipAppOpsListener.kt
@@ -19,7 +19,6 @@
 import android.content.Context
 import android.content.pm.PackageManager
 import com.android.wm.shell.common.ShellExecutor
-import com.android.wm.shell.pip.PipUtils
 
 class PipAppOpsListener(
     private val mContext: Context,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipMediaController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipMediaController.kt
index 2719cd2..427a555 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipMediaController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipMediaController.kt
@@ -33,7 +33,6 @@
 import android.os.HandlerExecutor
 import android.os.UserHandle
 import com.android.wm.shell.R
-import com.android.wm.shell.pip.PipUtils
 import java.util.function.Consumer
 
 /**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipUtils.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipUtils.kt
new file mode 100644
index 0000000..84feb03
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipUtils.kt
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.wm.shell.common.pip
+
+import android.app.ActivityTaskManager
+import android.app.RemoteAction
+import android.app.WindowConfiguration
+import android.content.ComponentName
+import android.content.Context
+import android.os.RemoteException
+import android.os.SystemProperties
+import android.util.DisplayMetrics
+import android.util.Log
+import android.util.Pair
+import android.util.TypedValue
+import android.window.TaskSnapshot
+import com.android.internal.protolog.common.ProtoLog
+import com.android.wm.shell.protolog.ShellProtoLogGroup
+import kotlin.math.abs
+
+/** A class that includes convenience methods.  */
+object PipUtils {
+    private const val TAG = "PipUtils"
+
+    // Minimum difference between two floats (e.g. aspect ratios) to consider them not equal.
+    private const val EPSILON = 1e-7
+    private const val ENABLE_PIP2_IMPLEMENTATION = "persist.wm.debug.enable_pip2_implementation"
+
+    /**
+     * @return the ComponentName and user id of the top non-SystemUI activity in the pinned stack.
+     * The component name may be null if no such activity exists.
+     */
+    @JvmStatic
+    fun getTopPipActivity(context: Context): Pair<ComponentName?, Int> {
+        try {
+            val sysUiPackageName = context.packageName
+            val pinnedTaskInfo = ActivityTaskManager.getService().getRootTaskInfo(
+                WindowConfiguration.WINDOWING_MODE_PINNED,
+                WindowConfiguration.ACTIVITY_TYPE_UNDEFINED
+            )
+            if (pinnedTaskInfo?.childTaskIds != null && pinnedTaskInfo.childTaskIds.isNotEmpty()) {
+                for (i in pinnedTaskInfo.childTaskNames.indices.reversed()) {
+                    val cn = ComponentName.unflattenFromString(
+                        pinnedTaskInfo.childTaskNames[i]
+                    )
+                    if (cn != null && cn.packageName != sysUiPackageName) {
+                        return Pair(cn, pinnedTaskInfo.childTaskUserIds[i])
+                    }
+                }
+            }
+        } catch (e: RemoteException) {
+            ProtoLog.w(
+                ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+                "%s: Unable to get pinned stack.", TAG
+            )
+        }
+        return Pair(null, 0)
+    }
+
+    /**
+     * @return the pixels for a given dp value.
+     */
+    @JvmStatic
+    fun dpToPx(dpValue: Float, dm: DisplayMetrics?): Int {
+        return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dpValue, dm).toInt()
+    }
+
+    /**
+     * @return true if the aspect ratios differ
+     */
+    @JvmStatic
+    fun aspectRatioChanged(aspectRatio1: Float, aspectRatio2: Float): Boolean {
+        return abs(aspectRatio1 - aspectRatio2) > EPSILON
+    }
+
+    /**
+     * Checks whether title, description and intent match.
+     * Comparing icons would be good, but using equals causes false negatives
+     */
+    @JvmStatic
+    fun remoteActionsMatch(action1: RemoteAction?, action2: RemoteAction?): Boolean {
+        if (action1 === action2) return true
+        if (action1 == null || action2 == null) return false
+        return action1.isEnabled == action2.isEnabled &&
+                action1.shouldShowIcon() == action2.shouldShowIcon() &&
+                action1.title == action2.title &&
+                action1.contentDescription == action2.contentDescription &&
+                action1.actionIntent == action2.actionIntent
+    }
+
+    /**
+     * Returns true if the actions in the lists match each other according to
+     * [ ][PipUtils.remoteActionsMatch], including their position.
+     */
+    @JvmStatic
+    fun remoteActionsChanged(list1: List<RemoteAction?>?, list2: List<RemoteAction?>?): Boolean {
+        if (list1 == null && list2 == null) {
+            return false
+        }
+        if (list1 == null || list2 == null) {
+            return true
+        }
+        if (list1.size != list2.size) {
+            return true
+        }
+        for (i in list1.indices) {
+            if (!remoteActionsMatch(list1[i], list2[i])) {
+                return true
+            }
+        }
+        return false
+    }
+
+    /** @return [TaskSnapshot] for a given task id.
+     */
+    @JvmStatic
+    fun getTaskSnapshot(taskId: Int, isLowResolution: Boolean): TaskSnapshot? {
+        return if (taskId <= 0) null else try {
+            ActivityTaskManager.getService().getTaskSnapshot(
+                taskId, isLowResolution, false /* takeSnapshotIfNeeded */
+            )
+        } catch (e: RemoteException) {
+            Log.e(TAG, "Failed to get task snapshot, taskId=$taskId", e)
+            null
+        }
+    }
+
+    @JvmStatic
+    val isPip2ExperimentEnabled: Boolean
+        get() = SystemProperties.getBoolean(ENABLE_PIP2_IMPLEMENTATION, false)
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
index 7bf0893..c111ce6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
@@ -34,6 +34,7 @@
 import android.view.Display;
 import android.view.InsetsSourceControl;
 import android.view.InsetsState;
+import android.view.accessibility.AccessibilityManager;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.wm.shell.ShellTaskOrganizer;
@@ -59,6 +60,7 @@
 import java.util.List;
 import java.util.Set;
 import java.util.function.Consumer;
+import java.util.function.Function;
 import java.util.function.Predicate;
 
 /**
@@ -80,6 +82,9 @@
 
     private static final String TAG = "CompatUIController";
 
+    // The time to wait before education and button hiding
+    private static final int DISAPPEAR_DELAY_MS = 5000;
+
     /** Whether the IME is shown on display id. */
     private final Set<Integer> mDisplaysWithIme = new ArraySet<>(1);
 
@@ -158,6 +163,9 @@
     @NonNull
     private final CompatUIShellCommandHandler mCompatUIShellCommandHandler;
 
+    @NonNull
+    private final Function<Integer, Integer> mDisappearTimeSupplier;
+
     @Nullable
     private CompatUICallback mCompatUICallback;
 
@@ -176,7 +184,8 @@
             @NonNull Lazy<Transitions> transitionsLazy,
             @NonNull DockStateReader dockStateReader,
             @NonNull CompatUIConfiguration compatUIConfiguration,
-            @NonNull CompatUIShellCommandHandler compatUIShellCommandHandler) {
+            @NonNull CompatUIShellCommandHandler compatUIShellCommandHandler,
+            @NonNull AccessibilityManager accessibilityManager) {
         mContext = context;
         mShellController = shellController;
         mDisplayController = displayController;
@@ -189,6 +198,8 @@
         mDockStateReader = dockStateReader;
         mCompatUIConfiguration = compatUIConfiguration;
         mCompatUIShellCommandHandler = compatUIShellCommandHandler;
+        mDisappearTimeSupplier = flags -> accessibilityManager.getRecommendedTimeoutMillis(
+                DISAPPEAR_DELAY_MS, flags);
         shellInit.addInitCallback(this::onInit, this);
     }
 
@@ -510,7 +521,8 @@
             ShellTaskOrganizer.TaskListener taskListener) {
         return new ReachabilityEduWindowManager(context, taskInfo, mSyncQueue,
                 taskListener, mDisplayController.getDisplayLayout(taskInfo.displayId),
-                mCompatUIConfiguration, mMainExecutor, this::onInitialReachabilityEduDismissed);
+                mCompatUIConfiguration, mMainExecutor, this::onInitialReachabilityEduDismissed,
+                mDisappearTimeSupplier);
     }
 
     private void onInitialReachabilityEduDismissed(@NonNull TaskInfo taskInfo,
@@ -556,7 +568,8 @@
             @Nullable ShellTaskOrganizer.TaskListener taskListener) {
         return new UserAspectRatioSettingsWindowManager(context, taskInfo, mSyncQueue,
                 taskListener, mDisplayController.getDisplayLayout(taskInfo.displayId),
-                mCompatUIHintsState, this::launchUserAspectRatioSettings, mMainExecutor);
+                mCompatUIHintsState, this::launchUserAspectRatioSettings, mMainExecutor,
+                mDisappearTimeSupplier);
     }
 
     private void launchUserAspectRatioSettings(
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/ReachabilityEduWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/ReachabilityEduWindowManager.java
index 9de3f9d..5612bc8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/ReachabilityEduWindowManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/ReachabilityEduWindowManager.java
@@ -28,6 +28,7 @@
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.WindowManager;
+import android.view.accessibility.AccessibilityManager;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.wm.shell.R;
@@ -37,15 +38,13 @@
 import com.android.wm.shell.common.SyncTransactionQueue;
 
 import java.util.function.BiConsumer;
+import java.util.function.Function;
 
 /**
  * Window manager for the reachability education
  */
 class ReachabilityEduWindowManager extends CompatUIWindowManagerAbstract {
 
-    // The time to wait before hiding the education
-    private static final long DISAPPEAR_DELAY_MS = 4000L;
-
     private static final int REACHABILITY_LEFT_OR_UP_POSITION = 0;
     private static final int REACHABILITY_RIGHT_OR_BOTTOM_POSITION = 2;
 
@@ -77,6 +76,8 @@
 
     private final BiConsumer<TaskInfo, ShellTaskOrganizer.TaskListener> mOnDismissCallback;
 
+    private final Function<Integer, Integer> mDisappearTimeSupplier;
+
     @Nullable
     @VisibleForTesting
     ReachabilityEduLayout mLayout;
@@ -85,7 +86,8 @@
             SyncTransactionQueue syncQueue,
             ShellTaskOrganizer.TaskListener taskListener, DisplayLayout displayLayout,
             CompatUIConfiguration compatUIConfiguration, ShellExecutor mainExecutor,
-            BiConsumer<TaskInfo, ShellTaskOrganizer.TaskListener> onDismissCallback) {
+            BiConsumer<TaskInfo, ShellTaskOrganizer.TaskListener> onDismissCallback,
+            Function<Integer, Integer> disappearTimeSupplier) {
         super(context, taskInfo, syncQueue, taskListener, displayLayout);
         mIsActivityLetterboxed = taskInfo.isLetterboxDoubleTapEnabled;
         mLetterboxVerticalPosition = taskInfo.topActivityLetterboxVerticalPosition;
@@ -95,6 +97,7 @@
         mCompatUIConfiguration = compatUIConfiguration;
         mMainExecutor = mainExecutor;
         mOnDismissCallback = onDismissCallback;
+        mDisappearTimeSupplier = disappearTimeSupplier;
     }
 
     @Override
@@ -215,7 +218,12 @@
     }
 
     void updateHideTime() {
-        mNextHideTime = SystemClock.uptimeMillis() + DISAPPEAR_DELAY_MS;
+        mNextHideTime = SystemClock.uptimeMillis() + getDisappearTimeMs();
+    }
+
+    private long getDisappearTimeMs() {
+        return mDisappearTimeSupplier.apply(
+                AccessibilityManager.FLAG_CONTENT_ICONS | AccessibilityManager.FLAG_CONTENT_TEXT);
     }
 
     private void updateVisibilityOfViews() {
@@ -248,14 +256,15 @@
                     availableHeight, mCompatUIConfiguration, lastTaskInfo);
             if (!mHasLetterboxSizeChanged) {
                 updateHideTime();
-                mMainExecutor.executeDelayed(this::hideReachability, DISAPPEAR_DELAY_MS);
+                final long disappearTimeMs = getDisappearTimeMs();
+                mMainExecutor.executeDelayed(this::hideReachability, disappearTimeMs);
                 // If reachability education has been seen for the first time, trigger callback to
                 // display aspect ratio settings button once reachability education disappears
                 if (hasShownHorizontalReachabilityEduFirstTime(hasSeenHorizontalReachabilityEdu)
                         || hasShownVerticalReachabilityEduFirstTime(
                         hasSeenVerticalReachabilityEdu)) {
                     mMainExecutor.executeDelayed(this::triggerOnDismissCallback,
-                            DISAPPEAR_DELAY_MS);
+                            disappearTimeMs);
                 }
             }
             mHasUserDoubleTapped = false;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManager.java
index bd53dc7..cbff464 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManager.java
@@ -26,6 +26,7 @@
 import android.os.SystemClock;
 import android.view.LayoutInflater;
 import android.view.View;
+import android.view.accessibility.AccessibilityManager;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.wm.shell.R;
@@ -36,6 +37,7 @@
 import com.android.wm.shell.compatui.CompatUIController.CompatUIHintsState;
 
 import java.util.function.BiConsumer;
+import java.util.function.Function;
 
 /**
  * Window manager for the user aspect ratio settings button which allows users to go to
@@ -45,12 +47,12 @@
 
     private static final long SHOW_USER_ASPECT_RATIO_BUTTON_DELAY_MS = 500L;
 
-    private static final long HIDE_USER_ASPECT_RATIO_BUTTON_DELAY_MS = 4000L;
-
     private long mNextButtonHideTimeMs = -1L;
 
     private final BiConsumer<TaskInfo, ShellTaskOrganizer.TaskListener> mOnButtonClicked;
 
+    private final Function<Integer, Integer> mDisappearTimeSupplier;
+
     private final ShellExecutor mShellExecutor;
 
     @VisibleForTesting
@@ -69,12 +71,14 @@
             @Nullable ShellTaskOrganizer.TaskListener taskListener,
             @NonNull DisplayLayout displayLayout, @NonNull CompatUIHintsState compatUIHintsState,
             @NonNull BiConsumer<TaskInfo, ShellTaskOrganizer.TaskListener> onButtonClicked,
-            @NonNull ShellExecutor shellExecutor) {
+            @NonNull ShellExecutor shellExecutor,
+            @NonNull Function<Integer, Integer> disappearTimeSupplier) {
         super(context, taskInfo, syncQueue, taskListener, displayLayout);
         mShellExecutor = shellExecutor;
         mHasUserAspectRatioSettingsButton = getHasUserAspectRatioSettingsButton(taskInfo);
         mCompatUIHintsState = compatUIHintsState;
         mOnButtonClicked = onButtonClicked;
+        mDisappearTimeSupplier = disappearTimeSupplier;
     }
 
     @Override
@@ -140,9 +144,9 @@
             return;
         }
         mLayout.setUserAspectRatioSettingsHintVisibility(/* show= */ true);
-        mNextButtonHideTimeMs = updateHideTime(HIDE_USER_ASPECT_RATIO_BUTTON_DELAY_MS);
-        mShellExecutor.executeDelayed(this::hideUserAspectRatioButton,
-                HIDE_USER_ASPECT_RATIO_BUTTON_DELAY_MS);
+        final long disappearTimeMs = getDisappearTimeMs();
+        mNextButtonHideTimeMs = updateHideTime(disappearTimeMs);
+        mShellExecutor.executeDelayed(this::hideUserAspectRatioButton, disappearTimeMs);
     }
 
     @Override
@@ -167,9 +171,9 @@
         if (mHasUserAspectRatioSettingsButton) {
             mShellExecutor.executeDelayed(this::showUserAspectRatioButton,
                     SHOW_USER_ASPECT_RATIO_BUTTON_DELAY_MS);
-            mNextButtonHideTimeMs = updateHideTime(HIDE_USER_ASPECT_RATIO_BUTTON_DELAY_MS);
-            mShellExecutor.executeDelayed(this::hideUserAspectRatioButton,
-                    HIDE_USER_ASPECT_RATIO_BUTTON_DELAY_MS);
+            final long disappearTimeMs = getDisappearTimeMs();
+            mNextButtonHideTimeMs = updateHideTime(disappearTimeMs);
+            mShellExecutor.executeDelayed(this::hideUserAspectRatioButton, disappearTimeMs);
         } else {
             mShellExecutor.removeCallbacks(this::showUserAspectRatioButton);
             mShellExecutor.execute(this::hideUserAspectRatioButton);
@@ -208,4 +212,8 @@
                 && (taskInfo.topActivityBoundsLetterboxed
                     || taskInfo.isUserFullscreenOverrideEnabled);
     }
+
+    private long getDisappearTimeMs() {
+        return mDisappearTimeSupplier.apply(AccessibilityManager.FLAG_CONTENT_CONTROLS);
+    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
index aafd9fd..b454807 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
@@ -24,6 +24,7 @@
 import android.os.Handler;
 import android.os.SystemProperties;
 import android.view.IWindowManager;
+import android.view.accessibility.AccessibilityManager;
 
 import com.android.internal.logging.UiEventLogger;
 import com.android.launcher3.icons.IconProvider;
@@ -232,10 +233,12 @@
             DisplayImeController imeController, SyncTransactionQueue syncQueue,
             @ShellMainThread ShellExecutor mainExecutor, Lazy<Transitions> transitionsLazy,
             DockStateReader dockStateReader, CompatUIConfiguration compatUIConfiguration,
-            CompatUIShellCommandHandler compatUIShellCommandHandler) {
+            CompatUIShellCommandHandler compatUIShellCommandHandler,
+            AccessibilityManager accessibilityManager) {
         return new CompatUIController(context, shellInit, shellController, displayController,
                 displayInsetsController, imeController, syncQueue, mainExecutor, transitionsLazy,
-                dockStateReader, compatUIConfiguration, compatUIShellCommandHandler);
+                dockStateReader, compatUIConfiguration, compatUIShellCommandHandler,
+                accessibilityManager);
     }
 
     @WMSingleton
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java
index 24ef44a..4e92ca1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java
@@ -34,6 +34,7 @@
 import com.android.wm.shell.common.pip.PipAppOpsListener;
 import com.android.wm.shell.common.pip.PipMediaController;
 import com.android.wm.shell.common.pip.PipUiEventLogger;
+import com.android.wm.shell.common.pip.PipUtils;
 import com.android.wm.shell.common.pip.SizeSpecSource;
 import com.android.wm.shell.dagger.WMShellBaseModule;
 import com.android.wm.shell.dagger.WMSingleton;
@@ -50,7 +51,6 @@
 import com.android.wm.shell.pip.PipTransition;
 import com.android.wm.shell.pip.PipTransitionController;
 import com.android.wm.shell.pip.PipTransitionState;
-import com.android.wm.shell.pip.PipUtils;
 import com.android.wm.shell.pip.phone.PhonePipKeepClearAlgorithm;
 import com.android.wm.shell.pip.phone.PhonePipMenuController;
 import com.android.wm.shell.pip.phone.PipController;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/PipModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/PipModule.java
index 04032bb1..9c9364e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/PipModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/PipModule.java
@@ -18,9 +18,9 @@
 
 import android.annotation.Nullable;
 
+import com.android.wm.shell.common.pip.PipUtils;
 import com.android.wm.shell.dagger.WMSingleton;
 import com.android.wm.shell.pip.PipTransitionController;
-import com.android.wm.shell.pip.PipUtils;
 
 import dagger.Module;
 import dagger.Provides;
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 633f627..b0f75c6 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
@@ -311,7 +311,7 @@
         )
         val wct = WindowContainerTransaction()
         wct.setWindowingMode(task.token, WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW)
-        wct.setBounds(task.token, null)
+        wct.setBounds(task.token, Rect())
         wct.setDensityDpi(task.token, getDefaultDensityDpi())
         if (Transitions.ENABLE_SHELL_TRANSITIONS) {
             transitions.startTransition(TRANSIT_CHANGE, wct, null /* handler */)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java
index ac711ea..4fef672 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java
@@ -28,6 +28,7 @@
 import android.view.Gravity;
 
 import com.android.wm.shell.R;
+import com.android.wm.shell.common.pip.PipUtils;
 import com.android.wm.shell.common.pip.SizeSpecSource;
 
 import java.io.PrintWriter;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipDisplayLayoutState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipDisplayLayoutState.java
index 456f85b..4aa260b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipDisplayLayoutState.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipDisplayLayoutState.java
@@ -16,7 +16,7 @@
 
 package com.android.wm.shell.pip;
 
-import static com.android.wm.shell.pip.PipUtils.dpToPx;
+import static com.android.wm.shell.common.pip.PipUtils.dpToPx;
 
 import android.content.Context;
 import android.content.res.Resources;
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 296857b..ed9ff1c 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
@@ -83,6 +83,7 @@
 import com.android.wm.shell.common.SyncTransactionQueue;
 import com.android.wm.shell.common.annotations.ShellMainThread;
 import com.android.wm.shell.common.pip.PipUiEventLogger;
+import com.android.wm.shell.common.pip.PipUtils;
 import com.android.wm.shell.pip.phone.PipMotionHelper;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
 import com.android.wm.shell.splitscreen.SplitScreenController;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
index db7e2c0..83e03dc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
@@ -64,6 +64,7 @@
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.wm.shell.R;
 import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.pip.PipUtils;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
 import com.android.wm.shell.splitscreen.SplitScreenController;
 import com.android.wm.shell.sysui.ShellInit;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
index 0f74f9e..64bba67 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
@@ -38,6 +38,7 @@
 import androidx.annotation.NonNull;
 
 import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.pip.PipUtils;
 import com.android.wm.shell.common.split.SplitScreenUtils;
 import com.android.wm.shell.sysui.ShellInit;
 import com.android.wm.shell.transition.Transitions;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipUtils.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipUtils.java
deleted file mode 100644
index 3cd9848..0000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipUtils.java
+++ /dev/null
@@ -1,145 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.pip;
-
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
-import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
-import static android.util.TypedValue.COMPLEX_UNIT_DIP;
-
-import android.annotation.Nullable;
-import android.app.ActivityTaskManager;
-import android.app.ActivityTaskManager.RootTaskInfo;
-import android.app.RemoteAction;
-import android.content.ComponentName;
-import android.content.Context;
-import android.os.RemoteException;
-import android.os.SystemProperties;
-import android.util.DisplayMetrics;
-import android.util.Log;
-import android.util.Pair;
-import android.util.TypedValue;
-import android.window.TaskSnapshot;
-
-import com.android.internal.protolog.common.ProtoLog;
-import com.android.wm.shell.protolog.ShellProtoLogGroup;
-
-import java.util.List;
-import java.util.Objects;
-
-/** A class that includes convenience methods. */
-public class PipUtils {
-    private static final String TAG = "PipUtils";
-
-    // Minimum difference between two floats (e.g. aspect ratios) to consider them not equal.
-    private static final double EPSILON = 1e-7;
-
-    private static final String ENABLE_PIP2_IMPLEMENTATION =
-            "persist.wm.debug.enable_pip2_implementation";
-
-    /**
-     * @return the ComponentName and user id of the top non-SystemUI activity in the pinned stack.
-     * The component name may be null if no such activity exists.
-     */
-    public static Pair<ComponentName, Integer> getTopPipActivity(Context context) {
-        try {
-            final String sysUiPackageName = context.getPackageName();
-            final RootTaskInfo pinnedTaskInfo = ActivityTaskManager.getService().getRootTaskInfo(
-                    WINDOWING_MODE_PINNED, ACTIVITY_TYPE_UNDEFINED);
-            if (pinnedTaskInfo != null && pinnedTaskInfo.childTaskIds != null
-                    && pinnedTaskInfo.childTaskIds.length > 0) {
-                for (int i = pinnedTaskInfo.childTaskNames.length - 1; i >= 0; i--) {
-                    ComponentName cn = ComponentName.unflattenFromString(
-                            pinnedTaskInfo.childTaskNames[i]);
-                    if (cn != null && !cn.getPackageName().equals(sysUiPackageName)) {
-                        return new Pair<>(cn, pinnedTaskInfo.childTaskUserIds[i]);
-                    }
-                }
-            }
-        } catch (RemoteException e) {
-            ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
-                    "%s: Unable to get pinned stack.", TAG);
-        }
-        return new Pair<>(null, 0);
-    }
-
-    /**
-     * @return the pixels for a given dp value.
-     */
-    public static int dpToPx(float dpValue, DisplayMetrics dm) {
-        return (int) TypedValue.applyDimension(COMPLEX_UNIT_DIP, dpValue, dm);
-    }
-
-    /**
-     * @return true if the aspect ratios differ
-     */
-    public static boolean aspectRatioChanged(float aspectRatio1, float aspectRatio2) {
-        return Math.abs(aspectRatio1 - aspectRatio2) > EPSILON;
-    }
-
-    /**
-     * Checks whether title, description and intent match.
-     * Comparing icons would be good, but using equals causes false negatives
-     */
-    public static boolean remoteActionsMatch(RemoteAction action1, RemoteAction action2) {
-        if (action1 == action2) return true;
-        if (action1 == null || action2 == null) return false;
-        return action1.isEnabled() == action2.isEnabled()
-                && action1.shouldShowIcon() == action2.shouldShowIcon()
-                && Objects.equals(action1.getTitle(), action2.getTitle())
-                && Objects.equals(action1.getContentDescription(), action2.getContentDescription())
-                && Objects.equals(action1.getActionIntent(), action2.getActionIntent());
-    }
-
-    /**
-     * Returns true if the actions in the lists match each other according to {@link
-     * PipUtils#remoteActionsMatch(RemoteAction, RemoteAction)}, including their position.
-     */
-    public static boolean remoteActionsChanged(List<RemoteAction> list1, List<RemoteAction> list2) {
-        if (list1 == null && list2 == null) {
-            return false;
-        }
-        if (list1 == null || list2 == null) {
-            return true;
-        }
-        if (list1.size() != list2.size()) {
-            return true;
-        }
-        for (int i = 0; i < list1.size(); i++) {
-            if (!remoteActionsMatch(list1.get(i), list2.get(i))) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    /** @return {@link TaskSnapshot} for a given task id. */
-    @Nullable
-    public static TaskSnapshot getTaskSnapshot(int taskId, boolean isLowResolution) {
-        if (taskId <= 0) return null;
-        try {
-            return ActivityTaskManager.getService().getTaskSnapshot(
-                    taskId, isLowResolution, false /* takeSnapshotIfNeeded */);
-        } catch (RemoteException e) {
-            Log.e(TAG, "Failed to get task snapshot, taskId=" + taskId, e);
-            return null;
-        }
-    }
-
-    public static boolean isPip2ExperimentEnabled() {
-        return SystemProperties.getBoolean(ENABLE_PIP2_IMPLEMENTATION, false);
-    }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
index 5c65d78..ddea574 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
@@ -77,6 +77,7 @@
 import com.android.wm.shell.common.TaskStackListenerImpl;
 import com.android.wm.shell.common.pip.PipAppOpsListener;
 import com.android.wm.shell.common.pip.PipMediaController;
+import com.android.wm.shell.common.pip.PipUtils;
 import com.android.wm.shell.onehanded.OneHandedController;
 import com.android.wm.shell.onehanded.OneHandedTransitionCallback;
 import com.android.wm.shell.pip.IPip;
@@ -93,7 +94,6 @@
 import com.android.wm.shell.pip.PipTaskOrganizer;
 import com.android.wm.shell.pip.PipTransitionController;
 import com.android.wm.shell.pip.PipTransitionState;
-import com.android.wm.shell.pip.PipUtils;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
 import com.android.wm.shell.sysui.ConfigurationChangeListener;
 import com.android.wm.shell.sysui.KeyguardChangeListener;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java
index 837426a..fc34772 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java
@@ -67,7 +67,7 @@
 import com.android.wm.shell.animation.Interpolators;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.pip.PipUiEventLogger;
-import com.android.wm.shell.pip.PipUtils;
+import com.android.wm.shell.common.pip.PipUtils;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
 import com.android.wm.shell.splitscreen.SplitScreenController;
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
index bfba4b4..cb4e6c8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
@@ -51,13 +51,13 @@
 import com.android.wm.shell.common.FloatingContentCoordinator;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.pip.PipUiEventLogger;
+import com.android.wm.shell.common.pip.PipUtils;
 import com.android.wm.shell.common.pip.SizeSpecSource;
 import com.android.wm.shell.pip.PipAnimationController;
 import com.android.wm.shell.pip.PipBoundsAlgorithm;
 import com.android.wm.shell.pip.PipBoundsState;
 import com.android.wm.shell.pip.PipTaskOrganizer;
 import com.android.wm.shell.pip.PipTransitionController;
-import com.android.wm.shell.pip.PipUtils;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
 import com.android.wm.shell.sysui.ShellInit;
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipActionsProvider.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipActionsProvider.java
index 7dc400c..4bba969 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipActionsProvider.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipActionsProvider.java
@@ -36,7 +36,7 @@
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.wm.shell.R;
 import com.android.wm.shell.common.pip.PipMediaController;
-import com.android.wm.shell.pip.PipUtils;
+import com.android.wm.shell.common.pip.PipUtils;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
 
 import java.util.ArrayList;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java
index 613791c..45e1cde 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java
@@ -55,7 +55,7 @@
 import com.android.internal.widget.RecyclerView;
 import com.android.wm.shell.R;
 import com.android.wm.shell.common.TvWindowMenuActionButton;
-import com.android.wm.shell.pip.PipUtils;
+import com.android.wm.shell.common.pip.PipUtils;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
 
 import java.util.List;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipNotificationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipNotificationController.java
index 39c7a4b..1c94625 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipNotificationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipNotificationController.java
@@ -37,8 +37,8 @@
 import com.android.internal.util.ImageUtils;
 import com.android.wm.shell.R;
 import com.android.wm.shell.common.pip.PipMediaController;
+import com.android.wm.shell.common.pip.PipUtils;
 import com.android.wm.shell.pip.PipParamsChangedForwarder;
-import com.android.wm.shell.pip.PipUtils;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
 
 import java.util.List;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTaskOrganizer.java
index dbec607..6720804 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTaskOrganizer.java
@@ -26,6 +26,7 @@
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.SyncTransactionQueue;
 import com.android.wm.shell.common.pip.PipUiEventLogger;
+import com.android.wm.shell.common.pip.PipUtils;
 import com.android.wm.shell.pip.PipAnimationController;
 import com.android.wm.shell.pip.PipBoundsAlgorithm;
 import com.android.wm.shell.pip.PipBoundsState;
@@ -36,7 +37,6 @@
 import com.android.wm.shell.pip.PipTaskOrganizer;
 import com.android.wm.shell.pip.PipTransitionController;
 import com.android.wm.shell.pip.PipTransitionState;
-import com.android.wm.shell.pip.PipUtils;
 import com.android.wm.shell.splitscreen.SplitScreenController;
 
 import java.util.Objects;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/OWNERS
new file mode 100644
index 0000000..ec09827
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/OWNERS
@@ -0,0 +1,3 @@
+# WM shell sub-module pip owner
+hwwang@google.com
+mateuszc@google.com
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
index 7df658e..d310ae3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
@@ -37,8 +37,12 @@
 import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS;
 import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_UNSPECIFIED;
 import static android.view.WindowManager.TRANSIT_CHANGE;
+import static android.view.WindowManager.TRANSIT_CLOSE;
 import static android.view.WindowManager.TRANSIT_KEYGUARD_UNOCCLUDE;
+import static android.view.WindowManager.TRANSIT_OPEN;
 import static android.view.WindowManager.TRANSIT_RELAUNCH;
+import static android.view.WindowManager.TRANSIT_TO_FRONT;
+import static android.window.TransitionInfo.FLAGS_IS_NON_APP_WINDOW;
 import static android.window.TransitionInfo.FLAG_BACK_GESTURE_ANIMATED;
 import static android.window.TransitionInfo.FLAG_CROSS_PROFILE_OWNER_THUMBNAIL;
 import static android.window.TransitionInfo.FLAG_CROSS_PROFILE_WORK_THUMBNAIL;
@@ -334,6 +338,10 @@
         boolean isDisplayRotationAnimationStarted = false;
         final boolean isDreamTransition = isDreamTransition(info);
         final boolean isOnlyTranslucent = isOnlyTranslucent(info);
+        final boolean isActivityReplace = checkActivityReplacement(info, startTransaction);
+        // Some patterns (eg. activity "replacement") require us to re-interpret the type
+        @WindowManager.TransitionType final int transitType =
+                isActivityReplace ? TRANSIT_OPEN : info.getType();
 
         for (int i = info.getChanges().size() - 1; i >= 0; --i) {
             final TransitionInfo.Change change = info.getChanges().get(i);
@@ -430,7 +438,8 @@
             // Don't animate anything that isn't independent.
             if (!TransitionInfo.isIndependent(change, info)) continue;
 
-            Animation a = loadAnimation(info, change, wallpaperTransit, isDreamTransition);
+            Animation a = loadAnimation(transitType, info, change, wallpaperTransit,
+                    isDreamTransition);
             if (a != null) {
                 if (isTask) {
                     final boolean isTranslucent = (change.getFlags() & FLAG_TRANSLUCENT) != 0;
@@ -604,6 +613,53 @@
         return (translucentOpen + translucentClose) > 0;
     }
 
+    /**
+     * Checks for an edge-case where an activity calls finish() followed immediately by
+     * startActivity() to "replace" itself. If in this case, it will swap the layer of the
+     * close/open activities and return `true`. This way, we pretend like we are just "opening"
+     * the new activity.
+     */
+    private static boolean checkActivityReplacement(@NonNull TransitionInfo info,
+            SurfaceControl.Transaction t) {
+        if (info.getType() != TRANSIT_CLOSE) {
+            return false;
+        }
+        int closing = -1;
+        int opening = -1;
+        for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+            final TransitionInfo.Change change = info.getChanges().get(i);
+            if ((change.getTaskInfo() != null || change.hasFlags(FLAG_IS_DISPLAY))
+                    && !TransitionUtil.isOrderOnly(change)) {
+                // This isn't an activity-level transition.
+                return false;
+            }
+            if (change.getTaskInfo() != null
+                    && change.hasFlags(FLAG_IS_DISPLAY | FLAGS_IS_NON_APP_WINDOW)) {
+                // Ignore non-activity containers.
+                continue;
+            }
+            if (TransitionUtil.isClosingType(change.getMode())) {
+                closing = i;
+            } else if (change.getMode() == TRANSIT_OPEN) {
+                // OPEN implies that it is a new launch. If going "back" the opening app will be
+                // TO_FRONT
+                opening = i;
+            } else if (change.getMode() == TRANSIT_TO_FRONT) {
+                // Normal "going back", so not a replacement.
+                return false;
+            }
+        }
+        if (closing < 0 || opening < 0) {
+            return false;
+        }
+        // Swap the opening and closing z-orders since we're swapping the transit type.
+        final int numChanges = info.getChanges().size();
+        final int zSplitLine = numChanges + 1;
+        t.setLayer(info.getChanges().get(opening).getLeash(), zSplitLine + numChanges - opening);
+        t.setLayer(info.getChanges().get(closing).getLeash(), zSplitLine - closing);
+        return true;
+    }
+
     @Override
     public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
             @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
@@ -656,12 +712,11 @@
     }
 
     @Nullable
-    private Animation loadAnimation(@NonNull TransitionInfo info,
+    private Animation loadAnimation(int type, @NonNull TransitionInfo info,
             @NonNull TransitionInfo.Change change, int wallpaperTransit,
             boolean isDreamTransition) {
         Animation a;
 
-        final int type = info.getType();
         final int flags = info.getFlags();
         final int changeMode = change.getMode();
         final int changeFlags = change.getFlags();
@@ -716,8 +771,8 @@
             // If there's a scene-transition, then jump-cut.
             return null;
         } else {
-            a = loadAttributeAnimation(
-                    info, change, wallpaperTransit, mTransitionAnimation, isDreamTransition);
+            a = loadAttributeAnimation(type, info, change, wallpaperTransit, mTransitionAnimation,
+                    isDreamTransition);
         }
 
         if (a != null) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java
index d978eaf..7b1ce2f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java
@@ -45,6 +45,7 @@
 import android.graphics.Shader;
 import android.view.Surface;
 import android.view.SurfaceControl;
+import android.view.WindowManager;
 import android.view.animation.Animation;
 import android.view.animation.Transformation;
 import android.window.ScreenCapture;
@@ -61,10 +62,10 @@
 
     /** Loads the animation that is defined through attribute id for the given transition. */
     @Nullable
-    public static Animation loadAttributeAnimation(@NonNull TransitionInfo info,
-            @NonNull TransitionInfo.Change change, int wallpaperTransit,
-            @NonNull TransitionAnimation transitionAnimation, boolean isDreamTransition) {
-        final int type = info.getType();
+    public static Animation loadAttributeAnimation(@WindowManager.TransitionType int type,
+            @NonNull TransitionInfo info, @NonNull TransitionInfo.Change change,
+            int wallpaperTransit, @NonNull TransitionAnimation transitionAnimation,
+            boolean isDreamTransition) {
         final int changeMode = change.getMode();
         final int changeFlags = change.getFlags();
         final boolean enter = TransitionUtil.isOpeningType(changeMode);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/ShellUnfoldProgressProvider.java b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/ShellUnfoldProgressProvider.java
index 367676f..f7a060f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/ShellUnfoldProgressProvider.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/ShellUnfoldProgressProvider.java
@@ -46,5 +46,7 @@
         default void onStateChangeProgress(@FloatRange(from = 0.0, to = 1.0) float progress) {}
 
         default void onStateChangeFinished() {}
+
+        default void onFoldStateChanged(boolean isFolded) {}
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java
index 2eb6e71..68b5a81 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java
@@ -63,6 +63,7 @@
     @Nullable
     private IBinder mTransition;
 
+    private boolean mAnimationFinished = false;
     private final List<UnfoldTaskAnimator> mAnimators = new ArrayList<>();
 
     public UnfoldTransitionHandler(ShellInit shellInit,
@@ -132,6 +133,13 @@
 
         startTransaction.apply();
         mFinishCallback = finishCallback;
+
+        // Shell transition started when unfold animation has already finished,
+        // finish shell transition immediately
+        if (mAnimationFinished) {
+            finishTransitionIfNeeded();
+        }
+
         return true;
     }
 
@@ -161,17 +169,8 @@
 
     @Override
     public void onStateChangeFinished() {
-        if (mFinishCallback == null) return;
-
-        for (int i = 0; i < mAnimators.size(); i++) {
-            final UnfoldTaskAnimator animator = mAnimators.get(i);
-            animator.clearTasks();
-            animator.stop();
-        }
-
-        mFinishCallback.onTransitionFinished(null);
-        mFinishCallback = null;
-        mTransition = null;
+        mAnimationFinished = true;
+        finishTransitionIfNeeded();
     }
 
     @Override
@@ -218,4 +217,25 @@
     public boolean willHandleTransition() {
         return mTransition != null;
     }
+
+    @Override
+    public void onFoldStateChanged(boolean isFolded) {
+        if (isFolded) {
+            mAnimationFinished = false;
+        }
+    }
+
+    private void finishTransitionIfNeeded() {
+        if (mFinishCallback == null) return;
+
+        for (int i = 0; i < mAnimators.size(); i++) {
+            final UnfoldTaskAnimator animator = mAnimators.get(i);
+            animator.clearTasks();
+            animator.stop();
+        }
+
+        mFinishCallback.onTransitionFinished(null);
+        mFinishCallback = null;
+        mTransition = null;
+    }
 }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblePositionerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblePositionerTest.java
index 139724f..58d9a64 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblePositionerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblePositionerTest.java
@@ -203,6 +203,60 @@
         assertThat(restingPosition.y).isEqualTo(getDefaultYPosition());
     }
 
+    /** Test that the default resting position on tablet is middle right. */
+    @Test
+    public void testGetDefaultPosition_appBubble_onTablet() {
+        new WindowManagerConfig().setLargeScreen().setUpConfig();
+        mPositioner.update();
+
+        RectF allowableStackRegion =
+                mPositioner.getAllowableStackPositionRegion(1 /* bubbleCount */);
+        PointF startPosition = mPositioner.getDefaultStartPosition(true /* isAppBubble */);
+
+        assertThat(startPosition.x).isEqualTo(allowableStackRegion.right);
+        assertThat(startPosition.y).isEqualTo(getDefaultYPosition());
+    }
+
+    @Test
+    public void testGetRestingPosition_appBubble_onTablet_RTL() {
+        new WindowManagerConfig().setLargeScreen().setLayoutDirection(
+                LAYOUT_DIRECTION_RTL).setUpConfig();
+        mPositioner.update();
+
+        RectF allowableStackRegion =
+                mPositioner.getAllowableStackPositionRegion(1 /* bubbleCount */);
+        PointF startPosition = mPositioner.getDefaultStartPosition(true /* isAppBubble */);
+
+        assertThat(startPosition.x).isEqualTo(allowableStackRegion.left);
+        assertThat(startPosition.y).isEqualTo(getDefaultYPosition());
+    }
+
+    @Test
+    public void testHasUserModifiedDefaultPosition_false() {
+        new WindowManagerConfig().setLargeScreen().setLayoutDirection(
+                LAYOUT_DIRECTION_RTL).setUpConfig();
+        mPositioner.update();
+
+        assertThat(mPositioner.hasUserModifiedDefaultPosition()).isFalse();
+
+        mPositioner.setRestingPosition(mPositioner.getDefaultStartPosition());
+
+        assertThat(mPositioner.hasUserModifiedDefaultPosition()).isFalse();
+    }
+
+    @Test
+    public void testHasUserModifiedDefaultPosition_true() {
+        new WindowManagerConfig().setLargeScreen().setLayoutDirection(
+                LAYOUT_DIRECTION_RTL).setUpConfig();
+        mPositioner.update();
+
+        assertThat(mPositioner.hasUserModifiedDefaultPosition()).isFalse();
+
+        mPositioner.setRestingPosition(new PointF(0, 100));
+
+        assertThat(mPositioner.hasUserModifiedDefaultPosition()).isTrue();
+    }
+
     /**
      * Calculates the Y position bubbles should be placed based on the config. Based on
      * the calculations in {@link BubblePositioner#getDefaultStartPosition()} and
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java
index efc69ebd..9b9600e 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java
@@ -41,6 +41,7 @@
 import android.testing.AndroidTestingRunner;
 import android.view.InsetsSource;
 import android.view.InsetsState;
+import android.view.accessibility.AccessibilityManager;
 
 import androidx.test.filters.SmallTest;
 
@@ -113,6 +114,9 @@
     @Mock
     private CompatUIShellCommandHandler mCompatUIShellCommandHandler;
 
+    @Mock
+    private AccessibilityManager mAccessibilityManager;
+
     @Captor
     ArgumentCaptor<OnInsetsChangedListener> mOnInsetsChangedListenerCaptor;
 
@@ -139,7 +143,7 @@
         mController = new CompatUIController(mContext, mShellInit, mMockShellController,
                 mMockDisplayController, mMockDisplayInsetsController, mMockImeController,
                 mMockSyncQueue, mMockExecutor, mMockTransitionsLazy, mDockStateReader,
-                mCompatUIConfiguration, mCompatUIShellCommandHandler) {
+                mCompatUIConfiguration, mCompatUIShellCommandHandler, mAccessibilityManager) {
             @Override
             CompatUIWindowManager createCompatUiWindowManager(Context context, TaskInfo taskInfo,
                     ShellTaskOrganizer.TaskListener taskListener) {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/ReachabilityEduWindowManagerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/ReachabilityEduWindowManagerTest.java
index a802f15a..5867a85 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/ReachabilityEduWindowManagerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/ReachabilityEduWindowManagerTest.java
@@ -109,6 +109,6 @@
     private ReachabilityEduWindowManager createReachabilityEduWindowManager(TaskInfo taskInfo) {
         return new ReachabilityEduWindowManager(mContext, taskInfo, mSyncTransactionQueue,
                 mTaskListener, mDisplayLayout, mCompatUIConfiguration, mExecutor,
-                mOnDismissCallback);
+                mOnDismissCallback, flags -> 0);
     }
 }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsLayoutTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsLayoutTest.java
index 1fee153..ce1290b 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsLayoutTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsLayoutTest.java
@@ -93,7 +93,7 @@
         mWindowManager = new UserAspectRatioSettingsWindowManager(mContext, mTaskInfo,
                 mSyncTransactionQueue, mTaskListener, new DisplayLayout(),
                 new CompatUIController.CompatUIHintsState(),
-                mOnUserAspectRatioSettingsButtonClicked, new TestShellExecutor());
+                mOnUserAspectRatioSettingsButtonClicked, new TestShellExecutor(), flags -> 0);
 
         mLayout = (UserAspectRatioSettingsLayout) LayoutInflater.from(mContext).inflate(
                 R.layout.user_aspect_ratio_settings_layout, null);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManagerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManagerTest.java
index b48538c..08cc2f7 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManagerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManagerTest.java
@@ -106,7 +106,7 @@
                 false, /* topActivityBoundsLetterboxed */ true);
         mWindowManager = new UserAspectRatioSettingsWindowManager(mContext, mTaskInfo,
                 mSyncTransactionQueue, mTaskListener, new DisplayLayout(), new CompatUIHintsState(),
-                mOnUserAspectRatioSettingsButtonClicked, mExecutor);
+                mOnUserAspectRatioSettingsButtonClicked, mExecutor, flags -> 0);
         spyOn(mWindowManager);
         doReturn(mLayout).when(mWindowManager).inflateLayout();
         doReturn(mViewHost).when(mWindowManager).createSurfaceViewHost();
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/unfold/UnfoldTransitionHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/unfold/UnfoldTransitionHandlerTest.java
new file mode 100644
index 0000000..63a685e
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/unfold/UnfoldTransitionHandlerTest.java
@@ -0,0 +1,280 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.unfold;
+
+import static android.view.WindowManager.TRANSIT_CHANGE;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import android.app.ActivityManager;
+import android.os.Binder;
+import android.os.IBinder;
+import android.view.Display;
+import android.view.SurfaceControl;
+import android.window.TransitionInfo;
+import android.window.TransitionRequestInfo;
+import android.window.WindowContainerTransaction;
+
+import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.TransactionPool;
+import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.transition.Transitions;
+import com.android.wm.shell.transition.Transitions.TransitionFinishCallback;
+import com.android.wm.shell.unfold.animation.FullscreenUnfoldTaskAnimator;
+import com.android.wm.shell.unfold.animation.SplitTaskUnfoldAnimator;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.Executor;
+
+public class UnfoldTransitionHandlerTest {
+
+    private UnfoldTransitionHandler mUnfoldTransitionHandler;
+
+    private final TestShellUnfoldProgressProvider mShellUnfoldProgressProvider =
+            new TestShellUnfoldProgressProvider();
+    private final TestTransactionPool mTransactionPool = new TestTransactionPool();
+
+    private FullscreenUnfoldTaskAnimator mFullscreenUnfoldTaskAnimator;
+    private SplitTaskUnfoldAnimator mSplitTaskUnfoldAnimator;
+    private Transitions mTransitions;
+
+    private final IBinder mTransition = new Binder();
+
+    @Before
+    public void before() {
+        final ShellExecutor executor = new TestSyncExecutor();
+        final ShellInit shellInit = new ShellInit(executor);
+
+        mFullscreenUnfoldTaskAnimator = mock(FullscreenUnfoldTaskAnimator.class);
+        mSplitTaskUnfoldAnimator = mock(SplitTaskUnfoldAnimator.class);
+        mTransitions = mock(Transitions.class);
+
+        mUnfoldTransitionHandler = new UnfoldTransitionHandler(
+                shellInit,
+                mShellUnfoldProgressProvider,
+                mFullscreenUnfoldTaskAnimator,
+                mSplitTaskUnfoldAnimator,
+                mTransactionPool,
+                executor,
+                mTransitions
+        );
+
+        shellInit.init();
+    }
+
+    @Test
+    public void handleRequest_physicalDisplayChange_handlesTransition() {
+        ActivityManager.RunningTaskInfo triggerTaskInfo = new ActivityManager.RunningTaskInfo();
+        TransitionRequestInfo.DisplayChange displayChange = new TransitionRequestInfo.DisplayChange(
+                Display.DEFAULT_DISPLAY).setPhysicalDisplayChanged(true);
+        TransitionRequestInfo requestInfo = new TransitionRequestInfo(TRANSIT_CHANGE,
+                triggerTaskInfo, /* remoteTransition= */ null, displayChange);
+
+        WindowContainerTransaction result = mUnfoldTransitionHandler.handleRequest(mTransition,
+                requestInfo);
+
+        assertThat(result).isNotNull();
+    }
+
+    @Test
+    public void handleRequest_noPhysicalDisplayChange_doesNotHandleTransition() {
+        ActivityManager.RunningTaskInfo triggerTaskInfo = new ActivityManager.RunningTaskInfo();
+        TransitionRequestInfo.DisplayChange displayChange = new TransitionRequestInfo.DisplayChange(
+                Display.DEFAULT_DISPLAY).setPhysicalDisplayChanged(false);
+        TransitionRequestInfo requestInfo = new TransitionRequestInfo(TRANSIT_CHANGE,
+                triggerTaskInfo, /* remoteTransition= */ null, displayChange);
+
+        WindowContainerTransaction result = mUnfoldTransitionHandler.handleRequest(mTransition,
+                requestInfo);
+
+        assertThat(result).isNull();
+    }
+
+    @Test
+    public void startAnimation_animationHasNotFinishedYet_doesNotFinishTheTransition() {
+        TransitionRequestInfo requestInfo = createUnfoldTransitionRequestInfo();
+        mUnfoldTransitionHandler.handleRequest(mTransition, requestInfo);
+        TransitionFinishCallback finishCallback = mock(TransitionFinishCallback.class);
+
+        mUnfoldTransitionHandler.startAnimation(
+                mTransition,
+                mock(TransitionInfo.class),
+                mock(SurfaceControl.Transaction.class),
+                mock(SurfaceControl.Transaction.class),
+                finishCallback
+        );
+
+        verify(finishCallback, never()).onTransitionFinished(any());
+    }
+
+    @Test
+    public void startAnimation_animationFinishes_finishesTheTransition() {
+        TransitionRequestInfo requestInfo = createUnfoldTransitionRequestInfo();
+        mUnfoldTransitionHandler.handleRequest(mTransition, requestInfo);
+        TransitionFinishCallback finishCallback = mock(TransitionFinishCallback.class);
+
+        mUnfoldTransitionHandler.startAnimation(
+                mTransition,
+                mock(TransitionInfo.class),
+                mock(SurfaceControl.Transaction.class),
+                mock(SurfaceControl.Transaction.class),
+                finishCallback
+        );
+        mShellUnfoldProgressProvider.onStateChangeStarted();
+        mShellUnfoldProgressProvider.onStateChangeFinished();
+
+        verify(finishCallback).onTransitionFinished(any());
+    }
+
+    @Test
+    public void startAnimation_animationIsAlreadyFinished_finishesTheTransition() {
+        TransitionRequestInfo requestInfo = createUnfoldTransitionRequestInfo();
+        mUnfoldTransitionHandler.handleRequest(mTransition, requestInfo);
+        TransitionFinishCallback finishCallback = mock(TransitionFinishCallback.class);
+
+        mShellUnfoldProgressProvider.onStateChangeStarted();
+        mShellUnfoldProgressProvider.onStateChangeFinished();
+        mUnfoldTransitionHandler.startAnimation(
+                mTransition,
+                mock(TransitionInfo.class),
+                mock(SurfaceControl.Transaction.class),
+                mock(SurfaceControl.Transaction.class),
+                finishCallback
+        );
+
+        verify(finishCallback).onTransitionFinished(any());
+    }
+
+    @Test
+    public void startAnimationSecondTimeAfterFold_animationAlreadyFinished_finishesTransition() {
+        TransitionRequestInfo requestInfo = createUnfoldTransitionRequestInfo();
+        TransitionFinishCallback finishCallback = mock(TransitionFinishCallback.class);
+
+        // First unfold
+        mShellUnfoldProgressProvider.onFoldStateChanged(/* isFolded= */ false);
+        mShellUnfoldProgressProvider.onStateChangeStarted();
+        mShellUnfoldProgressProvider.onStateChangeFinished();
+        mUnfoldTransitionHandler.handleRequest(mTransition, requestInfo);
+        mUnfoldTransitionHandler.startAnimation(
+                mTransition,
+                mock(TransitionInfo.class),
+                mock(SurfaceControl.Transaction.class),
+                mock(SurfaceControl.Transaction.class),
+                finishCallback
+        );
+        clearInvocations(finishCallback);
+
+        // Fold
+        mShellUnfoldProgressProvider.onFoldStateChanged(/* isFolded= */ true);
+
+        // Second unfold
+        mShellUnfoldProgressProvider.onFoldStateChanged(/* isFolded= */ false);
+        mShellUnfoldProgressProvider.onStateChangeStarted();
+        mShellUnfoldProgressProvider.onStateChangeFinished();
+        mUnfoldTransitionHandler.handleRequest(mTransition, requestInfo);
+        mUnfoldTransitionHandler.startAnimation(
+                mTransition,
+                mock(TransitionInfo.class),
+                mock(SurfaceControl.Transaction.class),
+                mock(SurfaceControl.Transaction.class),
+                finishCallback
+        );
+
+        verify(finishCallback).onTransitionFinished(any());
+    }
+
+    private TransitionRequestInfo createUnfoldTransitionRequestInfo() {
+        ActivityManager.RunningTaskInfo triggerTaskInfo = new ActivityManager.RunningTaskInfo();
+        TransitionRequestInfo.DisplayChange displayChange = new TransitionRequestInfo.DisplayChange(
+                Display.DEFAULT_DISPLAY).setPhysicalDisplayChanged(true);
+        return new TransitionRequestInfo(TRANSIT_CHANGE,
+                triggerTaskInfo, /* remoteTransition= */ null, displayChange);
+    }
+
+    private static class TestShellUnfoldProgressProvider implements ShellUnfoldProgressProvider,
+            ShellUnfoldProgressProvider.UnfoldListener {
+
+        private final List<UnfoldListener> mListeners = new ArrayList<>();
+
+        @Override
+        public void addListener(Executor executor, UnfoldListener listener) {
+            mListeners.add(listener);
+        }
+
+        @Override
+        public void onFoldStateChanged(boolean isFolded) {
+            mListeners.forEach(unfoldListener -> unfoldListener.onFoldStateChanged(isFolded));
+        }
+
+        @Override
+        public void onStateChangeFinished() {
+            mListeners.forEach(UnfoldListener::onStateChangeFinished);
+        }
+
+        @Override
+        public void onStateChangeProgress(float progress) {
+            mListeners.forEach(unfoldListener -> unfoldListener.onStateChangeProgress(progress));
+        }
+
+        @Override
+        public void onStateChangeStarted() {
+            mListeners.forEach(UnfoldListener::onStateChangeStarted);
+        }
+    }
+
+    private static class TestTransactionPool extends TransactionPool {
+        @Override
+        public SurfaceControl.Transaction acquire() {
+            return mock(SurfaceControl.Transaction.class);
+        }
+
+        @Override
+        public void release(SurfaceControl.Transaction t) {
+        }
+    }
+
+    private static class TestSyncExecutor implements ShellExecutor {
+        @Override
+        public void execute(Runnable runnable) {
+            runnable.run();
+        }
+
+        @Override
+        public void executeDelayed(Runnable runnable, long delayMillis) {
+            runnable.run();
+        }
+
+        @Override
+        public void removeCallbacks(Runnable runnable) {
+        }
+
+        @Override
+        public boolean hasCallback(Runnable runnable) {
+            return false;
+        }
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
index 0fa4ebd..dc93400 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
@@ -51,15 +51,18 @@
 private val KEY_TIMESTAMP = "appliedTimestamp"
 private val KNOWN_PLUGINS =
     mapOf<String, List<ClockMetadata>>(
-        "com.android.systemui.falcon.one" to listOf(ClockMetadata("ANALOG_CLOCK_BIGNUM")),
-        "com.android.systemui.falcon.two" to listOf(ClockMetadata("DIGITAL_CLOCK_CALLIGRAPHY")),
-        "com.android.systemui.falcon.three" to listOf(ClockMetadata("DIGITAL_CLOCK_FLEX")),
-        "com.android.systemui.falcon.four" to listOf(ClockMetadata("DIGITAL_CLOCK_GROWTH")),
-        "com.android.systemui.falcon.five" to listOf(ClockMetadata("DIGITAL_CLOCK_HANDWRITTEN")),
-        "com.android.systemui.falcon.six" to listOf(ClockMetadata("DIGITAL_CLOCK_INFLATE")),
-        "com.android.systemui.falcon.seven" to listOf(ClockMetadata("DIGITAL_CLOCK_METRO")),
-        "com.android.systemui.falcon.eight" to listOf(ClockMetadata("DIGITAL_CLOCK_NUMBEROVERLAP")),
-        "com.android.systemui.falcon.nine" to listOf(ClockMetadata("DIGITAL_CLOCK_WEATHER")),
+        "com.android.systemui.clocks.bignum" to listOf(ClockMetadata("ANALOG_CLOCK_BIGNUM")),
+        "com.android.systemui.clocks.calligraphy" to
+            listOf(ClockMetadata("DIGITAL_CLOCK_CALLIGRAPHY")),
+        "com.android.systemui.clocks.flex" to listOf(ClockMetadata("DIGITAL_CLOCK_FLEX")),
+        "com.android.systemui.clocks.growth" to listOf(ClockMetadata("DIGITAL_CLOCK_GROWTH")),
+        "com.android.systemui.clocks.handwritten" to
+            listOf(ClockMetadata("DIGITAL_CLOCK_HANDWRITTEN")),
+        "com.android.systemui.clocks.inflate" to listOf(ClockMetadata("DIGITAL_CLOCK_INFLATE")),
+        "com.android.systemui.clocks.metro" to listOf(ClockMetadata("DIGITAL_CLOCK_METRO")),
+        "com.android.systemui.clocks.numoverlap" to
+            listOf(ClockMetadata("DIGITAL_CLOCK_NUMBEROVERLAP")),
+        "com.android.systemui.clocks.weather" to listOf(ClockMetadata("DIGITAL_CLOCK_WEATHER")),
     )
 
 private fun <TKey, TVal> ConcurrentHashMap<TKey, TVal>.concurrentGetOrPut(
diff --git a/packages/SystemUI/res/layout/screen_share_dialog_spinner_item_text.xml b/packages/SystemUI/res/layout/screen_share_dialog_spinner_item_text.xml
index 66c2155..1e5b249 100644
--- a/packages/SystemUI/res/layout/screen_share_dialog_spinner_item_text.xml
+++ b/packages/SystemUI/res/layout/screen_share_dialog_spinner_item_text.xml
@@ -13,15 +13,32 @@
   See the License for the specific language governing permissions and
   limitations under the License.
   -->
-<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
-    android:id="@android:id/text1"
-    android:textAppearance="?android:attr/textAppearanceMedium"
-    android:textColor="?androidprv:attr/textColorOnAccent"
-    android:singleLine="true"
     android:layout_width="match_parent"
-    android:layout_height="@dimen/screenrecord_spinner_height"
-    android:gravity="center_vertical"
-    android:ellipsize="marquee"
+    android:layout_height="wrap_content"
+    android:minHeight="@dimen/screenrecord_spinner_height"
+    android:paddingEnd="@dimen/screenrecord_spinner_text_padding_end"
     android:paddingStart="@dimen/screenrecord_spinner_text_padding_start"
-    android:paddingEnd="@dimen/screenrecord_spinner_text_padding_end"/>
\ No newline at end of file
+    android:gravity="center_vertical"
+    android:orientation="vertical">
+
+    <TextView
+        android:id="@android:id/text1"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:ellipsize="marquee"
+        android:singleLine="true"
+        android:textAppearance="?android:attr/textAppearanceMedium"
+        android:textColor="?androidprv:attr/textColorOnAccent" />
+
+    <TextView
+        android:id="@android:id/text2"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:ellipsize="marquee"
+        android:singleLine="true"
+        android:textAppearance="?android:attr/textAppearanceSmall"
+        android:textColor="?androidprv:attr/colorError" />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml
index d2cb475..9957429 100644
--- a/packages/SystemUI/res/values/ids.xml
+++ b/packages/SystemUI/res/values/ids.xml
@@ -219,4 +219,10 @@
     <!-- Privacy dialog -->
     <item type="id" name="privacy_dialog_close_app_button" />
     <item type="id" name="privacy_dialog_manage_app_button" />
+
+    <!--
+    Used to tag views programmatically added to the smartspace area so they can be more easily
+    removed later.
+    -->
+    <item type="id" name="tag_smartspace_view" />
 </resources>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index cddfda2..275e5d8 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1102,6 +1102,8 @@
     <string name="media_projection_entry_app_permission_dialog_warning_single_app">When you’re sharing, recording, or casting an app, <xliff:g id="app_seeking_permission" example="Meet">%s</xliff:g> has access to anything shown or played on that app. So be careful with things like passwords, payment details, messages, photos, and audio and video.</string>
     <!-- 1P/3P apps media projection permission button to continue with app selection or recording [CHAR LIMIT=60] -->
     <string name="media_projection_entry_app_permission_dialog_continue">Start</string>
+    <!-- 1P/3P apps disabled the single app projection option. [CHAR LIMIT=NONE] -->
+    <string name="media_projection_entry_app_permission_dialog_single_app_disabled"><xliff:g id="app_name" example="Meet">%1$s</xliff:g> has disabled this option</string>
 
     <!-- Casting that launched by SysUI (i.e. when there is no app name) -->
     <!-- System casting media projection permission dialog title. [CHAR LIMIT=100] -->
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java
index a444022..8200e5c 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java
@@ -39,6 +39,7 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.os.RemoteException;
+import android.os.SystemProperties;
 import android.provider.Settings;
 import android.util.Log;
 import android.view.HapticFeedbackConstants;
@@ -76,6 +77,8 @@
     private static final String TAG = "RotationButtonController";
     private static final int BUTTON_FADE_IN_OUT_DURATION_MS = 100;
     private static final int NAVBAR_HIDDEN_PENDING_ICON_TIMEOUT_MS = 20000;
+    private static final boolean OEM_DISALLOW_ROTATION_IN_SUW =
+            SystemProperties.getBoolean("ro.setupwizard.rotation_locked", false);
     private static final Interpolator LINEAR_INTERPOLATOR = new LinearInterpolator();
 
     private static final int NUM_ACCEPTED_ROTATION_SUGGESTIONS_FOR_INTRODUCTION = 3;
@@ -375,6 +378,12 @@
     }
 
     public void onRotationProposal(int rotation, boolean isValid) {
+        boolean isUserSetupComplete = Settings.Secure.getInt(mContext.getContentResolver(),
+                Settings.Secure.USER_SETUP_COMPLETE, 0) != 0;
+        if (!isUserSetupComplete && OEM_DISALLOW_ROTATION_IN_SUW) {
+            return;
+        }
+
         int windowRotation = mWindowRotationProvider.get();
 
         if (!mRotationButton.acceptRotationProposal()) {
diff --git a/packages/SystemUI/src/com/android/keyguard/ConnectedDisplayKeyguardPresentation.kt b/packages/SystemUI/src/com/android/keyguard/ConnectedDisplayKeyguardPresentation.kt
index 899cad89..006974c 100644
--- a/packages/SystemUI/src/com/android/keyguard/ConnectedDisplayKeyguardPresentation.kt
+++ b/packages/SystemUI/src/com/android/keyguard/ConnectedDisplayKeyguardPresentation.kt
@@ -66,7 +66,7 @@
         window.isNavigationBarContrastEnforced = false
         window.navigationBarColor = Color.TRANSPARENT
 
-        clock = findViewById(R.id.clock)
+        clock = requireViewById(R.id.clock)
         keyguardStatusViewController =
             keyguardStatusViewComponentFactory.build(clock).keyguardStatusViewController.apply {
                 setDisplayedOnSecondaryDisplay()
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
index 4515a66..692b636 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
@@ -292,7 +292,7 @@
             int viewIndex = mStatusArea.indexOfChild(ksv);
             ksv.setVisibility(View.GONE);
 
-            mSmartspaceController.removeViewsFromParent(mStatusArea);
+            removeViewsFromStatusArea();
             addSmartspaceView();
             // TODO(b/261757708): add content observer for the Settings toggle and add/remove
             //  weather according to the Settings.
@@ -324,7 +324,7 @@
 
     void onLocaleListChanged() {
         if (mSmartspaceController.isEnabled()) {
-            mSmartspaceController.removeViewsFromParent(mStatusArea);
+            removeViewsFromStatusArea();
             addSmartspaceView();
             if (mSmartspaceController.isDateWeatherDecoupled()) {
                 mDateWeatherView.removeView(mWeatherView);
@@ -618,4 +618,13 @@
         return ((mCurrentClockSize == LARGE) ? clock.getLargeClock() : clock.getSmallClock())
                 .getConfig().getHasCustomWeatherDataDisplay();
     }
+
+    private void removeViewsFromStatusArea() {
+        for  (int i = mStatusArea.getChildCount() - 1; i >= 0; i--) {
+            final View childView = mStatusArea.getChildAt(i);
+            if (childView.getTag(R.id.tag_smartspace_view) != null) {
+                mStatusArea.removeViewAt(i);
+            }
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/dagger/UdfpsModule.kt b/packages/SystemUI/src/com/android/systemui/biometrics/dagger/UdfpsModule.kt
index 20c3e40..f7f9103 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/dagger/UdfpsModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/dagger/UdfpsModule.kt
@@ -56,10 +56,10 @@
                         )
                     )
                 } else {
-                    BoundingBoxOverlapDetector()
+                    BoundingBoxOverlapDetector(values[2])
                 }
             } else {
-                return BoundingBoxOverlapDetector()
+                return BoundingBoxOverlapDetector(1f)
             }
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/udfps/BoundingBoxOverlapDetector.kt b/packages/SystemUI/src/com/android/systemui/biometrics/udfps/BoundingBoxOverlapDetector.kt
index cf6044f..9b946db 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/udfps/BoundingBoxOverlapDetector.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/udfps/BoundingBoxOverlapDetector.kt
@@ -17,16 +17,30 @@
 package com.android.systemui.biometrics.udfps
 
 import android.graphics.Rect
+import android.os.Build
+import android.util.Log
 import com.android.systemui.dagger.SysUISingleton
 
 /** Returns whether the touch coordinates are within the sensor's bounding box. */
 @SysUISingleton
-class BoundingBoxOverlapDetector : OverlapDetector {
+class BoundingBoxOverlapDetector(private val targetSize: Float) : OverlapDetector {
+
+    private val TAG = "BoundingBoxOverlapDetector"
+
     override fun isGoodOverlap(
         touchData: NormalizedTouchData,
         nativeSensorBounds: Rect,
         nativeOverlayBounds: Rect,
-    ): Boolean =
-        touchData.isWithinBounds(nativeOverlayBounds) &&
-            touchData.isWithinBounds(nativeSensorBounds)
+    ): Boolean {
+        val scaledRadius = (nativeSensorBounds.width() / 2) * targetSize
+        val scaledSensorBounds =
+            Rect(
+                (nativeSensorBounds.centerX() - scaledRadius).toInt(),
+                (nativeSensorBounds.centerY() - scaledRadius).toInt(),
+                (nativeSensorBounds.centerX() + scaledRadius).toInt(),
+                (nativeSensorBounds.centerY() + scaledRadius).toInt(),
+            )
+
+        return touchData.isWithinBounds(scaledSensorBounds)
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
index 4dc7720..e8b8f54 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
@@ -289,7 +289,7 @@
             if (authenticateAfterError) {
                 showAuthenticating(messageAfterError)
             } else {
-                showHelp(messageAfterError)
+                showInfo(messageAfterError)
             }
         }
     }
@@ -309,12 +309,15 @@
     private fun supportsRetry(failedModality: BiometricModality) =
         failedModality == BiometricModality.Face
 
+    suspend fun showHelp(message: String) = showHelp(message, clearIconError = false)
+    suspend fun showInfo(message: String) = showHelp(message, clearIconError = true)
+
     /**
      * Show a persistent help message.
      *
      * Will be show even if the user has already authenticated.
      */
-    suspend fun showHelp(message: String) {
+    private suspend fun showHelp(message: String, clearIconError: Boolean) {
         val alreadyAuthenticated = _isAuthenticated.value.isAuthenticated
         if (!alreadyAuthenticated) {
             _isAuthenticating.value = false
@@ -329,6 +332,8 @@
                 AuthBiometricView.STATE_PENDING_CONFIRMATION
             } else if (alreadyAuthenticated && !isConfirmationRequired.first()) {
                 AuthBiometricView.STATE_AUTHENTICATED
+            } else if (clearIconError) {
+                AuthBiometricView.STATE_IDLE
             } else {
                 AuthBiometricView.STATE_HELP
             }
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepository.kt b/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepository.kt
index 918e168..f2b4e09 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepository.kt
@@ -58,7 +58,7 @@
      * ```
      */
     val panelExpansionAmount: StateFlow<Float>
-    val keyguardPosition: StateFlow<Float>
+    val keyguardPosition: StateFlow<Float?>
     val isBackButtonEnabled: StateFlow<Boolean?>
     /** Determines if user is already unlocked */
     val keyguardAuthenticated: StateFlow<Boolean?>
@@ -130,7 +130,7 @@
      */
     private val _panelExpansionAmount = MutableStateFlow(EXPANSION_HIDDEN)
     override val panelExpansionAmount = _panelExpansionAmount.asStateFlow()
-    private val _keyguardPosition = MutableStateFlow(0f)
+    private val _keyguardPosition = MutableStateFlow<Float?>(null)
     override val keyguardPosition = _keyguardPosition.asStateFlow()
     private val _isBackButtonEnabled = MutableStateFlow<Boolean?>(null)
     override val isBackButtonEnabled = _isBackButtonEnabled.asStateFlow()
@@ -244,6 +244,7 @@
             .logDiffsForTable(buffer, "", "PanelExpansionAmountMillis", -1)
             .launchIn(applicationScope)
         keyguardPosition
+            .filterNotNull()
             .map { it.toInt() }
             .logDiffsForTable(buffer, "", "KeyguardPosition", -1)
             .launchIn(applicationScope)
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt
index c486603..0e0f1f6 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt
@@ -94,7 +94,7 @@
     val startingDisappearAnimation: Flow<Runnable> =
         repository.primaryBouncerStartingDisappearAnimation.filterNotNull()
     val resourceUpdateRequests: Flow<Boolean> = repository.resourceUpdateRequests.filter { it }
-    val keyguardPosition: Flow<Float> = repository.keyguardPosition
+    val keyguardPosition: Flow<Float> = repository.keyguardPosition.filterNotNull()
     val panelExpansionAmount: Flow<Float> = repository.panelExpansionAmount
     /** 0f = bouncer fully hidden. 1f = bouncer fully visible. */
     val bouncerExpansion: Flow<Float> =
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 6d68eef..8b23b5a 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -285,11 +285,7 @@
     /** Enables preview loading animation in the wallpaper picker. */
     // TODO(b/274443705): Tracking Bug
     @JvmField
-    val WALLPAPER_PICKER_PREVIEW_ANIMATION =
-            unreleasedFlag(
-                    "wallpaper_picker_preview_animation",
-                teamfood = true
-            )
+    val WALLPAPER_PICKER_PREVIEW_ANIMATION = releasedFlag("wallpaper_picker_preview_animation")
 
     /** Stop running face auth when the display state changes to OFF. */
     // TODO(b/294221702): Tracking bug.
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractor.kt
index e501ece..635961b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractor.kt
@@ -54,11 +54,7 @@
         if (event.handleAction()) {
             when (event.keyCode) {
                 KeyEvent.KEYCODE_MENU -> return dispatchMenuKeyEvent()
-                KeyEvent.KEYCODE_SPACE,
-                KeyEvent.KEYCODE_ENTER ->
-                    if (isDeviceInteractive()) {
-                        return collapseShadeLockedOrShowPrimaryBouncer()
-                    }
+                KeyEvent.KEYCODE_SPACE -> return dispatchSpaceEvent()
             }
         }
         return false
@@ -94,22 +90,16 @@
                 (statusBarStateController.state != StatusBarState.SHADE) &&
                 statusBarKeyguardViewManager.shouldDismissOnMenuPressed()
         if (shouldUnlockOnMenuPressed) {
-            return collapseShadeLockedOrShowPrimaryBouncer()
+            shadeController.animateCollapseShadeForced()
+            return true
         }
         return false
     }
 
-    private fun collapseShadeLockedOrShowPrimaryBouncer(): Boolean {
-        when (statusBarStateController.state) {
-            StatusBarState.SHADE -> return false
-            StatusBarState.SHADE_LOCKED -> {
-                shadeController.animateCollapseShadeForced()
-                return true
-            }
-            StatusBarState.KEYGUARD -> {
-                statusBarKeyguardViewManager.showPrimaryBouncer(true)
-                return true
-            }
+    private fun dispatchSpaceEvent(): Boolean {
+        if (isDeviceInteractive() && statusBarStateController.state != StatusBarState.SHADE) {
+            shadeController.animateCollapseShadeForced()
+            return true
         }
         return false
     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSurfaceBehindParamsApplier.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSurfaceBehindParamsApplier.kt
index fe2df21..692aa85 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSurfaceBehindParamsApplier.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSurfaceBehindParamsApplier.kt
@@ -87,7 +87,7 @@
             }
             addListener(
                 object : AnimatorListenerAdapter() {
-                    override fun onAnimationEnd(animation: Animator?) {
+                    override fun onAnimationEnd(animation: Animator) {
                         updateIsAnimatingSurface()
                     }
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java
index cbd9a34..a9d2b30 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java
@@ -36,6 +36,7 @@
 import android.content.pm.PackageManager;
 import android.graphics.Typeface;
 import android.media.projection.IMediaProjection;
+import android.media.projection.MediaProjectionConfig;
 import android.media.projection.MediaProjectionManager;
 import android.media.projection.ReviewGrantedConsentResult;
 import android.os.Bundle;
@@ -212,11 +213,13 @@
         // the correct screen width when in split screen.
         Context dialogContext = getApplicationContext();
         if (isPartialScreenSharingEnabled()) {
-            mDialog = new MediaProjectionPermissionDialog(dialogContext, () -> {
-                ScreenShareOption selectedOption =
-                        ((MediaProjectionPermissionDialog) mDialog).getSelectedScreenShareOption();
-                grantMediaProjectionPermission(selectedOption.getMode());
-            }, () -> finish(RECORD_CANCEL, /* projection= */ null), appName);
+            mDialog = new MediaProjectionPermissionDialog(dialogContext, getMediaProjectionConfig(),
+                    () -> {
+                        MediaProjectionPermissionDialog dialog =
+                                (MediaProjectionPermissionDialog) mDialog;
+                        ScreenShareOption selectedOption = dialog.getSelectedScreenShareOption();
+                        grantMediaProjectionPermission(selectedOption.getMode());
+                    }, () -> finish(RECORD_CANCEL, /* projection= */ null), appName);
         } else {
             AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(dialogContext,
                     R.style.Theme_SystemUI_Dialog)
@@ -360,6 +363,16 @@
         }
     }
 
+    @Nullable
+    private MediaProjectionConfig getMediaProjectionConfig() {
+        Intent intent = getIntent();
+        if (intent == null) {
+            return null;
+        }
+        return intent.getParcelableExtra(
+                MediaProjectionManager.EXTRA_MEDIA_PROJECTION_CONFIG);
+    }
+
     private boolean isPartialScreenSharingEnabled() {
         return mFeatureFlags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/BaseScreenSharePermissionDialog.kt b/packages/SystemUI/src/com/android/systemui/screenrecord/BaseScreenSharePermissionDialog.kt
index 23894a3..7859fa0 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/BaseScreenSharePermissionDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/BaseScreenSharePermissionDialog.kt
@@ -18,7 +18,9 @@
 import android.content.Context
 import android.os.Bundle
 import android.view.Gravity
+import android.view.LayoutInflater
 import android.view.View
+import android.view.ViewGroup
 import android.view.ViewStub
 import android.view.WindowManager
 import android.widget.AdapterView
@@ -35,7 +37,7 @@
 
 /** Base permission dialog for screen share and recording */
 open class BaseScreenSharePermissionDialog(
-    context: Context?,
+    context: Context,
     private val screenShareOptions: List<ScreenShareOption>,
     private val appName: String?,
     @DrawableRes private val dialogIconDrawable: Int? = null,
@@ -82,14 +84,7 @@
         get() = context.getString(selectedScreenShareOption.warningText, appName)
 
     private fun initScreenShareSpinner() {
-        val options = screenShareOptions.map { context.getString(it.spinnerText) }.toTypedArray()
-        val adapter =
-            ArrayAdapter(
-                context.applicationContext,
-                R.layout.screen_share_dialog_spinner_text,
-                options
-            )
-        adapter.setDropDownViewResource(R.layout.screen_share_dialog_spinner_item_text)
+        val adapter = OptionsAdapter(context.applicationContext, screenShareOptions)
         screenShareModeSpinner = requireViewById(R.id.screen_share_mode_spinner)
         screenShareModeSpinner.adapter = adapter
         screenShareModeSpinner.onItemSelectedListener = this
@@ -131,3 +126,35 @@
         stub.inflate()
     }
 }
+
+private class OptionsAdapter(
+    context: Context,
+    private val options: List<ScreenShareOption>,
+) :
+    ArrayAdapter<String>(
+        context,
+        R.layout.screen_share_dialog_spinner_text,
+        options.map { context.getString(it.spinnerText) }
+    ) {
+
+    override fun isEnabled(position: Int): Boolean {
+        return options[position].spinnerDisabledText == null
+    }
+
+    override fun getDropDownView(position: Int, convertView: View?, parent: ViewGroup): View {
+        val inflater = LayoutInflater.from(parent.context)
+        val view = inflater.inflate(R.layout.screen_share_dialog_spinner_item_text, parent, false)
+        val titleTextView = view.findViewById<TextView>(android.R.id.text1)
+        val errorTextView = view.findViewById<TextView>(android.R.id.text2)
+        titleTextView.text = getItem(position)
+        errorTextView.text = options[position].spinnerDisabledText
+        if (isEnabled(position)) {
+            errorTextView.visibility = View.GONE
+            titleTextView.isEnabled = true
+        } else {
+            errorTextView.visibility = View.VISIBLE
+            titleTextView.isEnabled = false
+        }
+        return view
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/MediaProjectionPermissionDialog.kt b/packages/SystemUI/src/com/android/systemui/screenrecord/MediaProjectionPermissionDialog.kt
index f4f5f66..8cbc4aab 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/MediaProjectionPermissionDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/MediaProjectionPermissionDialog.kt
@@ -16,16 +16,23 @@
 package com.android.systemui.screenrecord
 
 import android.content.Context
+import android.media.projection.MediaProjectionConfig
 import android.os.Bundle
 import com.android.systemui.R
 
 /** Dialog to select screen recording options */
 class MediaProjectionPermissionDialog(
-    context: Context?,
+    context: Context,
+    mediaProjectionConfig: MediaProjectionConfig?,
     private val onStartRecordingClicked: Runnable,
     private val onCancelClicked: Runnable,
     private val appName: String?
-) : BaseScreenSharePermissionDialog(context, createOptionList(appName), appName) {
+) :
+    BaseScreenSharePermissionDialog(
+        context,
+        createOptionList(context, appName, mediaProjectionConfig),
+        appName
+    ) {
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
         // TODO(b/270018943): Handle the case of System sharing (not recording nor casting)
@@ -49,7 +56,11 @@
     }
 
     companion object {
-        private fun createOptionList(appName: String?): List<ScreenShareOption> {
+        private fun createOptionList(
+            context: Context,
+            appName: String?,
+            mediaProjectionConfig: MediaProjectionConfig?
+        ): List<ScreenShareOption> {
             val singleAppWarningText =
                 if (appName == null) {
                     R.string.media_projection_entry_cast_permission_dialog_warning_single_app
@@ -63,6 +74,19 @@
                     R.string.media_projection_entry_app_permission_dialog_warning_entire_screen
                 }
 
+            val singleAppDisabledText =
+                if (
+                    appName != null &&
+                        mediaProjectionConfig?.regionToCapture ==
+                            MediaProjectionConfig.CAPTURE_REGION_FIXED_DISPLAY
+                ) {
+                    context.getString(
+                        R.string.media_projection_entry_app_permission_dialog_single_app_disabled,
+                        appName
+                    )
+                } else {
+                    null
+                }
             return listOf(
                 ScreenShareOption(
                     mode = ENTIRE_SCREEN,
@@ -72,7 +96,8 @@
                 ScreenShareOption(
                     mode = SINGLE_APP,
                     spinnerText = R.string.screen_share_permission_dialog_option_single_app,
-                    warningText = singleAppWarningText
+                    warningText = singleAppWarningText,
+                    spinnerDisabledText = singleAppDisabledText,
                 )
             )
         }
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt
index fb99775..9c5da10 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt
@@ -41,7 +41,7 @@
 
 /** Dialog to select screen recording options */
 class ScreenRecordPermissionDialog(
-    context: Context?,
+    context: Context,
     private val hostUserHandle: UserHandle,
     private val controller: RecordingController,
     private val activityStarter: ActivityStarter,
@@ -52,7 +52,7 @@
     BaseScreenSharePermissionDialog(
         context,
         createOptionList(),
-        null,
+        appName = null,
         R.drawable.ic_screenrecord,
         R.color.screenrecord_icon_color
     ) {
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenShareOption.kt b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenShareOption.kt
index 3d39fd8..ebf0dd2 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenShareOption.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenShareOption.kt
@@ -29,5 +29,6 @@
 class ScreenShareOption(
     @ScreenShareMode val mode: Int,
     @StringRes val spinnerText: Int,
-    @StringRes val warningText: Int
+    @StringRes val warningText: Int,
+    val spinnerDisabledText: String? = null,
 )
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java
index 6c8190a..d0d37c4 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java
@@ -25,6 +25,7 @@
 
 import androidx.annotation.Nullable;
 
+import com.android.internal.logging.UiEventLogger;
 import com.android.settingslib.RestrictedLockUtils;
 import com.android.systemui.Gefingerpoken;
 import com.android.systemui.R;
@@ -52,6 +53,7 @@
     private BrightnessMirrorController mMirrorController;
     private boolean mTracking;
     private final FalsingManager mFalsingManager;
+    private final UiEventLogger mUiEventLogger;
 
     private final Gefingerpoken mOnInterceptListener = new Gefingerpoken() {
         @Override
@@ -72,9 +74,11 @@
 
     BrightnessSliderController(
             BrightnessSliderView brightnessSliderView,
-            FalsingManager falsingManager) {
+            FalsingManager falsingManager,
+            UiEventLogger uiEventLogger) {
         super(brightnessSliderView);
         mFalsingManager = falsingManager;
+        mUiEventLogger = uiEventLogger;
     }
 
     /**
@@ -206,7 +210,7 @@
         @Override
         public void onStartTrackingTouch(SeekBar seekBar) {
             mTracking = true;
-
+            mUiEventLogger.log(BrightnessSliderEvent.SLIDER_STARTED_TRACKING_TOUCH);
             if (mListener != null) {
                 mListener.onChanged(mTracking, getValue(), false);
             }
@@ -220,7 +224,7 @@
         @Override
         public void onStopTrackingTouch(SeekBar seekBar) {
             mTracking = false;
-
+            mUiEventLogger.log(BrightnessSliderEvent.SLIDER_STOPPED_TRACKING_TOUCH);
             if (mListener != null) {
                 mListener.onChanged(mTracking, getValue(), true);
             }
@@ -237,10 +241,12 @@
     public static class Factory {
 
         private final FalsingManager mFalsingManager;
+        private final UiEventLogger mUiEventLogger;
 
         @Inject
-        public Factory(FalsingManager falsingManager) {
+        public Factory(FalsingManager falsingManager, UiEventLogger uiEventLogger) {
             mFalsingManager = falsingManager;
+            mUiEventLogger = uiEventLogger;
         }
 
         /**
@@ -250,11 +256,13 @@
          * @param viewRoot the {@link ViewGroup} that will contain the hierarchy. The inflated
          *                 hierarchy will not be attached
          */
-        public BrightnessSliderController create(Context context, @Nullable ViewGroup viewRoot) {
+        public BrightnessSliderController create(
+                Context context,
+                @Nullable ViewGroup viewRoot) {
             int layout = getLayout();
             BrightnessSliderView root = (BrightnessSliderView) LayoutInflater.from(context)
                     .inflate(layout, viewRoot, false);
-            return new BrightnessSliderController(root, mFalsingManager);
+            return new BrightnessSliderController(root, mFalsingManager, mUiEventLogger);
         }
 
         /** Get the layout to inflate based on what slider to use */
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderEvent.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderEvent.java
new file mode 100644
index 0000000..3a30880
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderEvent.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.settings.brightness;
+
+import com.android.internal.logging.UiEvent;
+import com.android.internal.logging.UiEventLogger;
+
+public enum BrightnessSliderEvent implements UiEventLogger.UiEventEnum {
+
+    @UiEvent(doc = "slider started to track touch")
+    SLIDER_STARTED_TRACKING_TOUCH(1472),
+    @UiEvent(doc = "slider stopped tracking touch")
+    SLIDER_STOPPED_TRACKING_TOUCH(1473);
+
+    private final int mId;
+
+    BrightnessSliderEvent(int id) {
+        mId = id;
+    }
+
+    @Override
+    public int getId() {
+        return mId;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
index c5de165..092cbf1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
@@ -388,7 +388,10 @@
         })
         ssView.setFalsingManager(falsingManager)
         ssView.setKeyguardBypassEnabled(bypassController.bypassEnabled)
-        return (ssView as View).apply { addOnAttachStateChangeListener(stateChangeListener) }
+        return (ssView as View).apply {
+            setTag(R.id.tag_smartspace_view, Any())
+            addOnAttachStateChangeListener(stateChangeListener)
+        }
     }
 
     private fun connectSession() {
@@ -450,12 +453,6 @@
         session?.requestSmartspaceUpdate()
     }
 
-    fun removeViewsFromParent(viewGroup: ViewGroup) {
-        smartspaceViews.toList().forEach {
-            viewGroup.removeView(it as View)
-        }
-    }
-
     /**
      * Disconnects the smartspace view from the smartspace service and cleans up any resources.
      */
@@ -596,3 +593,4 @@
         }
     }
 }
+
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt
index 62a0d13..5c2f9a8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt
@@ -39,7 +39,10 @@
 
     override fun attach(pipeline: NotifPipeline) {
         pipeline.addOnAfterRenderListListener(::onAfterRenderList)
-        groupExpansionManagerImpl.attach(pipeline)
+        // TODO(b/282865576): This has an issue where it makes changes to some groups without
+        // notifying listeners. To be fixed in QPR, but for now let's comment it out to avoid the
+        // group expansion bug.
+        // groupExpansionManagerImpl.attach(pipeline)
     }
 
     fun onAfterRenderList(entries: List<ListEntry>, controller: NotifStackController) =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerImpl.java
index 5d33804..46af03a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerImpl.java
@@ -67,29 +67,18 @@
      * Cleanup entries from mExpandedGroups that no longer exist in the pipeline.
      */
     private final OnBeforeRenderListListener mNotifTracker = (entries) -> {
-        if (mExpandedGroups.isEmpty()) {
-            return; // nothing to do
-        }
-
         final Set<NotificationEntry> renderingSummaries = new HashSet<>();
         for (ListEntry entry : entries) {
             if (entry instanceof GroupEntry) {
                 renderingSummaries.add(entry.getRepresentativeEntry());
             }
         }
-
-        // Create a copy of mExpandedGroups so we can modify it in a thread-safe way.
-        final var currentExpandedGroups = new HashSet<>(mExpandedGroups);
-        for (NotificationEntry entry : currentExpandedGroups) {
-            setExpanded(entry, renderingSummaries.contains(entry));
-        }
+        mExpandedGroups.removeIf(expandedGroup -> !renderingSummaries.contains(expandedGroup));
     };
 
     public void attach(NotifPipeline pipeline) {
-        if (mFeatureFlags.isEnabled(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE)) {
-            mDumpManager.registerDumpable(this);
-            pipeline.addOnBeforeRenderListListener(mNotifTracker);
-        }
+        mDumpManager.registerDumpable(this);
+        pipeline.addOnBeforeRenderListListener(mNotifTracker);
     }
 
     @Override
@@ -105,24 +94,11 @@
     @Override
     public void setGroupExpanded(NotificationEntry entry, boolean expanded) {
         final NotificationEntry groupSummary = mGroupMembershipManager.getGroupSummary(entry);
-        setExpanded(groupSummary, expanded);
-    }
-
-    /**
-     * Add or remove {@code entry} to/from {@code mExpandedGroups} and notify listeners if
-     * something changed. This assumes that {@code entry} is a group summary.
-     * <p>
-     * TODO(b/293434635): Currently, in spite of its docs,
-     * {@code mGroupMembershipManager.getGroupSummary(entry)} returns null if {@code entry} is
-     * already a summary. Instead of needing this helper method to bypass that, we probably want to
-     * move this code back to {@code setGroupExpanded} and use that everywhere.
-     */
-    private void setExpanded(NotificationEntry entry, boolean expanded) {
         boolean changed;
         if (expanded) {
-            changed = mExpandedGroups.add(entry);
+            changed = mExpandedGroups.add(groupSummary);
         } else {
-            changed = mExpandedGroups.remove(entry);
+            changed = mExpandedGroups.remove(groupSummary);
         }
 
         // Only notify listeners if something changed.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index ea57eb4..27b8406 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -92,6 +92,8 @@
 import com.android.systemui.unfold.FoldAodAnimationController;
 import com.android.systemui.unfold.SysUIUnfoldComponent;
 
+import dagger.Lazy;
+
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.HashSet;
@@ -101,7 +103,6 @@
 
 import javax.inject.Inject;
 
-import dagger.Lazy;
 import kotlinx.coroutines.CoroutineDispatcher;
 
 /**
@@ -450,14 +451,6 @@
         }
     }
 
-    private KeyguardStateController.Callback mKeyguardStateControllerCallback =
-            new KeyguardStateController.Callback() {
-                @Override
-                public void onUnlockedChanged() {
-                    updateAlternateBouncerShowing(mAlternateBouncerInteractor.maybeHide());
-                }
-            };
-
     private void registerListeners() {
         mKeyguardUpdateManager.registerCallback(mUpdateMonitorCallback);
         mStatusBarStateController.addCallback(this);
@@ -471,7 +464,6 @@
             mDockManager.addListener(mDockEventListener);
             mIsDocked = mDockManager.isDocked();
         }
-        mKeyguardStateController.addCallback(mKeyguardStateControllerCallback);
 
         if (mFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
             mShadeViewController.postToView(() ->
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
index cd1afc7..37f032b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
@@ -226,7 +226,7 @@
         val builder = InteractionJankMonitor.Configuration.Builder
             .withView(
                     InteractionJankMonitor.CUJ_SCREEN_OFF_SHOW_AOD,
-                    notifShadeWindowControllerLazy.get().windowRootView
+                    checkNotNull(notifShadeWindowControllerLazy.get().windowRootView)
             )
             .setTag(statusBarStateControllerImpl.getClockId())
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/VariableDateViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/VariableDateViewController.kt
index f040d0a..369f9ca 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/VariableDateViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/VariableDateViewController.kt
@@ -31,6 +31,7 @@
 import androidx.annotation.VisibleForTesting
 import com.android.systemui.Dependency
 import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.shade.ShadeLogger
 import com.android.systemui.util.ViewController
 import com.android.systemui.util.time.SystemClock
 import java.text.FieldPosition
@@ -80,6 +81,7 @@
 class VariableDateViewController(
     private val systemClock: SystemClock,
     private val broadcastDispatcher: BroadcastDispatcher,
+    private val shadeLogger: ShadeLogger,
     private val timeTickHandler: Handler,
     view: VariableDateView
 ) : ViewController<VariableDateView>(view) {
@@ -107,24 +109,29 @@
 
     private val intentReceiver: BroadcastReceiver = object : BroadcastReceiver() {
         override fun onReceive(context: Context, intent: Intent) {
+            val action = intent.action
+            if (
+                    Intent.ACTION_LOCALE_CHANGED == action ||
+                    Intent.ACTION_TIMEZONE_CHANGED == action
+            ) {
+                // need to get a fresh date format
+                dateFormat = null
+                shadeLogger.d("VariableDateViewController received intent to refresh date format")
+            }
+
+            val handler = mView.handler
+
             // If the handler is null, it means we received a broadcast while the view has not
             // finished being attached or in the process of being detached.
             // In that case, do not post anything.
-            val handler = mView.handler ?: return
-            val action = intent.action
-            if (
+            if (handler == null) {
+                shadeLogger.d("VariableDateViewController received intent but handler was null")
+            } else if (
                     Intent.ACTION_TIME_TICK == action ||
                     Intent.ACTION_TIME_CHANGED == action ||
                     Intent.ACTION_TIMEZONE_CHANGED == action ||
                     Intent.ACTION_LOCALE_CHANGED == action
             ) {
-                if (
-                        Intent.ACTION_LOCALE_CHANGED == action ||
-                        Intent.ACTION_TIMEZONE_CHANGED == action
-                ) {
-                    // need to get a fresh date format
-                    handler.post { dateFormat = null }
-                }
                 handler.post(::updateClock)
             }
         }
@@ -211,12 +218,14 @@
     class Factory @Inject constructor(
         private val systemClock: SystemClock,
         private val broadcastDispatcher: BroadcastDispatcher,
+        private val shadeLogger: ShadeLogger,
         @Named(Dependency.TIME_TICK_HANDLER_NAME) private val handler: Handler
     ) {
         fun create(view: VariableDateView): VariableDateViewController {
             return VariableDateViewController(
                     systemClock,
                     broadcastDispatcher,
+                    shadeLogger,
                     handler,
                     view
             )
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldProgressProvider.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldProgressProvider.kt
index 2325acf..f5decaa 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldProgressProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldProgressProvider.kt
@@ -17,11 +17,13 @@
 package com.android.systemui.unfold
 
 import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
+import com.android.systemui.unfold.updates.FoldProvider
 import com.android.wm.shell.unfold.ShellUnfoldProgressProvider
 import com.android.wm.shell.unfold.ShellUnfoldProgressProvider.UnfoldListener
 import java.util.concurrent.Executor
 
-class UnfoldProgressProvider(private val unfoldProgressProvider: UnfoldTransitionProgressProvider) :
+class UnfoldProgressProvider(private val unfoldProgressProvider: UnfoldTransitionProgressProvider,
+        private val foldProvider: FoldProvider) :
     ShellUnfoldProgressProvider {
 
     override fun addListener(executor: Executor, listener: UnfoldListener) {
@@ -39,5 +41,11 @@
                     executor.execute { listener.onStateChangeFinished() }
                 }
             })
+
+        foldProvider.registerCallback(object : FoldProvider.FoldCallback {
+            override fun onFoldUpdated(isFolded: Boolean) {
+                listener.onFoldStateChanged(isFolded)
+            }
+        }, executor)
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt
index 992b022..ed3eacd 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt
@@ -118,6 +118,7 @@
     @Singleton
     fun provideShellProgressProvider(
         config: UnfoldTransitionConfig,
+        foldProvider: FoldProvider,
         provider: Provider<Optional<UnfoldTransitionProgressProvider>>,
         @Named(UNFOLD_ONLY_PROVIDER)
         unfoldOnlyProvider: Provider<Optional<UnfoldTransitionProgressProvider>>
@@ -135,8 +136,9 @@
                 null
             }
 
-        return resultingProvider?.get()?.orElse(null)?.let(::UnfoldProgressProvider)
-            ?: ShellUnfoldProgressProvider.NO_PROVIDER
+        return resultingProvider?.get()?.orElse(null)?.let {
+            unfoldProgressProvider -> UnfoldProgressProvider(unfoldProgressProvider, foldProvider)
+        } ?: ShellUnfoldProgressProvider.NO_PROVIDER
     }
 
     @Provides
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java
index 98d4d22..1be8746 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java
@@ -23,7 +23,6 @@
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.atLeast;
-import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -136,6 +135,10 @@
     public void setup() {
         MockitoAnnotations.initMocks(this);
 
+        mFakeDateView.setTag(R.id.tag_smartspace_view, new Object());
+        mFakeWeatherView.setTag(R.id.tag_smartspace_view, new Object());
+        mFakeSmartspaceView.setTag(R.id.tag_smartspace_view, new Object());
+
         when(mView.findViewById(R.id.left_aligned_notification_icon_container))
                 .thenReturn(mNotificationIcons);
         when(mNotificationIcons.getLayoutParams()).thenReturn(
@@ -158,12 +161,6 @@
         when(mSmartspaceController.buildAndConnectDateView(any())).thenReturn(mFakeDateView);
         when(mSmartspaceController.buildAndConnectWeatherView(any())).thenReturn(mFakeWeatherView);
         when(mSmartspaceController.buildAndConnectView(any())).thenReturn(mFakeSmartspaceView);
-        doAnswer(invocation -> {
-            removeView(mFakeDateView);
-            removeView(mFakeWeatherView);
-            removeView(mFakeSmartspaceView);
-            return null;
-        }).when(mSmartspaceController).removeViewsFromParent(any());
         mExecutor = new FakeExecutor(new FakeSystemClock());
         mFakeFeatureFlags = new FakeFeatureFlags();
         mFakeFeatureFlags.set(FACE_AUTH_REFACTOR, false);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/BoundingBoxOverlapDetectorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/BoundingBoxOverlapDetectorTest.kt
index da55d5a..95b72d5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/BoundingBoxOverlapDetectorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/BoundingBoxOverlapDetectorTest.kt
@@ -28,7 +28,7 @@
 @SmallTest
 @RunWith(Parameterized::class)
 class BoundingBoxOverlapDetectorTest(val testCase: TestCase) : SysuiTestCase() {
-    val underTest = BoundingBoxOverlapDetector()
+    val underTest = BoundingBoxOverlapDetector(1f)
 
     @Test
     fun isGoodOverlap() {
@@ -83,7 +83,7 @@
         GESTURE_START
     )
 
-private val SENSOR = Rect(100 /* left */, 200 /* top */, 300 /* right */, 500 /* bottom */)
+private val SENSOR = Rect(100 /* left */, 200 /* top */, 300 /* right */, 400 /* bottom */)
 private val OVERLAY = Rect(0 /* left */, 100 /* top */, 400 /* right */, 600 /* bottom */)
 
 private fun genTestCases(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
index 11b0b07..47084c0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
@@ -312,10 +312,13 @@
             assertThat(message).isEqualTo(PromptMessage.Empty)
             assertThat(messageVisible).isFalse()
         }
+        val clearIconError = !restart
         assertThat(legacyState)
             .isEqualTo(
                 if (restart) {
                     AuthBiometricView.STATE_AUTHENTICATING
+                } else if (clearIconError) {
+                    AuthBiometricView.STATE_IDLE
                 } else {
                     AuthBiometricView.STATE_HELP
                 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/KeyguardBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/KeyguardBouncerViewModelTest.kt
index 8236165..d4bba72 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/KeyguardBouncerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/KeyguardBouncerViewModelTest.kt
@@ -29,6 +29,8 @@
 import com.android.systemui.bouncer.shared.model.BouncerShowMessageModel
 import com.android.systemui.bouncer.ui.BouncerView
 import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.coroutines.collectValues
 import com.android.systemui.flags.FakeFeatureFlags
 import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.DismissCallbackRegistry
@@ -156,4 +158,24 @@
         assertThat(isShowing).isEqualTo(false)
         job.cancel()
     }
+
+    @Test
+    fun keyguardPosition_noValueSet_emptyByDefault() = runTest {
+        val positionValues by collectValues(underTest.keyguardPosition)
+
+        runCurrent()
+
+        assertThat(positionValues).isEmpty()
+    }
+
+    @Test
+    fun keyguardPosition_valueSet_returnsValue() = runTest {
+        val position by collectLastValue(underTest.keyguardPosition)
+        runCurrent()
+
+        repository.setKeyguardPosition(123f)
+        runCurrent()
+
+        assertThat(position).isEqualTo(123f)
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractorTest.kt
index e0ae0c3..a3f7fc5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractorTest.kt
@@ -131,12 +131,14 @@
     }
 
     @Test
-    fun dispatchKeyEvent_menuActionUp_interactiveKeyguard_showsPrimaryBouncer() {
+    fun dispatchKeyEvent_menuActionUp_interactiveKeyguard_collapsesShade() {
         keyguardInteractorWithDependencies.repository.setWakefulnessModel(awakeWakefulnessMode)
         whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD)
         whenever(statusBarKeyguardViewManager.shouldDismissOnMenuPressed()).thenReturn(true)
 
-        verifyActionUpShowsPrimaryBouncer(KeyEvent.KEYCODE_MENU)
+        val actionUpMenuKeyEvent = KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_MENU)
+        assertThat(underTest.dispatchKeyEvent(actionUpMenuKeyEvent)).isTrue()
+        verify(shadeController).animateCollapseShadeForced()
     }
 
     @Test
@@ -145,48 +147,42 @@
         whenever(statusBarStateController.state).thenReturn(StatusBarState.SHADE_LOCKED)
         whenever(statusBarKeyguardViewManager.shouldDismissOnMenuPressed()).thenReturn(true)
 
-        verifyActionUpCollapsesTheShade(KeyEvent.KEYCODE_MENU)
+        // action down: does NOT collapse the shade
+        val actionDownMenuKeyEvent = KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MENU)
+        assertThat(underTest.dispatchKeyEvent(actionDownMenuKeyEvent)).isFalse()
+        verify(shadeController, never()).animateCollapseShadeForced()
+
+        // action up: collapses the shade
+        val actionUpMenuKeyEvent = KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_MENU)
+        assertThat(underTest.dispatchKeyEvent(actionUpMenuKeyEvent)).isTrue()
+        verify(shadeController).animateCollapseShadeForced()
     }
 
     @Test
-    fun dispatchKeyEvent_menuActionUp_nonInteractiveKeyguard_doNothing() {
+    fun dispatchKeyEvent_menuActionUp_nonInteractiveKeyguard_neverCollapsesShade() {
         keyguardInteractorWithDependencies.repository.setWakefulnessModel(asleepWakefulnessMode)
         whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD)
         whenever(statusBarKeyguardViewManager.shouldDismissOnMenuPressed()).thenReturn(true)
 
-        verifyActionsDoNothing(KeyEvent.KEYCODE_MENU)
+        val actionUpMenuKeyEvent = KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_MENU)
+        assertThat(underTest.dispatchKeyEvent(actionUpMenuKeyEvent)).isFalse()
+        verify(shadeController, never()).animateCollapseShadeForced()
     }
 
     @Test
-    fun dispatchKeyEvent_spaceActionUp_interactiveKeyguard_showsPrimaryBouncer() {
+    fun dispatchKeyEvent_spaceActionUp_interactiveKeyguard_collapsesShade() {
         keyguardInteractorWithDependencies.repository.setWakefulnessModel(awakeWakefulnessMode)
         whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD)
 
-        verifyActionUpShowsPrimaryBouncer(KeyEvent.KEYCODE_SPACE)
-    }
+        // action down: does NOT collapse the shade
+        val actionDownMenuKeyEvent = KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_SPACE)
+        assertThat(underTest.dispatchKeyEvent(actionDownMenuKeyEvent)).isFalse()
+        verify(shadeController, never()).animateCollapseShadeForced()
 
-    @Test
-    fun dispatchKeyEvent_spaceActionUp_shadeLocked_collapsesShade() {
-        keyguardInteractorWithDependencies.repository.setWakefulnessModel(awakeWakefulnessMode)
-        whenever(statusBarStateController.state).thenReturn(StatusBarState.SHADE_LOCKED)
-
-        verifyActionUpCollapsesTheShade(KeyEvent.KEYCODE_SPACE)
-    }
-
-    @Test
-    fun dispatchKeyEvent_enterActionUp_interactiveKeyguard_showsPrimaryBouncer() {
-        keyguardInteractorWithDependencies.repository.setWakefulnessModel(awakeWakefulnessMode)
-        whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD)
-
-        verifyActionUpShowsPrimaryBouncer(KeyEvent.KEYCODE_ENTER)
-    }
-
-    @Test
-    fun dispatchKeyEvent_enterActionUp_shadeLocked_collapsesShade() {
-        keyguardInteractorWithDependencies.repository.setWakefulnessModel(awakeWakefulnessMode)
-        whenever(statusBarStateController.state).thenReturn(StatusBarState.SHADE_LOCKED)
-
-        verifyActionUpCollapsesTheShade(KeyEvent.KEYCODE_ENTER)
+        // action up: collapses the shade
+        val actionUpMenuKeyEvent = KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_SPACE)
+        assertThat(underTest.dispatchKeyEvent(actionUpMenuKeyEvent)).isTrue()
+        verify(shadeController).animateCollapseShadeForced()
     }
 
     @Test
@@ -256,42 +252,4 @@
             .isFalse()
         verify(statusBarKeyguardViewManager, never()).interceptMediaKey(any())
     }
-
-    private fun verifyActionUpCollapsesTheShade(keycode: Int) {
-        // action down: does NOT collapse the shade
-        val actionDownMenuKeyEvent = KeyEvent(KeyEvent.ACTION_DOWN, keycode)
-        assertThat(underTest.dispatchKeyEvent(actionDownMenuKeyEvent)).isFalse()
-        verify(shadeController, never()).animateCollapseShadeForced()
-
-        // action up: collapses the shade
-        val actionUpMenuKeyEvent = KeyEvent(KeyEvent.ACTION_UP, keycode)
-        assertThat(underTest.dispatchKeyEvent(actionUpMenuKeyEvent)).isTrue()
-        verify(shadeController).animateCollapseShadeForced()
-    }
-
-    private fun verifyActionUpShowsPrimaryBouncer(keycode: Int) {
-        // action down: does NOT collapse the shade
-        val actionDownMenuKeyEvent = KeyEvent(KeyEvent.ACTION_DOWN, keycode)
-        assertThat(underTest.dispatchKeyEvent(actionDownMenuKeyEvent)).isFalse()
-        verify(statusBarKeyguardViewManager, never()).showPrimaryBouncer(any())
-
-        // action up: collapses the shade
-        val actionUpMenuKeyEvent = KeyEvent(KeyEvent.ACTION_UP, keycode)
-        assertThat(underTest.dispatchKeyEvent(actionUpMenuKeyEvent)).isTrue()
-        verify(statusBarKeyguardViewManager).showPrimaryBouncer(eq(true))
-    }
-
-    private fun verifyActionsDoNothing(keycode: Int) {
-        // action down: does nothing
-        val actionDownMenuKeyEvent = KeyEvent(KeyEvent.ACTION_DOWN, keycode)
-        assertThat(underTest.dispatchKeyEvent(actionDownMenuKeyEvent)).isFalse()
-        verify(shadeController, never()).animateCollapseShadeForced()
-        verify(statusBarKeyguardViewManager, never()).showPrimaryBouncer(any())
-
-        // action up: doesNothing
-        val actionUpMenuKeyEvent = KeyEvent(KeyEvent.ACTION_UP, keycode)
-        assertThat(underTest.dispatchKeyEvent(actionUpMenuKeyEvent)).isFalse()
-        verify(shadeController, never()).animateCollapseShadeForced()
-        verify(statusBarKeyguardViewManager, never()).showPrimaryBouncer(any())
-    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessSliderControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessSliderControllerTest.kt
index 2b39354..d75405f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessSliderControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessSliderControllerTest.kt
@@ -20,6 +20,7 @@
 import android.view.MotionEvent
 import android.widget.SeekBar
 import androidx.test.filters.SmallTest
+import com.android.internal.logging.testing.UiEventLoggerFake
 import com.android.settingslib.RestrictedLockUtils
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.classifier.FalsingManagerFake
@@ -39,6 +40,7 @@
 import org.mockito.Mockito.isNull
 import org.mockito.Mockito.never
 import org.mockito.Mockito.notNull
+import org.mockito.Mockito.times
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
 import org.mockito.Mockito.`when` as whenever
@@ -64,6 +66,7 @@
     private lateinit var seekBarChangeCaptor: ArgumentCaptor<SeekBar.OnSeekBarChangeListener>
     @Mock
     private lateinit var seekBar: SeekBar
+    private val uiEventLogger = UiEventLoggerFake()
     private var mFalsingManager: FalsingManagerFake = FalsingManagerFake()
 
     private lateinit var mController: BrightnessSliderController
@@ -75,7 +78,8 @@
         whenever(mirrorController.toggleSlider).thenReturn(mirror)
         whenever(motionEvent.copy()).thenReturn(motionEvent)
 
-        mController = BrightnessSliderController(brightnessSliderView, mFalsingManager)
+        mController =
+            BrightnessSliderController(brightnessSliderView, mFalsingManager, uiEventLogger)
         mController.init()
         mController.setOnChangedListener(listener)
     }
@@ -190,6 +194,7 @@
     @Test
     fun testSeekBarTrackingStarted() {
         whenever(brightnessSliderView.value).thenReturn(42)
+        val event = BrightnessSliderEvent.SLIDER_STARTED_TRACKING_TOUCH
 
         mController.onViewAttached()
         mController.setMirrorControllerAndMirror(mirrorController)
@@ -200,11 +205,14 @@
         verify(listener).onChanged(eq(true), eq(42), eq(false))
         verify(mirrorController).showMirror()
         verify(mirrorController).setLocationAndSize(brightnessSliderView)
+        assertThat(uiEventLogger.numLogs()).isEqualTo(1)
+        assertThat(uiEventLogger.eventId(0)).isEqualTo(event.id)
     }
 
     @Test
     fun testSeekBarTrackingStopped() {
         whenever(brightnessSliderView.value).thenReturn(23)
+        val event = BrightnessSliderEvent.SLIDER_STOPPED_TRACKING_TOUCH
 
         mController.onViewAttached()
         mController.setMirrorControllerAndMirror(mirrorController)
@@ -214,5 +222,7 @@
 
         verify(listener).onChanged(eq(false), eq(23), eq(true))
         verify(mirrorController).hideMirror()
+        assertThat(uiEventLogger.numLogs()).isEqualTo(1)
+        assertThat(uiEventLogger.eventId(0)).isEqualTo(event.id)
     }
 }
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
index ecaf137..48665fe 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
@@ -340,9 +340,9 @@
     @Test
     fun knownPluginAttached_clockAndListChanged_notLoaded() {
         val mockPluginLifecycle1 = mock<PluginLifecycleManager<ClockProviderPlugin>>()
-        whenever(mockPluginLifecycle1.getPackage()).thenReturn("com.android.systemui.falcon.one")
+        whenever(mockPluginLifecycle1.getPackage()).thenReturn("com.android.systemui.clocks.metro")
         val mockPluginLifecycle2 = mock<PluginLifecycleManager<ClockProviderPlugin>>()
-        whenever(mockPluginLifecycle2.getPackage()).thenReturn("com.android.systemui.falcon.two")
+        whenever(mockPluginLifecycle2.getPackage()).thenReturn("com.android.systemui.clocks.bignum")
 
         var changeCallCount = 0
         var listChangeCallCount = 0
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerTest.kt
index 38a8f414..4a94dc8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerTest.kt
@@ -21,21 +21,11 @@
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.flags.FakeFeatureFlags
 import com.android.systemui.flags.Flags
-import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder
-import com.android.systemui.statusbar.notification.collection.ListEntry
-import com.android.systemui.statusbar.notification.collection.NotifPipeline
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
-import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeRenderListListener
-import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager.OnGroupExpansionChangeListener
-import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.mock
-import com.android.systemui.util.mockito.withArgCaptor
 import org.junit.Assert
 import org.junit.Before
 import org.junit.Test
-import org.mockito.Mockito.never
-import org.mockito.Mockito.verify
-import org.mockito.Mockito.verifyNoMoreInteractions
 import org.mockito.Mockito.`when` as whenever
 
 @SmallTest
@@ -46,43 +36,13 @@
     private val groupMembershipManager: GroupMembershipManager = mock()
     private val featureFlags = FakeFeatureFlags()
 
-    private val pipeline: NotifPipeline = mock()
-    private lateinit var beforeRenderListListener: OnBeforeRenderListListener
-
-    private val summary1 = notificationEntry("foo", 1)
-    private val summary2 = notificationEntry("bar", 1)
-    private val entries =
-        listOf<ListEntry>(
-            GroupEntryBuilder()
-                .setSummary(summary1)
-                .setChildren(
-                    listOf(
-                        notificationEntry("foo", 2),
-                        notificationEntry("foo", 3),
-                        notificationEntry("foo", 4)
-                    )
-                )
-                .build(),
-            GroupEntryBuilder()
-                .setSummary(summary2)
-                .setChildren(
-                    listOf(
-                        notificationEntry("bar", 2),
-                        notificationEntry("bar", 3),
-                        notificationEntry("bar", 4)
-                    )
-                )
-                .build(),
-            notificationEntry("baz", 1)
-        )
-
-    private fun notificationEntry(pkg: String, id: Int) =
-        NotificationEntryBuilder().setPkg(pkg).setId(id).build().apply { row = mock() }
+    private val entry1 = NotificationEntryBuilder().build()
+    private val entry2 = NotificationEntryBuilder().build()
 
     @Before
     fun setUp() {
-        whenever(groupMembershipManager.getGroupSummary(summary1)).thenReturn(summary1)
-        whenever(groupMembershipManager.getGroupSummary(summary2)).thenReturn(summary2)
+        whenever(groupMembershipManager.getGroupSummary(entry1)).thenReturn(entry1)
+        whenever(groupMembershipManager.getGroupSummary(entry2)).thenReturn(entry2)
 
         gem = GroupExpansionManagerImpl(dumpManager, groupMembershipManager, featureFlags)
     }
@@ -94,15 +54,15 @@
         var listenerCalledCount = 0
         gem.registerGroupExpansionChangeListener { _, _ -> listenerCalledCount++ }
 
-        gem.setGroupExpanded(summary1, false)
+        gem.setGroupExpanded(entry1, false)
         Assert.assertEquals(0, listenerCalledCount)
-        gem.setGroupExpanded(summary1, true)
+        gem.setGroupExpanded(entry1, true)
         Assert.assertEquals(1, listenerCalledCount)
-        gem.setGroupExpanded(summary2, true)
+        gem.setGroupExpanded(entry2, true)
         Assert.assertEquals(2, listenerCalledCount)
-        gem.setGroupExpanded(summary1, true)
+        gem.setGroupExpanded(entry1, true)
         Assert.assertEquals(2, listenerCalledCount)
-        gem.setGroupExpanded(summary2, false)
+        gem.setGroupExpanded(entry2, false)
         Assert.assertEquals(3, listenerCalledCount)
     }
 
@@ -113,39 +73,15 @@
         var listenerCalledCount = 0
         gem.registerGroupExpansionChangeListener { _, _ -> listenerCalledCount++ }
 
-        gem.setGroupExpanded(summary1, false)
+        gem.setGroupExpanded(entry1, false)
         Assert.assertEquals(1, listenerCalledCount)
-        gem.setGroupExpanded(summary1, true)
+        gem.setGroupExpanded(entry1, true)
         Assert.assertEquals(2, listenerCalledCount)
-        gem.setGroupExpanded(summary2, true)
+        gem.setGroupExpanded(entry2, true)
         Assert.assertEquals(3, listenerCalledCount)
-        gem.setGroupExpanded(summary1, true)
+        gem.setGroupExpanded(entry1, true)
         Assert.assertEquals(4, listenerCalledCount)
-        gem.setGroupExpanded(summary2, false)
+        gem.setGroupExpanded(entry2, false)
         Assert.assertEquals(5, listenerCalledCount)
     }
-
-    @Test
-    fun testSyncWithPipeline() {
-        featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, true)
-        gem.attach(pipeline)
-        beforeRenderListListener = withArgCaptor {
-            verify(pipeline).addOnBeforeRenderListListener(capture())
-        }
-
-        val listener: OnGroupExpansionChangeListener = mock()
-        gem.registerGroupExpansionChangeListener(listener)
-
-        beforeRenderListListener.onBeforeRenderList(entries)
-        verify(listener, never()).onGroupExpansionChange(any(), any())
-
-        // Expand one of the groups.
-        gem.setGroupExpanded(summary1, true)
-        verify(listener).onGroupExpansionChange(summary1.row, true)
-
-        // Empty the pipeline list and verify that the group is no longer expanded.
-        beforeRenderListListener.onBeforeRenderList(emptyList())
-        verify(listener).onGroupExpansionChange(summary1.row, false)
-        verifyNoMoreInteractions(listener)
-    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/VariableDateViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/VariableDateViewControllerTest.kt
index 871a48c..12c1335 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/VariableDateViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/VariableDateViewControllerTest.kt
@@ -24,6 +24,7 @@
 import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.capture
+import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
 import org.junit.Before
@@ -99,6 +100,7 @@
         controller = VariableDateViewController(
                 systemClock,
                 broadcastDispatcher,
+                mock(),
                 testableHandler,
                 view
         )
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/data/repository/FakeKeyguardBouncerRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/data/repository/FakeKeyguardBouncerRepository.kt
index 10529e6..0847c85 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/data/repository/FakeKeyguardBouncerRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/data/repository/FakeKeyguardBouncerRepository.kt
@@ -21,7 +21,7 @@
     override val primaryBouncerScrimmed = _primaryBouncerScrimmed.asStateFlow()
     private val _panelExpansionAmount = MutableStateFlow(KeyguardBouncerConstants.EXPANSION_HIDDEN)
     override val panelExpansionAmount = _panelExpansionAmount.asStateFlow()
-    private val _keyguardPosition = MutableStateFlow(0f)
+    private val _keyguardPosition = MutableStateFlow<Float?>(null)
     override val keyguardPosition = _keyguardPosition.asStateFlow()
     private val _isBackButtonEnabled = MutableStateFlow<Boolean?>(null)
     override val isBackButtonEnabled = _isBackButtonEnabled.asStateFlow()
diff --git a/services/backup/java/com/android/server/backup/UserBackupManagerService.java b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
index d5aee92..4c137bc 100644
--- a/services/backup/java/com/android/server/backup/UserBackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
@@ -132,7 +132,8 @@
 import com.android.server.backup.transport.TransportNotAvailableException;
 import com.android.server.backup.transport.TransportNotRegisteredException;
 import com.android.server.backup.utils.BackupEligibilityRules;
-import com.android.server.backup.utils.BackupManagerMonitorUtils;
+import com.android.server.backup.utils.BackupManagerMonitorDumpsysUtils;
+import com.android.server.backup.utils.BackupManagerMonitorEventSender;
 import com.android.server.backup.utils.BackupObserverUtils;
 import com.android.server.backup.utils.SparseArrayUtils;
 
@@ -141,6 +142,7 @@
 import com.google.android.collect.Sets;
 
 import java.io.BufferedInputStream;
+import java.io.BufferedReader;
 import java.io.ByteArrayOutputStream;
 import java.io.DataInputStream;
 import java.io.DataOutputStream;
@@ -149,6 +151,7 @@
 import java.io.FileInputStream;
 import java.io.FileNotFoundException;
 import java.io.FileOutputStream;
+import java.io.FileReader;
 import java.io.IOException;
 import java.io.PrintWriter;
 import java.io.RandomAccessFile;
@@ -1830,12 +1833,14 @@
      */
     public int requestBackup(String[] packages, IBackupObserver observer,
             IBackupManagerMonitor monitor, int flags) {
+        BackupManagerMonitorEventSender  mBackupManagerMonitorEventSender =
+                getBMMEventSender(monitor);
         mContext.enforceCallingPermission(android.Manifest.permission.BACKUP, "requestBackup");
 
         if (packages == null || packages.length < 1) {
             Slog.e(TAG, addUserIdToLogMessage(mUserId, "No packages named for backup request"));
             BackupObserverUtils.sendBackupFinished(observer, BackupManager.ERROR_TRANSPORT_ABORTED);
-            monitor = BackupManagerMonitorUtils.monitorEvent(monitor,
+            mBackupManagerMonitorEventSender.monitorEvent(
                     BackupManagerMonitor.LOG_EVENT_ID_NO_PACKAGES,
                     null, BackupManagerMonitor.LOG_EVENT_CATEGORY_TRANSPORT, null);
             throw new IllegalArgumentException("No packages are provided for backup");
@@ -1853,7 +1858,7 @@
             final int logTag = mSetupComplete
                     ? BackupManagerMonitor.LOG_EVENT_ID_BACKUP_DISABLED
                     : BackupManagerMonitor.LOG_EVENT_ID_DEVICE_NOT_PROVISIONED;
-            monitor = BackupManagerMonitorUtils.monitorEvent(monitor, logTag, null,
+            mBackupManagerMonitorEventSender.monitorEvent(logTag, null,
                     BackupManagerMonitor.LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY, null);
             return BackupManager.ERROR_BACKUP_NOT_ALLOWED;
         }
@@ -1871,7 +1876,7 @@
         } catch (TransportNotRegisteredException | TransportNotAvailableException
                 | RemoteException e) {
             BackupObserverUtils.sendBackupFinished(observer, BackupManager.ERROR_TRANSPORT_ABORTED);
-            monitor = BackupManagerMonitorUtils.monitorEvent(monitor,
+            mBackupManagerMonitorEventSender.monitorEvent(
                     BackupManagerMonitor.LOG_EVENT_ID_TRANSPORT_IS_NULL,
                     null, BackupManagerMonitor.LOG_EVENT_CATEGORY_TRANSPORT, null);
             return BackupManager.ERROR_TRANSPORT_ABORTED;
@@ -3066,7 +3071,9 @@
                     /* caller */ "BMS.reportDelayedRestoreResult");
 
             IBackupManagerMonitor monitor = transportClient.getBackupManagerMonitor();
-            BackupManagerMonitorUtils.sendAgentLoggingResults(monitor, packageInfo, results,
+            BackupManagerMonitorEventSender  mBackupManagerMonitorEventSender =
+                    getBMMEventSender(monitor);
+            mBackupManagerMonitorEventSender.sendAgentLoggingResults(packageInfo, results,
                     BackupAnnotations.OperationType.RESTORE);
         } catch (NameNotFoundException | TransportNotAvailableException
                 | TransportNotRegisteredException | RemoteException e) {
@@ -3190,6 +3197,11 @@
         }
     }
 
+    @VisibleForTesting
+    BackupManagerMonitorEventSender getBMMEventSender(IBackupManagerMonitor monitor) {
+        return new BackupManagerMonitorEventSender(monitor);
+    }
+
     /** User-configurable enabling/disabling of backups. */
     public void setBackupEnabled(boolean enable) {
         setBackupEnabled(enable, /* persistToDisk */ true);
@@ -4148,6 +4160,7 @@
                 }
             }
             dumpInternal(pw);
+            dumpBMMEvents(pw);
         } finally {
             Binder.restoreCallingIdentity(identityToken);
         }
@@ -4165,6 +4178,23 @@
         }
     }
 
+    private void dumpBMMEvents(PrintWriter pw) {
+        BackupManagerMonitorDumpsysUtils bm =
+                new BackupManagerMonitorDumpsysUtils();
+        File events = bm.getBMMEventsFile();
+        pw.println("START OF BACKUP MANAGER MONITOR EVENTS");
+        try (BufferedReader reader = new BufferedReader(new FileReader(events))) {
+            String line;
+            while ((line = reader.readLine()) != null) {
+                pw.println(line);
+            }
+        } catch (IOException e) {
+            Slog.e(TAG, "IO Exception when reading BMM events from file: " + e);
+            pw.println("IO Exception when reading BMM events from file");
+        }
+        pw.println("END OF BACKUP MANAGER MONITOR EVENTS");
+    }
+
     @NeverCompile // Avoid size overhead of debugging code.
     private void dumpInternal(PrintWriter pw) {
         // Add prefix for only non-system users so that system user dumpsys is the same as before
diff --git a/services/backup/java/com/android/server/backup/fullbackup/FullBackupEngine.java b/services/backup/java/com/android/server/backup/fullbackup/FullBackupEngine.java
index ad29422..1271206 100644
--- a/services/backup/java/com/android/server/backup/fullbackup/FullBackupEngine.java
+++ b/services/backup/java/com/android/server/backup/fullbackup/FullBackupEngine.java
@@ -23,13 +23,11 @@
 import static com.android.server.backup.UserBackupManagerService.BACKUP_METADATA_FILENAME;
 import static com.android.server.backup.UserBackupManagerService.SHARED_BACKUP_AGENT_PACKAGE;
 
-import android.annotation.Nullable;
 import android.annotation.UserIdInt;
 import android.app.ApplicationThreadConstants;
 import android.app.IBackupAgent;
 import android.app.backup.BackupTransport;
 import android.app.backup.FullBackupDataOutput;
-import android.app.backup.IBackupManagerMonitor;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
@@ -44,7 +42,7 @@
 import com.android.server.backup.UserBackupManagerService;
 import com.android.server.backup.remote.RemoteCall;
 import com.android.server.backup.utils.BackupEligibilityRules;
-import com.android.server.backup.utils.BackupManagerMonitorUtils;
+import com.android.server.backup.utils.BackupManagerMonitorEventSender;
 import com.android.server.backup.utils.FullBackupUtils;
 
 import java.io.File;
@@ -69,7 +67,7 @@
     private final int mTransportFlags;
     private final BackupAgentTimeoutParameters mAgentTimeoutParameters;
     private final BackupEligibilityRules mBackupEligibilityRules;
-    @Nullable private final IBackupManagerMonitor mMonitor;
+    private final BackupManagerMonitorEventSender mBackupManagerMonitorEventSender;
 
     class FullBackupRunner implements Runnable {
         private final @UserIdInt int mUserId;
@@ -198,7 +196,7 @@
             int opToken,
             int transportFlags,
             BackupEligibilityRules backupEligibilityRules,
-            IBackupManagerMonitor monitor) {
+            BackupManagerMonitorEventSender backupManagerMonitorEventSender) {
         this.backupManagerService = backupManagerService;
         mOutput = output;
         mPreflightHook = preflightHook;
@@ -213,7 +211,7 @@
                         backupManagerService.getAgentTimeoutParameters(),
                         "Timeout parameters cannot be null");
         mBackupEligibilityRules = backupEligibilityRules;
-        mMonitor = monitor;
+        mBackupManagerMonitorEventSender = backupManagerMonitorEventSender;
     }
 
     public int preflightCheck() throws RemoteException {
@@ -270,7 +268,7 @@
                     result = BackupTransport.TRANSPORT_OK;
                 }
 
-                BackupManagerMonitorUtils.monitorAgentLoggingResults(mMonitor, mPkg, mAgent);
+                mBackupManagerMonitorEventSender.monitorAgentLoggingResults(mPkg, mAgent);
             } catch (IOException e) {
                 Slog.e(TAG, "Error backing up " + mPkg.packageName + ": " + e.getMessage());
                 result = BackupTransport.AGENT_ERROR;
diff --git a/services/backup/java/com/android/server/backup/fullbackup/PerformAdbBackupTask.java b/services/backup/java/com/android/server/backup/fullbackup/PerformAdbBackupTask.java
index cba1e29..dc67091 100644
--- a/services/backup/java/com/android/server/backup/fullbackup/PerformAdbBackupTask.java
+++ b/services/backup/java/com/android/server/backup/fullbackup/PerformAdbBackupTask.java
@@ -40,6 +40,7 @@
 import com.android.server.backup.OperationStorage;
 import com.android.server.backup.UserBackupManagerService;
 import com.android.server.backup.utils.BackupEligibilityRules;
+import com.android.server.backup.utils.BackupManagerMonitorEventSender;
 import com.android.server.backup.utils.PasswordUtils;
 
 import java.io.ByteArrayOutputStream;
@@ -421,7 +422,7 @@
                                 mCurrentOpToken,
                                 /*transportFlags=*/ 0,
                                 mBackupEligibilityRules,
-                                /* monitor= */ null);
+                                new BackupManagerMonitorEventSender(null));
                 sendOnBackupPackage(isSharedStorage ? "Shared storage" : pkg.packageName);
 
                 // Don't need to check preflight result as there is no preflight hook.
diff --git a/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java b/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java
index 162046a..6aed9aa 100644
--- a/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java
+++ b/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java
@@ -54,7 +54,7 @@
 import com.android.server.backup.transport.TransportConnection;
 import com.android.server.backup.transport.TransportNotAvailableException;
 import com.android.server.backup.utils.BackupEligibilityRules;
-import com.android.server.backup.utils.BackupManagerMonitorUtils;
+import com.android.server.backup.utils.BackupManagerMonitorEventSender;
 import com.android.server.backup.utils.BackupObserverUtils;
 
 import com.google.android.collect.Sets;
@@ -153,7 +153,6 @@
     CountDownLatch mLatch;
     FullBackupJob mJob;             // if a scheduled job needs to be finished afterwards
     IBackupObserver mBackupObserver;
-    @Nullable private IBackupManagerMonitor mMonitor;
     boolean mUserInitiated;
     SinglePackageBackupRunner mBackupRunner;
     private final int mBackupRunnerOpToken;
@@ -167,6 +166,7 @@
     private final int mCurrentOpToken;
     private final BackupAgentTimeoutParameters mAgentTimeoutParameters;
     private final BackupEligibilityRules mBackupEligibilityRules;
+    private BackupManagerMonitorEventSender mBackupManagerMonitorEventSender;
 
     public PerformFullTransportBackupTask(UserBackupManagerService backupManagerService,
             OperationStorage operationStorage,
@@ -185,11 +185,12 @@
         mJob = runningJob;
         mPackages = new ArrayList<>(whichPackages.length);
         mBackupObserver = backupObserver;
-        mMonitor = monitor;
         mListener = (listener != null) ? listener : OnTaskFinishedListener.NOP;
         mUserInitiated = userInitiated;
         mCurrentOpToken = backupManagerService.generateRandomIntegerToken();
         mBackupRunnerOpToken = backupManagerService.generateRandomIntegerToken();
+        mBackupManagerMonitorEventSender =
+                new BackupManagerMonitorEventSender(monitor);
         mAgentTimeoutParameters = Objects.requireNonNull(
                 backupManagerService.getAgentTimeoutParameters(),
                 "Timeout parameters cannot be null");
@@ -218,7 +219,7 @@
                     if (MORE_DEBUG) {
                         Slog.d(TAG, "Ignoring ineligible package " + pkg);
                     }
-                    mMonitor = BackupManagerMonitorUtils.monitorEvent(mMonitor,
+                    mBackupManagerMonitorEventSender.monitorEvent(
                             BackupManagerMonitor.LOG_EVENT_ID_PACKAGE_INELIGIBLE,
                             mCurrentPackage,
                             BackupManagerMonitor.LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
@@ -233,7 +234,7 @@
                         Slog.d(TAG, "Ignoring full-data backup of key/value participant "
                                 + pkg);
                     }
-                    mMonitor = BackupManagerMonitorUtils.monitorEvent(mMonitor,
+                    mBackupManagerMonitorEventSender.monitorEvent(
                             BackupManagerMonitor.LOG_EVENT_ID_PACKAGE_KEY_VALUE_PARTICIPANT,
                             mCurrentPackage,
                             BackupManagerMonitor.LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
@@ -248,7 +249,7 @@
                     if (MORE_DEBUG) {
                         Slog.d(TAG, "Ignoring stopped package " + pkg);
                     }
-                    mMonitor = BackupManagerMonitorUtils.monitorEvent(mMonitor,
+                    mBackupManagerMonitorEventSender.monitorEvent(
                             BackupManagerMonitor.LOG_EVENT_ID_PACKAGE_STOPPED,
                             mCurrentPackage,
                             BackupManagerMonitor.LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
@@ -260,7 +261,7 @@
                 mPackages.add(info);
             } catch (NameNotFoundException e) {
                 Slog.i(TAG, "Requested package " + pkg + " not found; ignoring");
-                mMonitor = BackupManagerMonitorUtils.monitorEvent(mMonitor,
+                mBackupManagerMonitorEventSender.monitorEvent(
                         BackupManagerMonitor.LOG_EVENT_ID_PACKAGE_NOT_FOUND,
                         mCurrentPackage,
                         BackupManagerMonitor.LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
@@ -356,8 +357,8 @@
                 } else {
                     monitoringEvent = BackupManagerMonitor.LOG_EVENT_ID_DEVICE_NOT_PROVISIONED;
                 }
-                mMonitor = BackupManagerMonitorUtils
-                        .monitorEvent(mMonitor, monitoringEvent, null,
+                mBackupManagerMonitorEventSender
+                        .monitorEvent(monitoringEvent, null,
                                 BackupManagerMonitor.LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
                                 null);
                 mUpdateSchedule = false;
@@ -369,7 +370,7 @@
             if (transport == null) {
                 Slog.w(TAG, "Transport not present; full data backup not performed");
                 backupRunStatus = BackupManager.ERROR_TRANSPORT_ABORTED;
-                mMonitor = BackupManagerMonitorUtils.monitorEvent(mMonitor,
+                mBackupManagerMonitorEventSender.monitorEvent(
                         BackupManagerMonitor.LOG_EVENT_ID_PACKAGE_TRANSPORT_NOT_PRESENT,
                         mCurrentPackage, BackupManagerMonitor.LOG_EVENT_CATEGORY_TRANSPORT,
                         null);
@@ -378,9 +379,10 @@
 
             // In some cases there may not be a monitor passed in when creating this task. So, if we
             // don't have one already we ask the transport for a monitor.
-            if (mMonitor == null) {
+            if (mBackupManagerMonitorEventSender.getMonitor() == null) {
                 try {
-                    mMonitor = transport.getBackupManagerMonitor();
+                    mBackupManagerMonitorEventSender
+                            .setMonitor(transport.getBackupManagerMonitor());
                 } catch (RemoteException e) {
                     Slog.i(TAG, "Failed to retrieve monitor from transport");
                 }
@@ -457,11 +459,11 @@
                                     + packageName + ": " + preflightResult
                                     + ", not running backup.");
                         }
-                        mMonitor = BackupManagerMonitorUtils.monitorEvent(mMonitor,
+                        mBackupManagerMonitorEventSender.monitorEvent(
                                 BackupManagerMonitor.LOG_EVENT_ID_ERROR_PREFLIGHT,
                                 mCurrentPackage,
                                 BackupManagerMonitor.LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
-                                BackupManagerMonitorUtils.putMonitoringExtra(null,
+                                mBackupManagerMonitorEventSender.putMonitoringExtra(null,
                                         BackupManagerMonitor.EXTRA_LOG_PREFLIGHT_ERROR,
                                         preflightResult));
                         backupPackageStatus = (int) preflightResult;
@@ -492,7 +494,7 @@
                         if (backupPackageStatus == BackupTransport.TRANSPORT_QUOTA_EXCEEDED) {
                             Slog.w(TAG, "Package hit quota limit in-flight " + packageName
                                     + ": " + totalRead + " of " + quota);
-                            mMonitor = BackupManagerMonitorUtils.monitorEvent(mMonitor,
+                            mBackupManagerMonitorEventSender.monitorEvent(
                                     BackupManagerMonitor.LOG_EVENT_ID_QUOTA_HIT_PREFLIGHT,
                                     mCurrentPackage,
                                     BackupManagerMonitor.LOG_EVENT_CATEGORY_TRANSPORT,
@@ -647,11 +649,11 @@
         } catch (Exception e) {
             backupRunStatus = BackupManager.ERROR_TRANSPORT_ABORTED;
             Slog.w(TAG, "Exception trying full transport backup", e);
-            mMonitor = BackupManagerMonitorUtils.monitorEvent(mMonitor,
+            mBackupManagerMonitorEventSender.monitorEvent(
                     BackupManagerMonitor.LOG_EVENT_ID_EXCEPTION_FULL_BACKUP,
                     mCurrentPackage,
                     BackupManagerMonitor.LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
-                    BackupManagerMonitorUtils.putMonitoringExtra(null,
+                    mBackupManagerMonitorEventSender.putMonitoringExtra(null,
                             BackupManagerMonitor.EXTRA_LOG_EXCEPTION_FULL_BACKUP,
                             Log.getStackTraceString(e)));
 
@@ -885,7 +887,7 @@
                             mCurrentOpToken,
                             mTransportFlags,
                             mBackupEligibilityRules,
-                            mMonitor);
+                            mBackupManagerMonitorEventSender);
             try {
                 try {
                     if (!mIsCancelled) {
@@ -967,7 +969,7 @@
                 Slog.w(TAG, "Full backup cancel of " + mTarget.packageName);
             }
 
-            mMonitor = BackupManagerMonitorUtils.monitorEvent(mMonitor,
+            mBackupManagerMonitorEventSender.monitorEvent(
                     BackupManagerMonitor.LOG_EVENT_ID_FULL_BACKUP_CANCEL,
                     mCurrentPackage, BackupManagerMonitor.LOG_EVENT_CATEGORY_AGENT, null);
             mIsCancelled = true;
diff --git a/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupReporter.java b/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupReporter.java
index 4632cb0..20c8cf6 100644
--- a/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupReporter.java
+++ b/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupReporter.java
@@ -32,7 +32,7 @@
 import com.android.server.backup.DataChangedJournal;
 import com.android.server.backup.UserBackupManagerService;
 import com.android.server.backup.remote.RemoteResult;
-import com.android.server.backup.utils.BackupManagerMonitorUtils;
+import com.android.server.backup.utils.BackupManagerMonitorEventSender;
 import com.android.server.backup.utils.BackupObserverUtils;
 
 import java.io.File;
@@ -65,21 +65,21 @@
 
     private final UserBackupManagerService mBackupManagerService;
     private final IBackupObserver mObserver;
-    @Nullable private IBackupManagerMonitor mMonitor;
+    private final BackupManagerMonitorEventSender mBackupManagerMonitorEventSender;
 
     KeyValueBackupReporter(
             UserBackupManagerService backupManagerService,
             IBackupObserver observer,
-            @Nullable IBackupManagerMonitor monitor) {
+            BackupManagerMonitorEventSender backupManagerMonitorEventSender) {
         mBackupManagerService = backupManagerService;
         mObserver = observer;
-        mMonitor = monitor;
+        mBackupManagerMonitorEventSender = backupManagerMonitorEventSender;
     }
 
     /** Returns the monitor or {@code null} if we lost connection to it. */
     @Nullable
     IBackupManagerMonitor getMonitor() {
-        return mMonitor;
+        return mBackupManagerMonitorEventSender.getMonitor();
     }
 
     IBackupObserver getObserver() {
@@ -208,13 +208,11 @@
     void onAgentIllegalKey(PackageInfo packageInfo, String key) {
         String packageName = packageInfo.packageName;
         EventLog.writeEvent(EventLogTags.BACKUP_AGENT_FAILURE, packageName, "bad key");
-        mMonitor =
-                BackupManagerMonitorUtils.monitorEvent(
-                        mMonitor,
+        mBackupManagerMonitorEventSender.monitorEvent(
                         BackupManagerMonitor.LOG_EVENT_ID_ILLEGAL_KEY,
                         packageInfo,
                         BackupManagerMonitor.LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
-                        BackupManagerMonitorUtils.putMonitoringExtra(
+                        mBackupManagerMonitorEventSender.putMonitoringExtra(
                                 null, BackupManagerMonitor.EXTRA_LOG_ILLEGAL_KEY, key));
         BackupObserverUtils.sendBackupOnPackageResult(
                 mObserver, packageName, BackupManager.ERROR_AGENT_FAILURE);
@@ -254,13 +252,11 @@
         if (MORE_DEBUG) {
             Slog.i(TAG, "No backup data written, not calling transport");
         }
-        mMonitor =
-                BackupManagerMonitorUtils.monitorEvent(
-                        mMonitor,
-                        BackupManagerMonitor.LOG_EVENT_ID_NO_DATA_TO_SEND,
-                        packageInfo,
-                        BackupManagerMonitor.LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
-                        null);
+        mBackupManagerMonitorEventSender.monitorEvent(
+                BackupManagerMonitor.LOG_EVENT_ID_NO_DATA_TO_SEND,
+                packageInfo,
+                BackupManagerMonitor.LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
+                null);
     }
 
     void onPackageBackupComplete(String packageName, long size) {
@@ -291,8 +287,7 @@
 
     void onPackageBackupNonIncrementalRequired(PackageInfo packageInfo) {
         Slog.i(TAG, "Transport lost data, retrying package");
-        BackupManagerMonitorUtils.monitorEvent(
-                mMonitor,
+        mBackupManagerMonitorEventSender.monitorEvent(
                 BackupManagerMonitor.LOG_EVENT_ID_TRANSPORT_NON_INCREMENTAL_BACKUP_REQUIRED,
                 packageInfo,
                 BackupManagerMonitor.LOG_EVENT_CATEGORY_TRANSPORT,
@@ -335,28 +330,24 @@
         EventLog.writeEvent(EventLogTags.BACKUP_AGENT_FAILURE, packageName);
         // Time-out used to be implemented as cancel w/ cancelAll = false.
         // TODO: Change monitoring event to reflect time-out as an event itself.
-        mMonitor =
-                BackupManagerMonitorUtils.monitorEvent(
-                        mMonitor,
-                        BackupManagerMonitor.LOG_EVENT_ID_KEY_VALUE_BACKUP_CANCEL,
-                        packageInfo,
-                        BackupManagerMonitor.LOG_EVENT_CATEGORY_AGENT,
-                        BackupManagerMonitorUtils.putMonitoringExtra(
-                                null, BackupManagerMonitor.EXTRA_LOG_CANCEL_ALL, false));
+        mBackupManagerMonitorEventSender.monitorEvent(
+                BackupManagerMonitor.LOG_EVENT_ID_KEY_VALUE_BACKUP_CANCEL,
+                packageInfo,
+                BackupManagerMonitor.LOG_EVENT_CATEGORY_AGENT,
+                mBackupManagerMonitorEventSender.putMonitoringExtra(
+                        null, BackupManagerMonitor.EXTRA_LOG_CANCEL_ALL, false));
     }
 
     void onAgentCancelled(@Nullable PackageInfo packageInfo) {
         String packageName = getPackageName(packageInfo);
         Slog.i(TAG, "Cancel backing up " + packageName);
         EventLog.writeEvent(EventLogTags.BACKUP_AGENT_FAILURE, packageName);
-        mMonitor =
-                BackupManagerMonitorUtils.monitorEvent(
-                        mMonitor,
-                        BackupManagerMonitor.LOG_EVENT_ID_KEY_VALUE_BACKUP_CANCEL,
-                        packageInfo,
-                        BackupManagerMonitor.LOG_EVENT_CATEGORY_AGENT,
-                        BackupManagerMonitorUtils.putMonitoringExtra(
-                                null, BackupManagerMonitor.EXTRA_LOG_CANCEL_ALL, true));
+        mBackupManagerMonitorEventSender.monitorEvent(
+                BackupManagerMonitor.LOG_EVENT_ID_KEY_VALUE_BACKUP_CANCEL,
+                packageInfo,
+                BackupManagerMonitor.LOG_EVENT_CATEGORY_AGENT,
+                mBackupManagerMonitorEventSender.putMonitoringExtra(
+                        null, BackupManagerMonitor.EXTRA_LOG_CANCEL_ALL, true));
     }
 
     void onAgentResultError(@Nullable PackageInfo packageInfo) {
diff --git a/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java b/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java
index 41e8092..3a6e1ca 100644
--- a/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java
+++ b/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java
@@ -68,7 +68,7 @@
 import com.android.server.backup.transport.TransportConnection;
 import com.android.server.backup.transport.TransportNotAvailableException;
 import com.android.server.backup.utils.BackupEligibilityRules;
-import com.android.server.backup.utils.BackupManagerMonitorUtils;
+import com.android.server.backup.utils.BackupManagerMonitorEventSender;
 
 import libcore.io.IoUtils;
 
@@ -225,7 +225,8 @@
             boolean nonIncremental,
             BackupEligibilityRules backupEligibilityRules) {
         KeyValueBackupReporter reporter =
-                new KeyValueBackupReporter(backupManagerService, observer, monitor);
+                new KeyValueBackupReporter(backupManagerService, observer,
+                        new BackupManagerMonitorEventSender(monitor));
         KeyValueBackupTask task =
                 new KeyValueBackupTask(
                         backupManagerService,
@@ -698,8 +699,9 @@
 
         try {
             extractAgentData(mCurrentPackage);
-            BackupManagerMonitorUtils.monitorAgentLoggingResults(
-                    mReporter.getMonitor(), mCurrentPackage, mAgent);
+            BackupManagerMonitorEventSender mBackupManagerMonitorEventSender =
+                    new BackupManagerMonitorEventSender(mReporter.getMonitor());
+            mBackupManagerMonitorEventSender.monitorAgentLoggingResults(mCurrentPackage, mAgent);
             int status = sendDataToTransport(mCurrentPackage);
             cleanUpAgentForTransportStatus(status);
         } catch (AgentException | TaskException e) {
diff --git a/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java b/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
index 8cbb5dc..e04bf11 100644
--- a/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
+++ b/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
@@ -16,6 +16,8 @@
 
 package com.android.server.backup.restore;
 
+import static android.app.backup.BackupAnnotations.OperationType.RESTORE;
+
 import static com.android.server.backup.BackupManagerService.DEBUG;
 import static com.android.server.backup.BackupManagerService.MORE_DEBUG;
 import static com.android.server.backup.BackupManagerService.TAG;
@@ -30,6 +32,7 @@
 import android.annotation.Nullable;
 import android.app.ApplicationThreadConstants;
 import android.app.IBackupAgent;
+import android.app.backup.BackupAnnotations;
 import android.app.backup.BackupDataInput;
 import android.app.backup.BackupDataOutput;
 import android.app.backup.BackupManagerMonitor;
@@ -70,7 +73,7 @@
 import com.android.server.backup.transport.BackupTransportClient;
 import com.android.server.backup.transport.TransportConnection;
 import com.android.server.backup.utils.BackupEligibilityRules;
-import com.android.server.backup.utils.BackupManagerMonitorUtils;
+import com.android.server.backup.utils.BackupManagerMonitorEventSender;
 
 import libcore.io.IoUtils;
 
@@ -84,7 +87,6 @@
 import java.util.Set;
 
 public class PerformUnifiedRestoreTask implements BackupRestoreTask {
-
     private UserBackupManagerService backupManagerService;
     private final OperationStorage mOperationStorage;
     private final int mUserId;
@@ -98,8 +100,7 @@
     // Restore observer; may be null
     private IRestoreObserver mObserver;
 
-    // BackuoManagerMonitor; may be null
-    private IBackupManagerMonitor mMonitor;
+    private BackupManagerMonitorEventSender mBackupManagerMonitorEventSender;
 
     // Token identifying the dataset to the transport
     private long mToken;
@@ -181,6 +182,8 @@
         mUserId = 0;
         mBackupEligibilityRules = null;
         this.backupManagerService = backupManagerService;
+        mBackupManagerMonitorEventSender =
+                new BackupManagerMonitorEventSender(/*monitor*/null);
     }
 
     // This task can assume that the wakelock is properly held for it and doesn't have to worry
@@ -208,7 +211,8 @@
 
         mTransportConnection = transportConnection;
         mObserver = observer;
-        mMonitor = monitor;
+        mBackupManagerMonitorEventSender =
+                new BackupManagerMonitorEventSender(monitor);
         mToken = restoreSetToken;
         mPmToken = pmToken;
         mTargetPackage = targetPackage;
@@ -410,8 +414,8 @@
 
             // If the requester of the restore has not passed in a monitor, we ask the transport
             // for one.
-            if (mMonitor == null) {
-                mMonitor = transport.getBackupManagerMonitor();
+            if (mBackupManagerMonitorEventSender.getMonitor() == null) {
+                mBackupManagerMonitorEventSender.setMonitor(transport.getBackupManagerMonitor());
             }
 
             mStatus = transport.startRestore(mToken, packages);
@@ -425,10 +429,12 @@
             RestoreDescription desc = transport.nextRestorePackage();
             if (desc == null) {
                 Slog.e(TAG, "No restore metadata available; halting");
-                mMonitor = BackupManagerMonitorUtils.monitorEvent(mMonitor,
+                Bundle monitoringExtras = addRestoreOperationTypeToEvent(/*extras*/null);
+                mBackupManagerMonitorEventSender.monitorEvent(
                         BackupManagerMonitor.LOG_EVENT_ID_NO_RESTORE_METADATA_AVAILABLE,
                         mCurrentPackage,
-                        BackupManagerMonitor.LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY, null);
+                        BackupManagerMonitor.LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
+                        monitoringExtras);
                 mStatus = BackupTransport.TRANSPORT_ERROR;
                 executeNextState(UnifiedRestoreState.FINAL);
                 return;
@@ -437,10 +443,12 @@
                     desc.getPackageName())) {
                 Slog.e(TAG, "Required package metadata but got "
                         + desc.getPackageName());
-                mMonitor = BackupManagerMonitorUtils.monitorEvent(mMonitor,
+                Bundle monitoringExtras = addRestoreOperationTypeToEvent(/*extras*/null);
+                mBackupManagerMonitorEventSender.monitorEvent(
                         BackupManagerMonitor.LOG_EVENT_ID_NO_PM_METADATA_RECEIVED,
                         mCurrentPackage,
-                        BackupManagerMonitor.LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY, null);
+                        BackupManagerMonitor.LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
+                        monitoringExtras);
                 mStatus = BackupTransport.TRANSPORT_ERROR;
                 executeNextState(UnifiedRestoreState.FINAL);
                 return;
@@ -472,10 +480,12 @@
             // the restore operation.
             if (!mPmAgent.hasMetadata()) {
                 Slog.e(TAG, "PM agent has no metadata, so not restoring");
-                mMonitor = BackupManagerMonitorUtils.monitorEvent(mMonitor,
+                Bundle monitoringExtras = addRestoreOperationTypeToEvent(/*extras*/null);
+                mBackupManagerMonitorEventSender.monitorEvent(
                         BackupManagerMonitor.LOG_EVENT_ID_PM_AGENT_HAS_NO_METADATA,
                         mCurrentPackage,
-                        BackupManagerMonitor.LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY, null);
+                        BackupManagerMonitor.LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
+                        monitoringExtras);
                 EventLog.writeEvent(EventLogTags.RESTORE_AGENT_FAILURE,
                         PACKAGE_MANAGER_SENTINEL,
                         "Package manager restore metadata missing");
@@ -492,10 +502,12 @@
         } catch (Exception e) {
             // If we lost the transport at any time, halt
             Slog.e(TAG, "Unable to contact transport for restore: " + e.getMessage());
-            mMonitor = BackupManagerMonitorUtils.monitorEvent(mMonitor,
+            Bundle monitoringExtras = addRestoreOperationTypeToEvent(/*extras*/null);
+            mBackupManagerMonitorEventSender.monitorEvent(
                     BackupManagerMonitor.LOG_EVENT_ID_LOST_TRANSPORT,
                     null,
-                    BackupManagerMonitor.LOG_EVENT_CATEGORY_TRANSPORT, null);
+                    BackupManagerMonitor.LOG_EVENT_CATEGORY_TRANSPORT,
+                    monitoringExtras);
             mStatus = BackupTransport.TRANSPORT_ERROR;
             backupManagerService.getBackupHandler().removeMessages(
                     MSG_BACKUP_RESTORE_STEP, this);
@@ -552,11 +564,12 @@
                 // Whoops, we thought we could restore this package but it
                 // turns out not to be present.  Skip it.
                 Slog.e(TAG, "Package not present: " + pkgName);
-                mMonitor = BackupManagerMonitorUtils.monitorEvent(mMonitor,
+                Bundle monitoringExtras = addRestoreOperationTypeToEvent(/*extras*/null);
+                mBackupManagerMonitorEventSender.monitorEvent(
                         BackupManagerMonitor.LOG_EVENT_ID_PACKAGE_NOT_PRESENT,
                         mCurrentPackage,
                         BackupManagerMonitor.LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
-                        null);
+                        monitoringExtras);
                 EventLog.writeEvent(EventLogTags.RESTORE_AGENT_FAILURE, pkgName,
                         "Package missing on device");
                 nextState = UnifiedRestoreState.RUNNING_QUEUE;
@@ -572,13 +585,15 @@
                     String message = "Source version " + metaInfo.versionCode
                             + " > installed version " + mCurrentPackage.getLongVersionCode();
                     Slog.w(TAG, "Package " + pkgName + ": " + message);
-                    Bundle monitoringExtras = BackupManagerMonitorUtils.putMonitoringExtra(null,
+                    Bundle monitoringExtras = mBackupManagerMonitorEventSender.putMonitoringExtra(
+                            null,
                             BackupManagerMonitor.EXTRA_LOG_RESTORE_VERSION,
                             metaInfo.versionCode);
-                    monitoringExtras = BackupManagerMonitorUtils.putMonitoringExtra(
+                    monitoringExtras = mBackupManagerMonitorEventSender.putMonitoringExtra(
                             monitoringExtras,
                             BackupManagerMonitor.EXTRA_LOG_RESTORE_ANYWAY, false);
-                    mMonitor = BackupManagerMonitorUtils.monitorEvent(mMonitor,
+                    monitoringExtras = addRestoreOperationTypeToEvent(monitoringExtras);
+                    mBackupManagerMonitorEventSender.monitorEvent(
                             BackupManagerMonitor.LOG_EVENT_ID_RESTORE_VERSION_HIGHER,
                             mCurrentPackage,
                             BackupManagerMonitor.LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
@@ -593,13 +608,15 @@
                                 + " > installed version " + mCurrentPackage.getLongVersionCode()
                                 + " but restoreAnyVersion");
                     }
-                    Bundle monitoringExtras = BackupManagerMonitorUtils.putMonitoringExtra(null,
+                    Bundle monitoringExtras = mBackupManagerMonitorEventSender.putMonitoringExtra(
+                            null,
                             BackupManagerMonitor.EXTRA_LOG_RESTORE_VERSION,
                             metaInfo.versionCode);
-                    monitoringExtras = BackupManagerMonitorUtils.putMonitoringExtra(
+                    monitoringExtras = mBackupManagerMonitorEventSender.putMonitoringExtra(
                             monitoringExtras,
                             BackupManagerMonitor.EXTRA_LOG_RESTORE_ANYWAY, true);
-                    mMonitor = BackupManagerMonitorUtils.monitorEvent(mMonitor,
+                    monitoringExtras = addRestoreOperationTypeToEvent(monitoringExtras);
+                    mBackupManagerMonitorEventSender.monitorEvent(
                             BackupManagerMonitor.LOG_EVENT_ID_RESTORE_VERSION_HIGHER,
                             mCurrentPackage,
                             BackupManagerMonitor.LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
@@ -652,9 +669,10 @@
                 Slog.i(TAG, "Data exists for package " + packageName
                         + " but app has no agent; skipping");
             }
-            mMonitor = BackupManagerMonitorUtils.monitorEvent(mMonitor,
+            Bundle monitoringExtras = addRestoreOperationTypeToEvent(/*extras*/null);
+            mBackupManagerMonitorEventSender.monitorEvent(
                     BackupManagerMonitor.LOG_EVENT_ID_APP_HAS_NO_AGENT, mCurrentPackage,
-                    BackupManagerMonitor.LOG_EVENT_CATEGORY_AGENT, null);
+                    BackupManagerMonitor.LOG_EVENT_CATEGORY_AGENT, monitoringExtras);
             EventLog.writeEvent(EventLogTags.RESTORE_AGENT_FAILURE, packageName,
                     "Package has no agent");
             executeNextState(UnifiedRestoreState.RUNNING_QUEUE);
@@ -665,9 +683,11 @@
         PackageManagerInternal pmi = LocalServices.getService(PackageManagerInternal.class);
         if (!BackupUtils.signaturesMatch(metaInfo.sigHashes, mCurrentPackage, pmi)) {
             Slog.w(TAG, "Signature mismatch restoring " + packageName);
-            mMonitor = BackupManagerMonitorUtils.monitorEvent(mMonitor,
+            Bundle monitoringExtras = addRestoreOperationTypeToEvent(/*extras*/null);
+            mBackupManagerMonitorEventSender.monitorEvent(
                     BackupManagerMonitor.LOG_EVENT_ID_SIGNATURE_MISMATCH, mCurrentPackage,
-                    BackupManagerMonitor.LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY, null);
+                    BackupManagerMonitor.LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
+                    monitoringExtras);
             EventLog.writeEvent(EventLogTags.RESTORE_AGENT_FAILURE, packageName,
                     "Signature mismatch");
             executeNextState(UnifiedRestoreState.RUNNING_QUEUE);
@@ -681,9 +701,11 @@
                 mBackupEligibilityRules.getBackupDestination());
         if (mAgent == null) {
             Slog.w(TAG, "Can't find backup agent for " + packageName);
-            mMonitor = BackupManagerMonitorUtils.monitorEvent(mMonitor,
+            Bundle monitoringExtras = addRestoreOperationTypeToEvent(/*extras*/null);
+            mBackupManagerMonitorEventSender.monitorEvent(
                     BackupManagerMonitor.LOG_EVENT_ID_CANT_FIND_AGENT, mCurrentPackage,
-                    BackupManagerMonitor.LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY, null);
+                    BackupManagerMonitor.LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
+                    monitoringExtras);
             EventLog.writeEvent(EventLogTags.RESTORE_AGENT_FAILURE, packageName,
                     "Restore agent missing");
             executeNextState(UnifiedRestoreState.RUNNING_QUEUE);
@@ -941,8 +963,9 @@
             EventLog.writeEvent(EventLogTags.FULL_RESTORE_PACKAGE,
                     mCurrentPackage.packageName);
 
-            mEngine = new FullRestoreEngine(backupManagerService, mOperationStorage, this, null,
-                    mMonitor, mCurrentPackage, false, mEphemeralOpToken, false,
+            mEngine = new FullRestoreEngine(backupManagerService, mOperationStorage,
+                    this, null, mBackupManagerMonitorEventSender.getMonitor(),
+                    mCurrentPackage, false, mEphemeralOpToken, false,
                     mBackupEligibilityRules);
             mEngineThread = new FullRestoreEngineThread(mEngine, mEnginePipes[0]);
 
@@ -1095,10 +1118,11 @@
             if (DEBUG) {
                 Slog.w(TAG, "Full-data restore target timed out; shutting down");
             }
-
-            mMonitor = BackupManagerMonitorUtils.monitorEvent(mMonitor,
+            Bundle monitoringExtras = addRestoreOperationTypeToEvent(/*extras*/null);
+            mBackupManagerMonitorEventSender.monitorEvent(
                     BackupManagerMonitor.LOG_EVENT_ID_FULL_RESTORE_TIMEOUT,
-                    mCurrentPackage, BackupManagerMonitor.LOG_EVENT_CATEGORY_AGENT, null);
+                    mCurrentPackage, BackupManagerMonitor.LOG_EVENT_CATEGORY_AGENT,
+                    monitoringExtras);
             mEngineThread.handleTimeout();
 
             IoUtils.closeQuietly(mEnginePipes[1]);
@@ -1322,7 +1346,7 @@
 
                 // Ask the agent for logs after doRestoreFinished() has completed executing to allow
                 // it to finalize its logs.
-                BackupManagerMonitorUtils.monitorAgentLoggingResults(mMonitor, mCurrentPackage,
+                mBackupManagerMonitorEventSender.monitorAgentLoggingResults(mCurrentPackage,
                         mAgent);
 
                 // Just go back to running the restore queue
@@ -1358,9 +1382,10 @@
     public void handleCancel(boolean cancelAll) {
         mOperationStorage.removeOperation(mEphemeralOpToken);
         Slog.e(TAG, "Timeout restoring application " + mCurrentPackage.packageName);
-        mMonitor = BackupManagerMonitorUtils.monitorEvent(mMonitor,
+        Bundle monitoringExtras = addRestoreOperationTypeToEvent(/*extras*/null);
+        mBackupManagerMonitorEventSender.monitorEvent(
                 BackupManagerMonitor.LOG_EVENT_ID_KEY_VALUE_RESTORE_TIMEOUT,
-                mCurrentPackage, BackupManagerMonitor.LOG_EVENT_CATEGORY_AGENT, null);
+                mCurrentPackage, BackupManagerMonitor.LOG_EVENT_CATEGORY_AGENT, monitoringExtras);
         EventLog.writeEvent(EventLogTags.RESTORE_AGENT_FAILURE,
                 mCurrentPackage.packageName, "restore timeout");
         // Handle like an agent that threw on invocation: wipe it and go on to the next
@@ -1433,4 +1458,10 @@
             }
         }
     }
+
+    private Bundle addRestoreOperationTypeToEvent (@Nullable Bundle extra) {
+        return mBackupManagerMonitorEventSender.putMonitoringExtra(
+                extra,
+                BackupManagerMonitor.EXTRA_LOG_OPERATION_TYPE, RESTORE);
+    }
 }
diff --git a/services/backup/java/com/android/server/backup/utils/BackupManagerMonitorDumpsysUtils.java b/services/backup/java/com/android/server/backup/utils/BackupManagerMonitorDumpsysUtils.java
new file mode 100644
index 0000000..0b55ca2
--- /dev/null
+++ b/services/backup/java/com/android/server/backup/utils/BackupManagerMonitorDumpsysUtils.java
@@ -0,0 +1,260 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.backup.utils;
+
+import android.app.backup.BackupAnnotations;
+import android.app.backup.BackupManagerMonitor;
+import android.app.backup.BackupRestoreEventLogger;
+import android.os.Bundle;
+import android.os.Environment;
+import android.util.Slog;
+
+import com.android.internal.util.FastPrintWriter;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.PrintWriter;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.Map;
+
+
+/*
+ * Util class to parse a BMM event and write it to a text file, to be the printed in
+ * the backup dumpsys
+ *
+ * Note: this class is note thread safe
+ */
+public class BackupManagerMonitorDumpsysUtils {
+
+    private static final String TAG = "BackupManagerMonitorDumpsysUtils";
+    // Name of the subdirectory where the text file containing the BMM events will be stored.
+    // Same as {@link UserBackupManagerFiles}
+    private static final String BACKUP_PERSISTENT_DIR = "backup";
+
+    /**
+     * Parses the BackupManagerMonitor bundle for a RESTORE event in a series of strings that
+     * will be persisted in a text file and printed in the dumpsys.
+     *
+     * If the evenntBundle passed is not a RESTORE event, return early
+     *
+     * Key information related to the event:
+     * - Timestamp (HAS TO ALWAYS BE THE FIRST LINE OF EACH EVENT)
+     * - Event ID
+     * - Event Category
+     * - Operation type
+     * - Package name (can be null)
+     * - Agent logs (if available)
+     *
+     * Example of formatting:
+     * RESTORE Event: [2023-08-18 17:16:00.735] Agent - Agent logging results
+     *     Package name: com.android.wallpaperbackup
+     *     Agent Logs:
+     *         Data Type: wlp_img_system
+     *             Item restored: 0/1
+     *             Agent Error - Category: no_wallpaper, Count: 1
+     *         Data Type: wlp_img_lock
+     *             Item restored: 0/1
+     *             Agent Error - Category: no_wallpaper, Count: 1
+     */
+    public void parseBackupManagerMonitorRestoreEventForDumpsys(Bundle eventBundle) {
+        if (eventBundle == null) {
+            return;
+        }
+
+        if (!isOpTypeRestore(eventBundle)) {
+            //We only log Restore events
+            return;
+        }
+
+        if (!eventBundle.containsKey(BackupManagerMonitor.EXTRA_LOG_EVENT_ID)
+                || !eventBundle.containsKey(BackupManagerMonitor.EXTRA_LOG_EVENT_CATEGORY)) {
+            Slog.w(TAG, "Event id and category are not optional fields.");
+            return;
+        }
+        File bmmEvents = getBMMEventsFile();
+
+        try (FileOutputStream out = new FileOutputStream(bmmEvents, /*append*/ true);
+            PrintWriter pw = new FastPrintWriter(out);) {
+
+            int eventCategory = eventBundle.getInt(BackupManagerMonitor.EXTRA_LOG_EVENT_CATEGORY);
+            int eventId = eventBundle.getInt(BackupManagerMonitor.EXTRA_LOG_EVENT_ID);
+
+            if (eventId == BackupManagerMonitor.LOG_EVENT_ID_AGENT_LOGGING_RESULTS &&
+                    !hasAgentLogging(eventBundle)) {
+                // Do not record an empty agent logging event
+                return;
+            }
+
+            pw.println("RESTORE Event: [" + timestamp() + "] " +
+                    getCategory(eventCategory) + " - " +
+                    getId(eventId));
+
+            if (eventBundle.containsKey(BackupManagerMonitor.EXTRA_LOG_EVENT_PACKAGE_NAME)) {
+                pw.println("\tPackage name: "
+                        + eventBundle.getString(BackupManagerMonitor.EXTRA_LOG_EVENT_PACKAGE_NAME));
+            }
+
+            // TODO(b/296818666): add extras to the events
+            addAgentLogsIfAvailable(eventBundle, pw);
+        } catch (java.io.IOException e) {
+            Slog.e(TAG, "IO Exception when writing BMM events to file: " + e);
+        }
+
+    }
+
+    private boolean hasAgentLogging(Bundle eventBundle) {
+        if (eventBundle.containsKey(BackupManagerMonitor.EXTRA_LOG_AGENT_LOGGING_RESULTS)) {
+            ArrayList<BackupRestoreEventLogger.DataTypeResult> agentLogs =
+                    eventBundle.getParcelableArrayList(
+                            BackupManagerMonitor.EXTRA_LOG_AGENT_LOGGING_RESULTS);
+
+            return !agentLogs.isEmpty();
+        }
+        return false;
+    }
+
+    /**
+     * Extracts agent logs from the BackupManagerMonitor event. These logs detail:
+     * - the data type for the agent
+     * - the count of successfully restored items
+     * - the count of items that failed to restore
+     * - the metadata associated with this datatype
+     * - any errors
+     */
+    private void addAgentLogsIfAvailable(Bundle eventBundle, PrintWriter pw) {
+        if (hasAgentLogging(eventBundle)) {
+            pw.println("\tAgent Logs:");
+            ArrayList<BackupRestoreEventLogger.DataTypeResult> agentLogs =
+                    eventBundle.getParcelableArrayList(
+                            BackupManagerMonitor.EXTRA_LOG_AGENT_LOGGING_RESULTS);
+            for (BackupRestoreEventLogger.DataTypeResult result : agentLogs) {
+                int totalItems = result.getFailCount() + result.getSuccessCount();
+                pw.println("\t\tData Type: " + result.getDataType());
+                pw.println("\t\t\tItem restored: " + result.getSuccessCount() + "/" +
+                        totalItems);
+                for (Map.Entry<String, Integer> entry : result.getErrors().entrySet()) {
+                    pw.println("\t\t\tAgent Error - Category: " +
+                            entry.getKey() + ", Count: " + entry.getValue());
+                }
+            }
+        }
+    }
+
+    /*
+     * Get the path of the text files which stores the BMM events
+     */
+    public File getBMMEventsFile() {
+        File dataDir = new File(Environment.getDataDirectory(), BACKUP_PERSISTENT_DIR);
+        File fname = new File(dataDir, "bmmevents.txt");
+        return fname;
+    }
+
+    private String timestamp() {
+        long currentTime = System.currentTimeMillis();
+        Date date = new Date(currentTime);
+        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
+        return dateFormat.format(date);
+    }
+
+    private String getCategory(int code) {
+        String category = switch (code) {
+            case BackupManagerMonitor.LOG_EVENT_CATEGORY_TRANSPORT -> "Transport";
+            case BackupManagerMonitor.LOG_EVENT_CATEGORY_AGENT -> "Agent";
+            case BackupManagerMonitor.LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY ->
+                    "Backup Manager Policy";
+            default -> "Unknown category code: " + code;
+        };
+        return category;
+    }
+
+    private String getId(int code) {
+        String id = switch (code) {
+            case BackupManagerMonitor.LOG_EVENT_ID_FULL_BACKUP_CANCEL -> "Full backup cancel";
+            case BackupManagerMonitor.LOG_EVENT_ID_ILLEGAL_KEY -> "Illegal key";
+            case BackupManagerMonitor.LOG_EVENT_ID_NO_DATA_TO_SEND -> "No data to send";
+            case BackupManagerMonitor.LOG_EVENT_ID_PACKAGE_INELIGIBLE -> "Package ineligible";
+            case BackupManagerMonitor.LOG_EVENT_ID_PACKAGE_KEY_VALUE_PARTICIPANT ->
+                    "Package key-value participant";
+            case BackupManagerMonitor.LOG_EVENT_ID_PACKAGE_STOPPED -> "Package stopped";
+            case BackupManagerMonitor.LOG_EVENT_ID_PACKAGE_NOT_FOUND -> "Package not found";
+            case BackupManagerMonitor.LOG_EVENT_ID_BACKUP_DISABLED -> "Backup disabled";
+            case BackupManagerMonitor.LOG_EVENT_ID_DEVICE_NOT_PROVISIONED ->
+                    "Device not provisioned";
+            case BackupManagerMonitor.LOG_EVENT_ID_PACKAGE_TRANSPORT_NOT_PRESENT ->
+                    "Package transport not present";
+            case BackupManagerMonitor.LOG_EVENT_ID_ERROR_PREFLIGHT -> "Error preflight";
+            case BackupManagerMonitor.LOG_EVENT_ID_QUOTA_HIT_PREFLIGHT -> "Quota hit preflight";
+            case BackupManagerMonitor.LOG_EVENT_ID_EXCEPTION_FULL_BACKUP -> "Exception full backup";
+            case BackupManagerMonitor.LOG_EVENT_ID_KEY_VALUE_BACKUP_CANCEL ->
+                    "Key-value backup cancel";
+            case BackupManagerMonitor.LOG_EVENT_ID_NO_RESTORE_METADATA_AVAILABLE ->
+                    "No restore metadata available";
+            case BackupManagerMonitor.LOG_EVENT_ID_NO_PM_METADATA_RECEIVED ->
+                    "No PM metadata received";
+            case BackupManagerMonitor.LOG_EVENT_ID_PM_AGENT_HAS_NO_METADATA ->
+                    "PM agent has no metadata";
+            case BackupManagerMonitor.LOG_EVENT_ID_LOST_TRANSPORT -> "Lost transport";
+            case BackupManagerMonitor.LOG_EVENT_ID_PACKAGE_NOT_PRESENT -> "Package not present";
+            case BackupManagerMonitor.LOG_EVENT_ID_RESTORE_VERSION_HIGHER ->
+                    "Restore version higher";
+            case BackupManagerMonitor.LOG_EVENT_ID_APP_HAS_NO_AGENT -> "App has no agent";
+            case BackupManagerMonitor.LOG_EVENT_ID_SIGNATURE_MISMATCH -> "Signature mismatch";
+            case BackupManagerMonitor.LOG_EVENT_ID_CANT_FIND_AGENT -> "Can't find agent";
+            case BackupManagerMonitor.LOG_EVENT_ID_KEY_VALUE_RESTORE_TIMEOUT ->
+                    "Key-value restore timeout";
+            case BackupManagerMonitor.LOG_EVENT_ID_RESTORE_ANY_VERSION -> "Restore any version";
+            case BackupManagerMonitor.LOG_EVENT_ID_VERSIONS_MATCH -> "Versions match";
+            case BackupManagerMonitor.LOG_EVENT_ID_VERSION_OF_BACKUP_OLDER ->
+                    "Version of backup older";
+            case BackupManagerMonitor.LOG_EVENT_ID_FULL_RESTORE_SIGNATURE_MISMATCH ->
+                    "Full restore signature mismatch";
+            case BackupManagerMonitor.LOG_EVENT_ID_SYSTEM_APP_NO_AGENT -> "System app no agent";
+            case BackupManagerMonitor.LOG_EVENT_ID_FULL_RESTORE_ALLOW_BACKUP_FALSE ->
+                    "Full restore allow backup false";
+            case BackupManagerMonitor.LOG_EVENT_ID_APK_NOT_INSTALLED -> "APK not installed";
+            case BackupManagerMonitor.LOG_EVENT_ID_CANNOT_RESTORE_WITHOUT_APK ->
+                    "Cannot restore without APK";
+            case BackupManagerMonitor.LOG_EVENT_ID_MISSING_SIGNATURE -> "Missing signature";
+            case BackupManagerMonitor.LOG_EVENT_ID_EXPECTED_DIFFERENT_PACKAGE ->
+                    "Expected different package";
+            case BackupManagerMonitor.LOG_EVENT_ID_UNKNOWN_VERSION -> "Unknown version";
+            case BackupManagerMonitor.LOG_EVENT_ID_FULL_RESTORE_TIMEOUT -> "Full restore timeout";
+            case BackupManagerMonitor.LOG_EVENT_ID_CORRUPT_MANIFEST -> "Corrupt manifest";
+            case BackupManagerMonitor.LOG_EVENT_ID_WIDGET_METADATA_MISMATCH ->
+                    "Widget metadata mismatch";
+            case BackupManagerMonitor.LOG_EVENT_ID_WIDGET_UNKNOWN_VERSION ->
+                    "Widget unknown version";
+            case BackupManagerMonitor.LOG_EVENT_ID_NO_PACKAGES -> "No packages";
+            case BackupManagerMonitor.LOG_EVENT_ID_TRANSPORT_IS_NULL -> "Transport is null";
+            case BackupManagerMonitor.LOG_EVENT_ID_TRANSPORT_NON_INCREMENTAL_BACKUP_REQUIRED ->
+                    "Transport non-incremental backup required";
+            case BackupManagerMonitor.LOG_EVENT_ID_AGENT_LOGGING_RESULTS -> "Agent logging results";
+            default -> "Unknown log event ID: " + code;
+        };
+        return id;
+    }
+
+    private boolean isOpTypeRestore(Bundle eventBundle) {
+        return switch (eventBundle.getInt(
+                BackupManagerMonitor.EXTRA_LOG_OPERATION_TYPE, -1)) {
+            case BackupAnnotations.OperationType.RESTORE -> true;
+            default -> false;
+        };
+    }
+}
diff --git a/services/backup/java/com/android/server/backup/utils/BackupManagerMonitorUtils.java b/services/backup/java/com/android/server/backup/utils/BackupManagerMonitorEventSender.java
similarity index 69%
rename from services/backup/java/com/android/server/backup/utils/BackupManagerMonitorUtils.java
rename to services/backup/java/com/android/server/backup/utils/BackupManagerMonitorEventSender.java
index 439b836..92e3107 100644
--- a/services/backup/java/com/android/server/backup/utils/BackupManagerMonitorUtils.java
+++ b/services/backup/java/com/android/server/backup/utils/BackupManagerMonitorEventSender.java
@@ -25,7 +25,6 @@
 import static com.android.server.backup.BackupManagerService.DEBUG;
 import static com.android.server.backup.BackupManagerService.TAG;
 
-import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.IBackupAgent;
 import android.app.backup.BackupAnnotations.OperationType;
@@ -37,6 +36,7 @@
 import android.os.RemoteException;
 import android.util.Slog;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.infra.AndroidFuture;
 
 import java.util.List;
@@ -44,9 +44,9 @@
 import java.util.concurrent.TimeoutException;
 
 /**
- * Utility methods to communicate with BackupManagerMonitor.
+ * Utility methods to log BackupManagerMonitor events.
  */
-public class BackupManagerMonitorUtils {
+public class BackupManagerMonitorEventSender {
     /**
      * Timeout for how long we wait before we give up on getting logs from a {@link IBackupAgent}.
      * We expect this to be very fast since the agent immediately returns whatever logs have been
@@ -54,51 +54,77 @@
      * for non-essential logs.
      */
     private static final int AGENT_LOGGER_RESULTS_TIMEOUT_MILLIS = 500;
+    @Nullable private IBackupManagerMonitor mMonitor;
+    private final BackupManagerMonitorDumpsysUtils mBackupManagerMonitorDumpsysUtils;
+    public BackupManagerMonitorEventSender(@Nullable IBackupManagerMonitor monitor) {
+        mMonitor = monitor;
+        mBackupManagerMonitorDumpsysUtils = new BackupManagerMonitorDumpsysUtils();
+    }
+
+    @VisibleForTesting
+    BackupManagerMonitorEventSender(@Nullable IBackupManagerMonitor monitor,
+            BackupManagerMonitorDumpsysUtils backupManagerMonitorDumpsysUtils) {
+        mMonitor = monitor;
+        mBackupManagerMonitorDumpsysUtils = backupManagerMonitorDumpsysUtils;
+    }
+
+    public void setMonitor(IBackupManagerMonitor monitor) {
+        mMonitor = monitor;
+    }
+
+    public IBackupManagerMonitor getMonitor() {
+        return mMonitor;
+    }
 
     /**
      * Notifies monitor about the event.
      *
      * Calls {@link IBackupManagerMonitor#onEvent(Bundle)} with a bundle representing current event.
      *
-     * @param monitor - implementation of {@link IBackupManagerMonitor} to notify.
      * @param id - event id.
      * @param pkg - package event is related to.
      * @param category - event category.
      * @param extras - additional event data.
-     * @return <code>monitor</code> if call succeeded and <code>null</code> otherwise.
      */
-    @Nullable
-    public static IBackupManagerMonitor monitorEvent(
-            @Nullable IBackupManagerMonitor monitor,
+    public void monitorEvent(
             int id,
             PackageInfo pkg,
             int category,
             Bundle extras) {
-        if (monitor != null) {
-            try {
-                Bundle bundle = new Bundle();
-                bundle.putInt(BackupManagerMonitor.EXTRA_LOG_EVENT_ID, id);
-                bundle.putInt(BackupManagerMonitor.EXTRA_LOG_EVENT_CATEGORY, category);
-                if (pkg != null) {
-                    bundle.putString(EXTRA_LOG_EVENT_PACKAGE_NAME,
-                            pkg.packageName);
-                    bundle.putInt(BackupManagerMonitor.EXTRA_LOG_EVENT_PACKAGE_VERSION,
-                            pkg.versionCode);
-                    bundle.putLong(BackupManagerMonitor.EXTRA_LOG_EVENT_PACKAGE_LONG_VERSION,
-                            pkg.getLongVersionCode());
-                }
-                if (extras != null) {
-                    bundle.putAll(extras);
-                }
-                monitor.onEvent(bundle);
-                return monitor;
-            } catch (RemoteException e) {
-                if (DEBUG) {
-                    Slog.w(TAG, "backup manager monitor went away");
+        try {
+            Bundle bundle = new Bundle();
+            bundle.putInt(BackupManagerMonitor.EXTRA_LOG_EVENT_ID, id);
+            bundle.putInt(BackupManagerMonitor.EXTRA_LOG_EVENT_CATEGORY, category);
+            if (pkg != null) {
+                bundle.putString(EXTRA_LOG_EVENT_PACKAGE_NAME,
+                        pkg.packageName);
+                bundle.putInt(BackupManagerMonitor.EXTRA_LOG_EVENT_PACKAGE_VERSION,
+                        pkg.versionCode);
+                bundle.putLong(BackupManagerMonitor.EXTRA_LOG_EVENT_PACKAGE_LONG_VERSION,
+                        pkg.getLongVersionCode());
+            }
+            if (extras != null) {
+                bundle.putAll(extras);
+                if (extras.containsKey(EXTRA_LOG_OPERATION_TYPE) &&
+                        extras.getInt(EXTRA_LOG_OPERATION_TYPE) == OperationType.RESTORE){
+                    mBackupManagerMonitorDumpsysUtils
+                            .parseBackupManagerMonitorRestoreEventForDumpsys(bundle);
                 }
             }
+
+            if (mMonitor != null) {
+                mMonitor.onEvent(bundle);
+            } else {
+                if (DEBUG) {
+                    Slog.w(TAG, "backup manager monitor is null unable to send event");
+                }
+            }
+        } catch (RemoteException e) {
+            mMonitor = null;
+            if (DEBUG) {
+                Slog.w(TAG, "backup manager monitor went away");
+            }
         }
-        return null;
     }
 
     /**
@@ -108,17 +134,12 @@
      * <p>Note that this method does two separate binder calls (one to the agent and one to the
      * monitor).
      *
-     * @param monitor - implementation of {@link IBackupManagerMonitor} to notify.
      * @param pkg - package the {@code agent} belongs to.
      * @param agent - the {@link IBackupAgent} to retrieve logs from.
-     * @return {@code null} if the monitor is null. {@code monitor} if we fail to retrieve the logs
-     *     from the {@code agent}. Otherwise, the result of {@link
-     *     #monitorEvent(IBackupManagerMonitor, int, PackageInfo, int, Bundle)}.
      */
-    public static IBackupManagerMonitor monitorAgentLoggingResults(
-            @Nullable IBackupManagerMonitor monitor, PackageInfo pkg, IBackupAgent agent) {
-        if (monitor == null) {
-            return null;
+    public void monitorAgentLoggingResults(PackageInfo pkg, IBackupAgent agent) {
+        if (mMonitor == null) {
+            Slog.i(TAG, "backup manager monitor is null unable to send event"+pkg);
         }
 
         try {
@@ -127,7 +148,7 @@
             AndroidFuture<Integer> operationTypeFuture = new AndroidFuture<>();
             agent.getLoggerResults(resultsFuture);
             agent.getOperationType(operationTypeFuture);
-            return sendAgentLoggingResults(monitor, pkg,
+            sendAgentLoggingResults(pkg,
                     resultsFuture.get(AGENT_LOGGER_RESULTS_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS),
                     operationTypeFuture.get(AGENT_LOGGER_RESULTS_TIMEOUT_MILLIS,
                             TimeUnit.MILLISECONDS));
@@ -136,18 +157,15 @@
         } catch (Exception e) {
             Slog.w(TAG, "Failed to retrieve logging results from agent", e);
         }
-        return monitor;
     }
 
-    public static IBackupManagerMonitor sendAgentLoggingResults(
-            @NonNull IBackupManagerMonitor monitor, PackageInfo pkg, List<DataTypeResult> results,
+    public void sendAgentLoggingResults(PackageInfo pkg, List<DataTypeResult> results,
             @OperationType int operationType) {
         Bundle loggerResultsBundle = new Bundle();
         loggerResultsBundle.putParcelableList(
                 EXTRA_LOG_AGENT_LOGGING_RESULTS, results);
         loggerResultsBundle.putInt(EXTRA_LOG_OPERATION_TYPE, operationType);
-        return monitorEvent(
-                monitor,
+        monitorEvent(
                 LOG_EVENT_ID_AGENT_LOGGING_RESULTS,
                 pkg,
                 LOG_EVENT_CATEGORY_AGENT,
diff --git a/services/backup/java/com/android/server/backup/utils/TarBackupReader.java b/services/backup/java/com/android/server/backup/utils/TarBackupReader.java
index 71ca8ca..78a9952 100644
--- a/services/backup/java/com/android/server/backup/utils/TarBackupReader.java
+++ b/services/backup/java/com/android/server/backup/utils/TarBackupReader.java
@@ -85,7 +85,8 @@
     private final InputStream mInputStream;
     private final BytesReadListener mBytesReadListener;
 
-    private IBackupManagerMonitor mMonitor;
+
+    private BackupManagerMonitorEventSender mBackupManagerMonitorEventSender;
 
     // Widget blob to be restored out-of-band.
     private byte[] mWidgetData = null;
@@ -94,7 +95,7 @@
             IBackupManagerMonitor monitor) {
         mInputStream = inputStream;
         mBytesReadListener = bytesReadListener;
-        mMonitor = monitor;
+        mBackupManagerMonitorEventSender = new BackupManagerMonitorEventSender(monitor);
     }
 
     /**
@@ -323,24 +324,22 @@
                         return sigs;
                     } else {
                         Slog.i(TAG, "Missing signature on backed-up package " + info.packageName);
-                        mMonitor = BackupManagerMonitorUtils.monitorEvent(
-                                mMonitor,
+                        mBackupManagerMonitorEventSender.monitorEvent(
                                 LOG_EVENT_ID_MISSING_SIGNATURE,
                                 null,
                                 LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
-                                BackupManagerMonitorUtils.putMonitoringExtra(null,
+                                mBackupManagerMonitorEventSender.putMonitoringExtra(null,
                                         EXTRA_LOG_EVENT_PACKAGE_NAME, info.packageName));
                     }
                 } else {
                     Slog.i(TAG, "Expected package " + info.packageName
                             + " but restore manifest claims " + manifestPackage);
-                    Bundle monitoringExtras = BackupManagerMonitorUtils.putMonitoringExtra(null,
-                            EXTRA_LOG_EVENT_PACKAGE_NAME, info.packageName);
-                    monitoringExtras = BackupManagerMonitorUtils.putMonitoringExtra(
+                    Bundle monitoringExtras = mBackupManagerMonitorEventSender.putMonitoringExtra(
+                            null, EXTRA_LOG_EVENT_PACKAGE_NAME, info.packageName);
+                    monitoringExtras = mBackupManagerMonitorEventSender.putMonitoringExtra(
                             monitoringExtras,
                             EXTRA_LOG_MANIFEST_PACKAGE_NAME, manifestPackage);
-                    mMonitor = BackupManagerMonitorUtils.monitorEvent(
-                            mMonitor,
+                    mBackupManagerMonitorEventSender.monitorEvent(
                             LOG_EVENT_ID_EXPECTED_DIFFERENT_PACKAGE,
                             null,
                             LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
@@ -349,12 +348,11 @@
             } else {
                 Slog.i(TAG, "Unknown restore manifest version " + version
                         + " for package " + info.packageName);
-                Bundle monitoringExtras = BackupManagerMonitorUtils.putMonitoringExtra(null,
-                        EXTRA_LOG_EVENT_PACKAGE_NAME, info.packageName);
-                monitoringExtras = BackupManagerMonitorUtils.putMonitoringExtra(monitoringExtras,
-                        EXTRA_LOG_EVENT_PACKAGE_VERSION, version);
-                mMonitor = BackupManagerMonitorUtils.monitorEvent(
-                        mMonitor,
+                Bundle monitoringExtras = mBackupManagerMonitorEventSender.putMonitoringExtra(
+                        null, EXTRA_LOG_EVENT_PACKAGE_NAME, info.packageName);
+                monitoringExtras = mBackupManagerMonitorEventSender.putMonitoringExtra(
+                        monitoringExtras, EXTRA_LOG_EVENT_PACKAGE_VERSION, version);
+                mBackupManagerMonitorEventSender.monitorEvent(
                         BackupManagerMonitor.LOG_EVENT_ID_UNKNOWN_VERSION,
                         null,
                         LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
@@ -363,12 +361,12 @@
             }
         } catch (NumberFormatException e) {
             Slog.w(TAG, "Corrupt restore manifest for package " + info.packageName);
-            mMonitor = BackupManagerMonitorUtils.monitorEvent(
-                    mMonitor,
+            mBackupManagerMonitorEventSender.monitorEvent(
                     BackupManagerMonitor.LOG_EVENT_ID_CORRUPT_MANIFEST,
                     null,
                     LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
-                    BackupManagerMonitorUtils.putMonitoringExtra(null, EXTRA_LOG_EVENT_PACKAGE_NAME,
+                    mBackupManagerMonitorEventSender.putMonitoringExtra(null,
+                            EXTRA_LOG_EVENT_PACKAGE_NAME,
                             info.packageName));
         } catch (IllegalArgumentException e) {
             Slog.w(TAG, e.getMessage());
@@ -436,8 +434,7 @@
                         if ((pkgInfo.applicationInfo.flags
                                 & ApplicationInfo.FLAG_RESTORE_ANY_VERSION) != 0) {
                             Slog.i(TAG, "Package has restoreAnyVersion; taking data");
-                            mMonitor = BackupManagerMonitorUtils.monitorEvent(
-                                    mMonitor,
+                            mBackupManagerMonitorEventSender.monitorEvent(
                                     LOG_EVENT_ID_RESTORE_ANY_VERSION,
                                     pkgInfo,
                                     LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
@@ -446,8 +443,7 @@
                         } else if (pkgInfo.getLongVersionCode() >= info.version) {
                             Slog.i(TAG, "Sig + version match; taking data");
                             policy = RestorePolicy.ACCEPT;
-                            mMonitor = BackupManagerMonitorUtils.monitorEvent(
-                                    mMonitor,
+                            mBackupManagerMonitorEventSender.monitorEvent(
                                     LOG_EVENT_ID_VERSIONS_MATCH,
                                     pkgInfo,
                                     LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
@@ -466,12 +462,11 @@
                             } else {
                                 Slog.i(TAG, "Data requires newer version "
                                         + info.version + "; ignoring");
-                                mMonitor = BackupManagerMonitorUtils
-                                        .monitorEvent(mMonitor,
+                                mBackupManagerMonitorEventSender.monitorEvent(
                                                 LOG_EVENT_ID_VERSION_OF_BACKUP_OLDER,
                                                 pkgInfo,
                                                 LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
-                                                BackupManagerMonitorUtils
+                                                mBackupManagerMonitorEventSender
                                                         .putMonitoringExtra(
                                                                 null,
                                                                 EXTRA_LOG_OLD_VERSION,
@@ -484,8 +479,7 @@
                         Slog.w(TAG, "Restore manifest signatures do not match "
                                 + "installed application for "
                                 + info.packageName);
-                        mMonitor = BackupManagerMonitorUtils.monitorEvent(
-                                mMonitor,
+                        mBackupManagerMonitorEventSender.monitorEvent(
                                 LOG_EVENT_ID_FULL_RESTORE_SIGNATURE_MISMATCH,
                                 pkgInfo,
                                 LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
@@ -494,8 +488,7 @@
                 } else {
                     Slog.w(TAG, "Package " + info.packageName
                             + " is system level with no agent");
-                    mMonitor = BackupManagerMonitorUtils.monitorEvent(
-                            mMonitor,
+                    mBackupManagerMonitorEventSender.monitorEvent(
                             LOG_EVENT_ID_SYSTEM_APP_NO_AGENT,
                             pkgInfo,
                             LOG_EVENT_CATEGORY_AGENT,
@@ -506,8 +499,7 @@
                     Slog.i(TAG,
                             "Restore manifest from " + info.packageName + " but allowBackup=false");
                 }
-                mMonitor = BackupManagerMonitorUtils.monitorEvent(
-                        mMonitor,
+                mBackupManagerMonitorEventSender.monitorEvent(
                         LOG_EVENT_ID_FULL_RESTORE_ALLOW_BACKUP_FALSE,
                         pkgInfo,
                         LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
@@ -526,14 +518,13 @@
             } else {
                 policy = RestorePolicy.IGNORE;
             }
-            Bundle monitoringExtras = BackupManagerMonitorUtils.putMonitoringExtra(
+            Bundle monitoringExtras = mBackupManagerMonitorEventSender.putMonitoringExtra(
                     null,
                     EXTRA_LOG_EVENT_PACKAGE_NAME, info.packageName);
-            monitoringExtras = BackupManagerMonitorUtils.putMonitoringExtra(
+            monitoringExtras = mBackupManagerMonitorEventSender.putMonitoringExtra(
                     monitoringExtras,
                     EXTRA_LOG_POLICY_ALLOW_APKS, allowApks);
-            mMonitor = BackupManagerMonitorUtils.monitorEvent(
-                    mMonitor,
+            mBackupManagerMonitorEventSender.monitorEvent(
                     LOG_EVENT_ID_APK_NOT_INSTALLED,
                     null,
                     LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
@@ -543,12 +534,11 @@
         if (policy == RestorePolicy.ACCEPT_IF_APK && !info.hasApk) {
             Slog.i(TAG, "Cannot restore package " + info.packageName
                     + " without the matching .apk");
-            mMonitor = BackupManagerMonitorUtils.monitorEvent(
-                    mMonitor,
+            mBackupManagerMonitorEventSender.monitorEvent(
                     LOG_EVENT_ID_CANNOT_RESTORE_WITHOUT_APK,
                     null,
                     LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
-                    BackupManagerMonitorUtils.putMonitoringExtra(null,
+                    mBackupManagerMonitorEventSender.putMonitoringExtra(null,
                             EXTRA_LOG_EVENT_PACKAGE_NAME, info.packageName));
         }
 
@@ -632,12 +622,11 @@
                         "Metadata mismatch: package " + info.packageName + " but widget data for "
                                 + pkg);
 
-                Bundle monitoringExtras = BackupManagerMonitorUtils.putMonitoringExtra(null,
+                Bundle monitoringExtras = mBackupManagerMonitorEventSender.putMonitoringExtra(null,
                         EXTRA_LOG_EVENT_PACKAGE_NAME, info.packageName);
-                monitoringExtras = BackupManagerMonitorUtils.putMonitoringExtra(monitoringExtras,
-                        BackupManagerMonitor.EXTRA_LOG_WIDGET_PACKAGE_NAME, pkg);
-                mMonitor = BackupManagerMonitorUtils.monitorEvent(
-                        mMonitor,
+                monitoringExtras = mBackupManagerMonitorEventSender.putMonitoringExtra(
+                        monitoringExtras, BackupManagerMonitor.EXTRA_LOG_WIDGET_PACKAGE_NAME, pkg);
+                mBackupManagerMonitorEventSender.monitorEvent(
                         BackupManagerMonitor.LOG_EVENT_ID_WIDGET_METADATA_MISMATCH,
                         null,
                         LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
@@ -646,13 +635,12 @@
         } else {
             Slog.w(TAG, "Unsupported metadata version " + version);
 
-            Bundle monitoringExtras = BackupManagerMonitorUtils
+            Bundle monitoringExtras = mBackupManagerMonitorEventSender
                     .putMonitoringExtra(null, EXTRA_LOG_EVENT_PACKAGE_NAME,
                             info.packageName);
-            monitoringExtras = BackupManagerMonitorUtils.putMonitoringExtra(monitoringExtras,
+            monitoringExtras = mBackupManagerMonitorEventSender.putMonitoringExtra(monitoringExtras,
                     EXTRA_LOG_EVENT_PACKAGE_VERSION, version);
-            mMonitor = BackupManagerMonitorUtils.monitorEvent(
-                    mMonitor,
+            mBackupManagerMonitorEventSender.monitorEvent(
                     BackupManagerMonitor.LOG_EVENT_ID_WIDGET_UNKNOWN_VERSION,
                     null,
                     LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY,
@@ -810,7 +798,7 @@
     }
 
     public IBackupManagerMonitor getMonitor() {
-        return mMonitor;
+        return mBackupManagerMonitorEventSender.getMonitor();
     }
 
     public byte[] getWidgetData() {
diff --git a/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java b/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java
index f6e9415..5942145 100644
--- a/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java
+++ b/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java
@@ -32,6 +32,7 @@
 import android.os.Message;
 import android.os.UserManager;
 import android.util.Log;
+import android.util.Slog;
 import android.util.SparseArray;
 
 import com.android.server.companion.AssociationStore;
@@ -226,53 +227,40 @@
 
     private void onDevicePresent(@NonNull Set<Integer> presentDevicesForSource,
             int newDeviceAssociationId, @NonNull String sourceLoggingTag) {
-        if (DEBUG) {
-            Log.i(TAG, "onDevice_Present() id=" + newDeviceAssociationId
-                    + ", source=" + sourceLoggingTag);
-            Log.d(TAG, "  > association="
-                    + mAssociationStore.getAssociationById(newDeviceAssociationId));
-        }
+        Slog.i(TAG, "onDevice_Present() id=" + newDeviceAssociationId
+                + ", source=" + sourceLoggingTag);
 
         final boolean alreadyPresent = isDevicePresent(newDeviceAssociationId);
         if (alreadyPresent) {
-            Log.i(TAG, "Device" + "id (" + newDeviceAssociationId + ") already present.");
+            Slog.i(TAG, "Device" + "id (" + newDeviceAssociationId + ") already present.");
         }
 
         final boolean added = presentDevicesForSource.add(newDeviceAssociationId);
         if (!added) {
-            Log.w(TAG, "Association with id "
+            Slog.i(TAG, "Association with id "
                     + newDeviceAssociationId + " is ALREADY reported as "
                     + "present by this source (" + sourceLoggingTag + ")");
         }
 
-        if (alreadyPresent) return;
-
         mCallback.onDeviceAppeared(newDeviceAssociationId);
     }
 
     private void onDeviceGone(@NonNull Set<Integer> presentDevicesForSource,
             int goneDeviceAssociationId, @NonNull String sourceLoggingTag) {
-        if (DEBUG) {
-            Log.i(TAG, "onDevice_Gone() id=" + goneDeviceAssociationId
-                    + ", source=" + sourceLoggingTag);
-            Log.d(TAG, "  > association="
-                    + mAssociationStore.getAssociationById(goneDeviceAssociationId));
-        }
+        Slog.i(TAG, "onDevice_Gone() id=" + goneDeviceAssociationId
+                + ", source=" + sourceLoggingTag);
 
         final boolean removed = presentDevicesForSource.remove(goneDeviceAssociationId);
         if (!removed) {
-            Log.w(TAG, "Association with id " + goneDeviceAssociationId + " was NOT reported "
+            Slog.w(TAG, "Association with id " + goneDeviceAssociationId + " was NOT reported "
                     + "as present by this source (" + sourceLoggingTag + ")");
-
             return;
         }
 
         final boolean stillPresent = isDevicePresent(goneDeviceAssociationId);
+
         if (stillPresent) {
-            if (DEBUG) {
-                Log.i(TAG, "  Device id (" + goneDeviceAssociationId + ") is still present.");
-            }
-            return;
+            Slog.w(TAG, "  Device id (" + goneDeviceAssociationId + ") is still present.");
         }
 
         mCallback.onDeviceDisappeared(goneDeviceAssociationId);
diff --git a/services/core/java/com/android/server/BinaryTransparencyService.java b/services/core/java/com/android/server/BinaryTransparencyService.java
index 8fc30e4..e11c028 100644
--- a/services/core/java/com/android/server/BinaryTransparencyService.java
+++ b/services/core/java/com/android/server/BinaryTransparencyService.java
@@ -80,9 +80,9 @@
 import android.util.apk.ApkSigningBlockUtils;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.modules.expresslog.Histogram;
 import com.android.internal.os.IBinaryTransparencyService;
 import com.android.internal.util.FrameworkStatsLog;
+import com.android.modules.expresslog.Histogram;
 import com.android.server.pm.ApexManager;
 import com.android.server.pm.pkg.AndroidPackage;
 import com.android.server.pm.pkg.AndroidPackageSplit;
@@ -1391,7 +1391,7 @@
         // Check the flag to determine whether biometric property verification is enabled. It's
         // disabled by default.
         if (!DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_BIOMETRICS,
-                KEY_ENABLE_BIOMETRIC_PROPERTY_VERIFICATION, false)) {
+                KEY_ENABLE_BIOMETRIC_PROPERTY_VERIFICATION, true)) {
             if (DEBUG) {
                 Slog.d(TAG, "Do not collect/verify biometric properties. Feature disabled by "
                         + "DeviceConfig");
diff --git a/services/core/java/com/android/server/audio/AdiDeviceState.java b/services/core/java/com/android/server/audio/AdiDeviceState.java
index 247094f..ba43c8d 100644
--- a/services/core/java/com/android/server/audio/AdiDeviceState.java
+++ b/services/core/java/com/android/server/audio/AdiDeviceState.java
@@ -221,7 +221,7 @@
             }
             final AdiDeviceState deviceState = new AdiDeviceState(deviceType,
                     internalDeviceType, fields[1]);
-            deviceState.setHasHeadTracker(Integer.parseInt(fields[2]) == 1);
+            deviceState.setSAEnabled(Integer.parseInt(fields[2]) == 1);
             deviceState.setHasHeadTracker(Integer.parseInt(fields[3]) == 1);
             deviceState.setHeadTrackerEnabled(Integer.parseInt(fields[4]) == 1);
             deviceState.setAudioDeviceCategory(audioDeviceCategory);
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index ceb96ef..646228e 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -7591,6 +7591,10 @@
             throw new IllegalArgumentException("Illegal BluetoothProfile profile for device "
                     + previousDevice + " -> " + newDevice + ". Got: " + profile);
         }
+
+        sDeviceLogger.enqueue(new EventLogger.StringEvent("BlutoothActiveDeviceChanged for "
+                + BluetoothProfile.getProfileName(profile) + ", device update " + previousDevice
+                + " -> " + newDevice));
         AudioDeviceBroker.BtDeviceChangedData data =
                 new AudioDeviceBroker.BtDeviceChangedData(newDevice, previousDevice, info,
                         "AudioService");
@@ -9636,6 +9640,9 @@
                 }
             } else if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) {
                 state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1);
+                sDeviceLogger.enqueue(new EventLogger.StringEvent(
+                        "BluetoothAdapter ACTION_STATE_CHANGED with state " + state));
+
                 if (state == BluetoothAdapter.STATE_OFF ||
                         state == BluetoothAdapter.STATE_TURNING_OFF) {
                     mDeviceBroker.disconnectAllBluetoothProfiles();
diff --git a/services/core/java/com/android/server/audio/BtHelper.java b/services/core/java/com/android/server/audio/BtHelper.java
index a4d26d3..b350363 100644
--- a/services/core/java/com/android/server/audio/BtHelper.java
+++ b/services/core/java/com/android/server/audio/BtHelper.java
@@ -459,6 +459,8 @@
 
     //@GuardedBy("AudioDeviceBroker.mDeviceStateLock")
     /*package*/ synchronized void onBtProfileDisconnected(int profile) {
+        AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
+                "BT profile " + BluetoothProfile.getProfileName(profile) + " disconnected"));
         switch (profile) {
             case BluetoothProfile.A2DP:
                 mA2dp = null;
@@ -487,6 +489,9 @@
 
     @GuardedBy("AudioDeviceBroker.mDeviceStateLock")
     /*package*/ synchronized void onBtProfileConnected(int profile, BluetoothProfile proxy) {
+        AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
+                "BT profile " + BluetoothProfile.getProfileName(profile) + " connected to proxy "
+                + proxy));
         if (profile == BluetoothProfile.HEADSET) {
             onHeadsetProfileConnected((BluetoothHeadset) proxy);
             return;
diff --git a/services/core/java/com/android/server/biometrics/AuthenticationStatsCollector.java b/services/core/java/com/android/server/biometrics/AuthenticationStatsCollector.java
index 97e5c6f..54b34de 100644
--- a/services/core/java/com/android/server/biometrics/AuthenticationStatsCollector.java
+++ b/services/core/java/com/android/server/biometrics/AuthenticationStatsCollector.java
@@ -26,6 +26,7 @@
 import android.hardware.face.FaceManager;
 import android.hardware.fingerprint.FingerprintManager;
 import android.os.UserHandle;
+import android.util.Slog;
 
 import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
@@ -48,12 +49,14 @@
     // Upload the data every 50 attempts (average number of daily authentications).
     private static final int AUTHENTICATION_UPLOAD_INTERVAL = 50;
     // The maximum number of eligible biometric enrollment notification can be sent.
-    private static final int MAXIMUM_ENROLLMENT_NOTIFICATIONS = 2;
+    @VisibleForTesting
+    static final int MAXIMUM_ENROLLMENT_NOTIFICATIONS = 2;
 
     @NonNull private final Context mContext;
 
     private final float mThreshold;
     private final int mModality;
+    private boolean mPersisterInitialized = false;
 
     @NonNull private final Map<Integer, AuthenticationStats> mUserAuthenticationStatsMap;
 
@@ -85,9 +88,15 @@
     }
 
     private void initializeUserAuthenticationStatsMap() {
-        mAuthenticationStatsPersister = new AuthenticationStatsPersister(mContext);
-        for (AuthenticationStats stats : mAuthenticationStatsPersister.getAllFrrStats(mModality)) {
-            mUserAuthenticationStatsMap.put(stats.getUserId(), stats);
+        try {
+            mAuthenticationStatsPersister = new AuthenticationStatsPersister(mContext);
+            for (AuthenticationStats stats :
+                    mAuthenticationStatsPersister.getAllFrrStats(mModality)) {
+                mUserAuthenticationStatsMap.put(stats.getUserId(), stats);
+            }
+            mPersisterInitialized = true;
+        } catch (IllegalStateException e) {
+            Slog.w(TAG, "Failed to initialize AuthenticationStatsPersister.", e);
         }
     }
 
@@ -106,9 +115,15 @@
 
         AuthenticationStats authenticationStats = mUserAuthenticationStatsMap.get(userId);
 
+        if (authenticationStats.getEnrollmentNotifications() >= MAXIMUM_ENROLLMENT_NOTIFICATIONS) {
+            return;
+        }
+
         authenticationStats.authenticate(authenticated);
 
-        persistDataIfNeeded(userId);
+        if (mPersisterInitialized) {
+            persistDataIfNeeded(userId);
+        }
         sendNotificationIfNeeded(userId);
     }
 
@@ -166,11 +181,13 @@
     }
 
     private void onUserRemoved(final int userId) {
-        if (mAuthenticationStatsPersister == null) {
+        if (!mPersisterInitialized) {
             initializeUserAuthenticationStatsMap();
         }
-        mUserAuthenticationStatsMap.remove(userId);
-        mAuthenticationStatsPersister.removeFrrStats(userId);
+        if (mPersisterInitialized) {
+            mUserAuthenticationStatsMap.remove(userId);
+            mAuthenticationStatsPersister.removeFrrStats(userId);
+        }
     }
 
     /**
diff --git a/services/core/java/com/android/server/biometrics/AuthenticationStatsPersister.java b/services/core/java/com/android/server/biometrics/AuthenticationStatsPersister.java
index 21e93a8..74e1410 100644
--- a/services/core/java/com/android/server/biometrics/AuthenticationStatsPersister.java
+++ b/services/core/java/com/android/server/biometrics/AuthenticationStatsPersister.java
@@ -21,6 +21,7 @@
 import android.content.SharedPreferences;
 import android.hardware.biometrics.BiometricsProtoEnums;
 import android.os.Environment;
+import android.os.UserHandle;
 import android.util.Slog;
 
 import org.json.JSONException;
@@ -72,14 +73,16 @@
                 JSONObject frrStatsJson = new JSONObject(frrStats);
                 if (modality == BiometricsProtoEnums.MODALITY_FACE) {
                     authenticationStatsList.add(new AuthenticationStats(
-                            getIntValue(frrStatsJson, USER_ID, -1 /* defaultValue */),
+                            getIntValue(frrStatsJson, USER_ID,
+                                    UserHandle.USER_NULL /* defaultValue */),
                             getIntValue(frrStatsJson, FACE_ATTEMPTS),
                             getIntValue(frrStatsJson, FACE_REJECTIONS),
                             getIntValue(frrStatsJson, ENROLLMENT_NOTIFICATIONS),
                             modality));
                 } else if (modality == BiometricsProtoEnums.MODALITY_FINGERPRINT) {
                     authenticationStatsList.add(new AuthenticationStats(
-                            getIntValue(frrStatsJson, USER_ID, -1 /* defaultValue */),
+                            getIntValue(frrStatsJson, USER_ID,
+                                    UserHandle.USER_NULL /* defaultValue */),
                             getIntValue(frrStatsJson, FINGERPRINT_ATTEMPTS),
                             getIntValue(frrStatsJson, FINGERPRINT_REJECTIONS),
                             getIntValue(frrStatsJson, ENROLLMENT_NOTIFICATIONS),
@@ -138,13 +141,11 @@
 
             // If there's existing frr stats in the file, we want to update the stats for the given
             // modality and keep the stats for other modalities.
-            if (frrStatJson != null) {
-                frrStatsSet.add(buildFrrStats(frrStatJson, totalAttempts, rejectedAttempts,
-                        enrollmentNotifications, modality));
-            } else {
-                frrStatsSet.add(buildFrrStats(userId, totalAttempts, rejectedAttempts,
-                        enrollmentNotifications, modality));
+            if (frrStatJson == null) {
+                frrStatJson = new JSONObject().put(USER_ID, userId);
             }
+            frrStatsSet.add(buildFrrStats(frrStatJson, totalAttempts, rejectedAttempts,
+                    enrollmentNotifications, modality));
 
             mSharedPreferences.edit().putStringSet(KEY, frrStatsSet).apply();
 
@@ -177,29 +178,6 @@
         }
     }
 
-    // Build string for new user and new authentication stats.
-    private String buildFrrStats(int userId, int totalAttempts, int rejectedAttempts,
-            int enrollmentNotifications, int modality)
-            throws JSONException {
-        if (modality == BiometricsProtoEnums.MODALITY_FACE) {
-            return new JSONObject()
-                    .put(USER_ID, userId)
-                    .put(FACE_ATTEMPTS, totalAttempts)
-                    .put(FACE_REJECTIONS, rejectedAttempts)
-                    .put(ENROLLMENT_NOTIFICATIONS, enrollmentNotifications)
-                    .toString();
-        } else if (modality == BiometricsProtoEnums.MODALITY_FINGERPRINT) {
-            return new JSONObject()
-                    .put(USER_ID, userId)
-                    .put(FINGERPRINT_ATTEMPTS, totalAttempts)
-                    .put(FINGERPRINT_REJECTIONS, rejectedAttempts)
-                    .put(ENROLLMENT_NOTIFICATIONS, enrollmentNotifications)
-                    .toString();
-        } else {
-            return "";
-        }
-    }
-
     private String getValue(JSONObject jsonObject, String key) throws JSONException {
         return jsonObject.has(key) ? jsonObject.getString(key) : "";
     }
diff --git a/services/core/java/com/android/server/display/AutomaticBrightnessController.java b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
index d647757..6a36fbe 100644
--- a/services/core/java/com/android/server/display/AutomaticBrightnessController.java
+++ b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
@@ -366,9 +366,10 @@
     }
 
     /**
-     * @return The current brightness recommendation calculated from the current conditions.
-     * @param brightnessEvent Event object to populate with details about why the specific
-     *                        brightness was chosen.
+     * @param brightnessEvent Holds details about how the brightness is calculated.
+     *
+     * @return The current automatic brightness recommended value. Populates brightnessEvent
+     *         parameters with details about how the brightness was calculated.
      */
     public float getAutomaticScreenBrightness(BrightnessEvent brightnessEvent) {
         if (brightnessEvent != null) {
diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
index 486cd28..2464eb0 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
@@ -184,6 +184,7 @@
  *        <defaultRefreshRateInHbmSunlight>75</defaultRefreshRateInHbmSunlight>
  *        <lowerBlockingZoneConfigs>
  *          <defaultRefreshRate>75</defaultRefreshRate>
+ *          <refreshRateThermalThrottlingId>id_of_a_throttling_map</refreshRateThermalThrottlingId>
  *          <blockingZoneThreshold>
  *            <displayBrightnessPoint>
  *              <lux>50</lux>
@@ -722,6 +723,12 @@
     private float[] mHighDisplayBrightnessThresholds = DEFAULT_BRIGHTNESS_THRESHOLDS;
     private float[] mHighAmbientBrightnessThresholds = DEFAULT_BRIGHTNESS_THRESHOLDS;
 
+    /**
+     * Thermal throttling maps for the low and high blocking zones.
+     */
+    private String mLowBlockingZoneThermalMapId = null;
+    private String mHighBlockingZoneThermalMapId = null;
+
     private final HashMap<String, ThermalBrightnessThrottlingData>
             mThermalBrightnessThrottlingDataMapByThrottlingId = new HashMap<>();
 
@@ -1526,6 +1533,13 @@
     }
 
     /**
+     * @return The refresh rate thermal map for low blocking zone.
+     */
+    public SparseArray<SurfaceControl.RefreshRateRange> getLowBlockingZoneThermalMap() {
+        return getThermalRefreshRateThrottlingData(mLowBlockingZoneThermalMapId);
+    }
+
+    /**
      * @return An array of high display brightness thresholds. This, in combination with high
      * ambient brightness thresholds help define buckets in which the refresh rate switching is not
      * allowed.
@@ -1548,6 +1562,13 @@
     }
 
     /**
+     * @return The refresh rate thermal map for high blocking zone.
+     */
+    public SparseArray<SurfaceControl.RefreshRateRange> getHighBlockingZoneThermalMap() {
+        return getThermalRefreshRateThrottlingData(mHighBlockingZoneThermalMapId);
+    }
+
+    /**
      * @return A mapping from screen off brightness sensor readings to lux values. This estimates
      * the ambient lux when the screen is off to determine the initial brightness
      */
@@ -1664,6 +1685,8 @@
                 + ", mDefaultRefreshRateInHbmHdr= " + mDefaultRefreshRateInHbmHdr
                 + ", mDefaultRefreshRateInHbmSunlight= " + mDefaultRefreshRateInHbmSunlight
                 + ", mRefreshRateThrottlingMap= " + mRefreshRateThrottlingMap
+                + ", mLowBlockingZoneThermalMapId= " + mLowBlockingZoneThermalMapId
+                + ", mHighBlockingZoneThermalMapId= " + mHighBlockingZoneThermalMapId
                 + "\n"
                 + ", mLowDisplayBrightnessThresholds= "
                 + Arrays.toString(mLowDisplayBrightnessThresholds)
@@ -2114,9 +2137,13 @@
     }
 
     /**
-     * Loads the refresh rate configurations pertaining to the upper blocking zones.
+     * Loads the refresh rate configurations pertaining to the lower blocking zones.
      */
     private void loadLowerRefreshRateBlockingZones(BlockingZoneConfig lowerBlockingZoneConfig) {
+        if (lowerBlockingZoneConfig != null) {
+            mLowBlockingZoneThermalMapId =
+                    lowerBlockingZoneConfig.getRefreshRateThermalThrottlingId();
+        }
         loadLowerBlockingZoneDefaultRefreshRate(lowerBlockingZoneConfig);
         loadLowerBrightnessThresholds(lowerBlockingZoneConfig);
     }
@@ -2125,6 +2152,10 @@
      * Loads the refresh rate configurations pertaining to the upper blocking zones.
      */
     private void loadHigherRefreshRateBlockingZones(BlockingZoneConfig upperBlockingZoneConfig) {
+        if (upperBlockingZoneConfig != null) {
+            mHighBlockingZoneThermalMapId =
+                    upperBlockingZoneConfig.getRefreshRateThermalThrottlingId();
+        }
         loadHigherBlockingZoneDefaultRefreshRate(upperBlockingZoneConfig);
         loadHigherBrightnessThresholds(upperBlockingZoneConfig);
     }
diff --git a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
index 82755b6..6079a32 100644
--- a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
+++ b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
@@ -1540,6 +1540,21 @@
         private final Injector mInjector;
         private final Handler mHandler;
 
+        private final IThermalEventListener.Stub mThermalListener =
+                new IThermalEventListener.Stub() {
+                    @Override
+                    public void notifyThrottling(Temperature temp) {
+                        @Temperature.ThrottlingStatus int currentStatus = temp.getStatus();
+                        synchronized (mLock) {
+                            if (mThermalStatus != currentStatus) {
+                                mThermalStatus = currentStatus;
+                            }
+                            onBrightnessChangedLocked();
+                        }
+                    }
+                };
+        private boolean mThermalRegistered;
+
         // Enable light sensor only when mShouldObserveAmbientLowChange is true or
         // mShouldObserveAmbientHighChange is true, screen is on, peak refresh rate
         // changeable and low power mode off. After initialization, these states will
@@ -1548,9 +1563,17 @@
         private boolean mRefreshRateChangeable = false;
         private boolean mLowPowerModeEnabled = false;
 
+        @Nullable
+        private SparseArray<RefreshRateRange> mLowZoneRefreshRateForThermals;
         private int mRefreshRateInLowZone;
+
+        @Nullable
+        private SparseArray<RefreshRateRange> mHighZoneRefreshRateForThermals;
         private int mRefreshRateInHighZone;
 
+        @GuardedBy("mLock")
+        private @Temperature.ThrottlingStatus int mThermalStatus = Temperature.THROTTLING_NONE;
+
         BrightnessObserver(Context context, Handler handler, Injector injector) {
             mContext = context;
             mHandler = handler;
@@ -1649,6 +1672,8 @@
                                 R.integer.config_defaultRefreshRateInZone)
                         : displayDeviceConfig.getDefaultLowBlockingZoneRefreshRate();
             }
+            mLowZoneRefreshRateForThermals = displayDeviceConfig == null ? null
+                    : displayDeviceConfig.getLowBlockingZoneThermalMap();
             mRefreshRateInLowZone = refreshRateInLowZone;
         }
 
@@ -1668,6 +1693,8 @@
                                 R.integer.config_fixedRefreshRateInHighZone)
                         : displayDeviceConfig.getDefaultHighBlockingZoneRefreshRate();
             }
+            mHighZoneRefreshRateForThermals = displayDeviceConfig == null ? null
+                    : displayDeviceConfig.getHighBlockingZoneThermalMap();
             mRefreshRateInHighZone = refreshRateInHighZone;
         }
 
@@ -2117,6 +2144,15 @@
             if (insideLowZone) {
                 refreshRateVote =
                         Vote.forPhysicalRefreshRates(mRefreshRateInLowZone, mRefreshRateInLowZone);
+                if (mLowZoneRefreshRateForThermals != null) {
+                    RefreshRateRange range = SkinThermalStatusObserver
+                            .findBestMatchingRefreshRateRange(mThermalStatus,
+                                    mLowZoneRefreshRateForThermals);
+                    if (range != null) {
+                        refreshRateVote =
+                                Vote.forPhysicalRefreshRates(range.min, range.max);
+                    }
+                }
                 refreshRateSwitchingVote = Vote.forDisableRefreshRateSwitching();
             }
 
@@ -2126,6 +2162,15 @@
                 refreshRateVote =
                         Vote.forPhysicalRefreshRates(mRefreshRateInHighZone,
                                 mRefreshRateInHighZone);
+                if (mHighZoneRefreshRateForThermals != null) {
+                    RefreshRateRange range = SkinThermalStatusObserver
+                            .findBestMatchingRefreshRateRange(mThermalStatus,
+                                    mHighZoneRefreshRateForThermals);
+                    if (range != null) {
+                        refreshRateVote =
+                                Vote.forPhysicalRefreshRates(range.min, range.max);
+                    }
+                }
                 refreshRateSwitchingVote = Vote.forDisableRefreshRateSwitching();
             }
 
@@ -2184,13 +2229,25 @@
                         + mRefreshRateChangeable);
             }
 
+            boolean registerForThermals = false;
             if ((mShouldObserveAmbientLowChange || mShouldObserveAmbientHighChange)
                      && isDeviceActive() && !mLowPowerModeEnabled && mRefreshRateChangeable) {
                 registerLightSensor();
-
+                registerForThermals = mLowZoneRefreshRateForThermals != null
+                        || mHighZoneRefreshRateForThermals != null;
             } else {
                 unregisterSensorListener();
             }
+
+            if (registerForThermals && !mThermalRegistered) {
+                mThermalRegistered = mInjector.registerThermalServiceListener(mThermalListener);
+            } else if (!registerForThermals && mThermalRegistered) {
+                mInjector.unregisterThermalServiceListener(mThermalListener);
+                mThermalRegistered = false;
+                synchronized (mLock) {
+                    mThermalStatus = Temperature.THROTTLING_NONE; // reset
+                }
+            }
         }
 
         private void registerLightSensor() {
@@ -2823,6 +2880,7 @@
         boolean isDozeState(Display d);
 
         boolean registerThermalServiceListener(IThermalEventListener listener);
+        void unregisterThermalServiceListener(IThermalEventListener listener);
 
         boolean supportsFrameRateOverride();
     }
@@ -2918,6 +2976,19 @@
         }
 
         @Override
+        public void unregisterThermalServiceListener(IThermalEventListener listener) {
+            IThermalService thermalService = getThermalService();
+            if (thermalService == null) {
+                Slog.w(TAG, "Could not unregister thermal status. Service not available");
+            }
+            try {
+                thermalService.unregisterThermalEventListener(listener);
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Failed to unregister thermal status listener", e);
+            }
+        }
+
+        @Override
         public boolean supportsFrameRateOverride() {
             return SurfaceFlingerProperties.enable_frame_rate_override().orElse(true);
         }
diff --git a/services/core/java/com/android/server/display/mode/SkinThermalStatusObserver.java b/services/core/java/com/android/server/display/mode/SkinThermalStatusObserver.java
index 58e1550..b29cda8 100644
--- a/services/core/java/com/android/server/display/mode/SkinThermalStatusObserver.java
+++ b/services/core/java/com/android/server/display/mode/SkinThermalStatusObserver.java
@@ -64,6 +64,20 @@
         mHandler = handler;
     }
 
+    @Nullable
+    public static SurfaceControl.RefreshRateRange findBestMatchingRefreshRateRange(
+            @Temperature.ThrottlingStatus int currentStatus,
+            SparseArray<SurfaceControl.RefreshRateRange> throttlingMap) {
+        SurfaceControl.RefreshRateRange foundRange = null;
+        for (int status = currentStatus; status >= 0; status--) {
+            foundRange = throttlingMap.get(status);
+            if (foundRange != null) {
+                break;
+            }
+        }
+        return foundRange;
+    }
+
     void observe() {
         // if failed to register thermal service listener, don't register display listener
         if (!mInjector.registerThermalServiceListener(this)) {
@@ -228,20 +242,6 @@
         }
     }
 
-    @Nullable
-    private SurfaceControl.RefreshRateRange findBestMatchingRefreshRateRange(
-            @Temperature.ThrottlingStatus int currentStatus,
-            SparseArray<SurfaceControl.RefreshRateRange> throttlingMap) {
-        SurfaceControl.RefreshRateRange foundRange = null;
-        for (int status = currentStatus; status >= 0; status--) {
-            foundRange = throttlingMap.get(status);
-            if (foundRange != null) {
-                break;
-            }
-        }
-        return foundRange;
-    }
-
     private void fallbackReportThrottlingIfNeeded(int displayId,
             @Temperature.ThrottlingStatus int currentStatus) {
         Vote vote = null;
diff --git a/services/core/java/com/android/server/input/GestureMonitorSpyWindow.java b/services/core/java/com/android/server/input/GestureMonitorSpyWindow.java
index d238dae..2ede56d 100644
--- a/services/core/java/com/android/server/input/GestureMonitorSpyWindow.java
+++ b/services/core/java/com/android/server/input/GestureMonitorSpyWindow.java
@@ -67,7 +67,7 @@
 
         final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
         t.setInputWindowInfo(mInputSurface, mWindowHandle);
-        t.setLayer(mInputSurface, Integer.MAX_VALUE);
+        t.setLayer(mInputSurface, InputManagerService.INPUT_OVERLAY_LAYER_GESTURE_MONITOR);
         t.setPosition(mInputSurface, 0, 0);
         t.setCrop(mInputSurface, null /* crop to parent surface */);
         t.show(mInputSurface);
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index 5c80291..ff69719 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -381,6 +381,17 @@
     public static final int SW_CAMERA_LENS_COVER_BIT = 1 << SW_CAMERA_LENS_COVER;
     public static final int SW_MUTE_DEVICE_BIT = 1 << SW_MUTE_DEVICE;
 
+    // The following are layer numbers used for z-ordering the input overlay layers on the display.
+    // This is used for ordering layers inside {@code DisplayContent#getInputOverlayLayer()}.
+    //
+    // The layer where gesture monitors are added.
+    public static final int INPUT_OVERLAY_LAYER_GESTURE_MONITOR = 1;
+    // Place the handwriting layer above gesture monitors so that styluses cannot trigger
+    // system gestures (e.g. navigation bar, edge-back, etc) while there is an active
+    // handwriting session.
+    public static final int INPUT_OVERLAY_LAYER_HANDWRITING_SURFACE = 2;
+
+
     private final String mVelocityTrackerStrategy;
 
     /** Whether to use the dev/input/event or uevent subsystem for the audio jack. */
diff --git a/services/core/java/com/android/server/inputmethod/HandwritingEventReceiverSurface.java b/services/core/java/com/android/server/inputmethod/HandwritingEventReceiverSurface.java
index 0c889c2..7726f40 100644
--- a/services/core/java/com/android/server/inputmethod/HandwritingEventReceiverSurface.java
+++ b/services/core/java/com/android/server/inputmethod/HandwritingEventReceiverSurface.java
@@ -27,16 +27,13 @@
 import android.view.SurfaceControl;
 import android.view.WindowManager;
 
+import com.android.server.input.InputManagerService;
+
 final class HandwritingEventReceiverSurface {
 
     public static final String TAG = HandwritingEventReceiverSurface.class.getSimpleName();
     static final boolean DEBUG = HandwritingModeController.DEBUG;
 
-    // Place the layer at the highest layer so stylus cannot trigger gesture monitors
-    // (e.g. navigation bar, edge-back, etc) while handwriting is ongoing.
-    // TODO(b/217538817): Specify the ordering in WM by usage.
-    private static final int HANDWRITING_SURFACE_LAYER = Integer.MAX_VALUE;
-
     private final InputWindowHandle mWindowHandle;
     private final InputChannel mClientChannel;
     private final SurfaceControl mInputSurface;
@@ -68,7 +65,7 @@
 
         final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
         t.setInputWindowInfo(mInputSurface, mWindowHandle);
-        t.setLayer(mInputSurface, HANDWRITING_SURFACE_LAYER);
+        t.setLayer(mInputSurface, InputManagerService.INPUT_OVERLAY_LAYER_HANDWRITING_SURFACE);
         t.setPosition(mInputSurface, 0, 0);
         t.setCrop(mInputSurface, null /* crop to parent surface */);
         t.show(mInputSurface);
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index f0e3895..d662aae 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -67,6 +67,7 @@
 import android.app.admin.DevicePolicyManager;
 import android.app.admin.DevicePolicyManagerInternal;
 import android.compat.annotation.ChangeId;
+import android.compat.annotation.Disabled;
 import android.compat.annotation.EnabledSince;
 import android.content.ComponentName;
 import android.content.Context;
@@ -311,6 +312,19 @@
     private static final long SILENT_INSTALL_ALLOWED = 265131695L;
 
     /**
+     * The system supports pre-approval and update ownership features from
+     * {@link Build.VERSION_CODES#UPSIDE_DOWN_CAKE API 34}. The change id is used to make sure
+     * the system includes the fix of pre-approval with update ownership case. When checking the
+     * change id, if it is disabled, it means the build includes the fix. The more detail is on
+     * b/293644536.
+     * See {@link PackageInstaller.SessionParams#setRequestUpdateOwnership(boolean)} and
+     * {@link #requestUserPreapproval(PreapprovalDetails, IntentSender)} for more details.
+     */
+    @Disabled
+    @ChangeId
+    private static final long PRE_APPROVAL_WITH_UPDATE_OWNERSHIP_FIX = 293644536L;
+
+    /**
      * The default value of {@link #mValidatedTargetSdk} is {@link Integer#MAX_VALUE}. If {@link
      * #mValidatedTargetSdk} is compared with {@link Build.VERSION_CODES#S} before getting the
      * target sdk version from a validated apk in {@link #validateApkInstallLocked()}, the compared
@@ -893,16 +907,27 @@
             if (mPermissionsManuallyAccepted) {
                 return USER_ACTION_NOT_NEEDED;
             }
-            packageName = mPackageName;
+            // For pre-pappvoal case, the mPackageName would be null.
+            if (mPackageName != null) {
+                packageName = mPackageName;
+            } else if (mPreapprovalRequested.get() && mPreapprovalDetails != null) {
+                packageName = mPreapprovalDetails.getPackageName();
+            } else {
+                packageName = null;
+            }
             hasDeviceAdminReceiver = mHasDeviceAdminReceiver;
         }
 
-        final boolean forcePermissionPrompt =
+        // For the below cases, force user action prompt
+        // 1. installFlags includes INSTALL_FORCE_PERMISSION_PROMPT
+        // 2. params.requireUserAction is USER_ACTION_REQUIRED
+        final boolean forceUserActionPrompt =
                 (params.installFlags & PackageManager.INSTALL_FORCE_PERMISSION_PROMPT) != 0
                         || params.requireUserAction == SessionParams.USER_ACTION_REQUIRED;
-        if (forcePermissionPrompt) {
-            return USER_ACTION_REQUIRED;
-        }
+        final int userActionNotTypicallyNeededResponse = forceUserActionPrompt
+                ? USER_ACTION_REQUIRED
+                : USER_ACTION_NOT_NEEDED;
+
         // It is safe to access mInstallerUid and mInstallSource without lock
         // because they are immutable after sealing.
         final Computer snapshot = mPm.snapshotComputer();
@@ -956,7 +981,7 @@
                 || isInstallerDeviceOwnerOrAffiliatedProfileOwner();
 
         if (noUserActionNecessary) {
-            return USER_ACTION_NOT_NEEDED;
+            return userActionNotTypicallyNeededResponse;
         }
 
         if (isUpdateOwnershipEnforcementEnabled
@@ -969,7 +994,7 @@
         }
 
         if (isPermissionGranted) {
-            return USER_ACTION_NOT_NEEDED;
+            return userActionNotTypicallyNeededResponse;
         }
 
         if (snapshot.isInstallDisabledForPackage(getInstallerPackageName(), mInstallerUid,
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index e5dc688..c583f17 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -5257,9 +5257,6 @@
         public int getUserMinAspectRatio(@NonNull String packageName, int userId) {
             final Computer snapshot = snapshotComputer();
             final int callingUid = Binder.getCallingUid();
-            snapshot.enforceCrossUserPermission(
-                    callingUid, userId, false /* requireFullPermission */,
-                    false /* checkShell */, "getUserMinAspectRatio");
             final PackageStateInternal packageState = snapshot
                     .getPackageStateForInstalledAndFiltered(packageName, callingUid, userId);
             return packageState == null ? USER_MIN_ASPECT_RATIO_UNSET
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 4cb4fe2..4a40395 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -328,6 +328,12 @@
      */
     private SurfaceControl mOverlayLayer;
 
+    /**
+     * A SurfaceControl that contains input overlays used for cases where we need to receive input
+     * over the entire display.
+     */
+    private SurfaceControl mInputOverlayLayer;
+
     /** A surfaceControl specifically for accessibility overlays. */
     private SurfaceControl mA11yOverlayLayer;
 
@@ -1329,6 +1335,12 @@
             transaction.reparent(mOverlayLayer, mSurfaceControl);
         }
 
+        if (mInputOverlayLayer == null) {
+            mInputOverlayLayer = b.setName("Input Overlays").setParent(mSurfaceControl).build();
+        } else {
+            transaction.reparent(mInputOverlayLayer, mSurfaceControl);
+        }
+
         if (mA11yOverlayLayer == null) {
             mA11yOverlayLayer =
                     b.setName("Accessibility Overlays").setParent(mSurfaceControl).build();
@@ -1342,7 +1354,9 @@
                 .show(mSurfaceControl)
                 .setLayer(mOverlayLayer, Integer.MAX_VALUE)
                 .show(mOverlayLayer)
-                .setLayer(mA11yOverlayLayer, Integer.MAX_VALUE - 1)
+                .setLayer(mInputOverlayLayer, Integer.MAX_VALUE - 1)
+                .show(mInputOverlayLayer)
+                .setLayer(mA11yOverlayLayer, Integer.MAX_VALUE - 2)
                 .show(mA11yOverlayLayer);
     }
 
@@ -3353,6 +3367,7 @@
             // -> this DisplayContent.
             setRemoteInsetsController(null);
             mOverlayLayer.release();
+            mInputOverlayLayer.release();
             mA11yOverlayLayer.release();
             mWindowingLayer.release();
             mInputMonitor.onDisplayRemoved();
@@ -5703,6 +5718,10 @@
         return mOverlayLayer;
     }
 
+    SurfaceControl getInputOverlayLayer() {
+        return mInputOverlayLayer;
+    }
+
     SurfaceControl getA11yOverlayLayer() {
         return mA11yOverlayLayer;
     }
diff --git a/services/core/java/com/android/server/wm/InputManagerCallback.java b/services/core/java/com/android/server/wm/InputManagerCallback.java
index 73fdfe0..8cf4713 100644
--- a/services/core/java/com/android/server/wm/InputManagerCallback.java
+++ b/services/core/java/com/android/server/wm/InputManagerCallback.java
@@ -275,11 +275,17 @@
                         + " - DisplayContent not found.");
                 return null;
             }
+            final SurfaceControl inputOverlay = dc.getInputOverlayLayer();
+            if (inputOverlay == null) {
+                Slog.e(TAG, "Failed to create a gesture monitor on display: " + displayId
+                        + " - Input overlay layer is not initialized.");
+                return null;
+            }
             return mService.makeSurfaceBuilder(dc.getSession())
                     .setContainerLayer()
                     .setName(name)
                     .setCallsite("createSurfaceForGestureMonitor")
-                    .setParent(dc.getSurfaceControl())
+                    .setParent(inputOverlay)
                     .build();
         }
     }
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index 5b466a0..3eabbe7 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -1147,6 +1147,7 @@
             Transition.asyncTraceBegin("animating", 0x41bfaf1 /* hashcode of TAG */);
         } else if (!animatingState && mAnimatingState) {
             t.setEarlyWakeupEnd();
+            mAtm.mWindowManager.requestTraversal();
             mSnapshotController.setPause(false);
             mAnimatingState = false;
             Transition.asyncTraceEnd(0x41bfaf1 /* hashcode of TAG */);
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 3a19a3b..b20be55 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -8311,12 +8311,18 @@
                             + displayId + " - DisplayContent not found.");
                     return null;
                 }
-                //TODO (b/210039666): Use a method like add/removeDisplayOverlay if available.
+                final SurfaceControl inputOverlay = dc.getInputOverlayLayer();
+                if (inputOverlay == null) {
+                    Slog.e(TAG, "Failed to create a gesture monitor on display: " + displayId
+                            + " - Input overlay layer is not initialized.");
+                    return null;
+                }
+                // TODO(b/210039666): Use a method like add/removeDisplayOverlay if available.
                 return makeSurfaceBuilder(dc.getSession())
                         .setContainerLayer()
                         .setName("IME Handwriting Surface")
                         .setCallsite("getHandwritingSurfaceForDisplay")
-                        .setParent(dc.getSurfaceControl())
+                        .setParent(inputOverlay)
                         .build();
             }
         }
diff --git a/services/core/xsd/display-device-config/display-device-config.xsd b/services/core/xsd/display-device-config/display-device-config.xsd
index 7104a80..b63154d 100644
--- a/services/core/xsd/display-device-config/display-device-config.xsd
+++ b/services/core/xsd/display-device-config/display-device-config.xsd
@@ -579,6 +579,10 @@
                     minOccurs="1" maxOccurs="1">
             <xs:annotation name="final"/>
         </xs:element>
+        <xs:element type ="xs:string" name="refreshRateThermalThrottlingId">
+            <xs:annotation name="nullable"/>
+            <xs:annotation name="final"/>
+        </xs:element>
         <xs:element name="blockingZoneThreshold" type="blockingZoneThreshold"
                     minOccurs="1" maxOccurs="1">
             <xs:annotation name="final"/>
diff --git a/services/core/xsd/display-device-config/schema/current.txt b/services/core/xsd/display-device-config/schema/current.txt
index 507c9dc..426fcb6 100644
--- a/services/core/xsd/display-device-config/schema/current.txt
+++ b/services/core/xsd/display-device-config/schema/current.txt
@@ -17,8 +17,10 @@
     ctor public BlockingZoneConfig();
     method public final com.android.server.display.config.BlockingZoneThreshold getBlockingZoneThreshold();
     method public final java.math.BigInteger getDefaultRefreshRate();
+    method @Nullable public final String getRefreshRateThermalThrottlingId();
     method public final void setBlockingZoneThreshold(com.android.server.display.config.BlockingZoneThreshold);
     method public final void setDefaultRefreshRate(java.math.BigInteger);
+    method public final void setRefreshRateThermalThrottlingId(@Nullable String);
   }
 
   public class BlockingZoneThreshold {
diff --git a/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupReporterTest.java b/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupReporterTest.java
index 14b4dc3..2db2438 100644
--- a/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupReporterTest.java
+++ b/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupReporterTest.java
@@ -27,6 +27,7 @@
 import android.util.Log;
 
 import com.android.server.backup.UserBackupManagerService;
+import com.android.server.backup.utils.BackupManagerMonitorEventSender;
 import com.android.server.testing.shadows.ShadowEventLog;
 import com.android.server.testing.shadows.ShadowSlog;
 
@@ -46,10 +47,13 @@
     @Mock private IBackupManagerMonitor mMonitor;
 
     private KeyValueBackupReporter mReporter;
+    private BackupManagerMonitorEventSender mBackupManagerMonitorEventSender;
 
     @Before
     public void setUp() {
-        mReporter = new KeyValueBackupReporter(mBackupManagerService, mObserver, mMonitor);
+        mBackupManagerMonitorEventSender = new BackupManagerMonitorEventSender(mMonitor);
+        mReporter = new KeyValueBackupReporter(
+                mBackupManagerService, mObserver, mBackupManagerMonitorEventSender);
     }
 
     @Test
diff --git a/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java b/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java
index bfbc0f5..7349c14 100644
--- a/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java
+++ b/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java
@@ -122,6 +122,7 @@
 import com.android.server.backup.testing.TransportTestUtils;
 import com.android.server.backup.testing.TransportTestUtils.TransportMock;
 import com.android.server.backup.utils.BackupEligibilityRules;
+import com.android.server.backup.utils.BackupManagerMonitorEventSender;
 import com.android.server.testing.shadows.FrameworkShadowLooper;
 import com.android.server.testing.shadows.ShadowApplicationPackageManager;
 import com.android.server.testing.shadows.ShadowBackupDataInput;
@@ -260,7 +261,8 @@
         mBackupHandler = mBackupManagerService.getBackupHandler();
         mShadowBackupLooper = shadowOf(mBackupHandler.getLooper());
         ShadowEventLog.setUp();
-        mReporter = spy(new KeyValueBackupReporter(mBackupManagerService, mObserver, mMonitor));
+        mReporter = spy(new KeyValueBackupReporter(mBackupManagerService, mObserver,
+                new BackupManagerMonitorEventSender(mMonitor)));
 
         when(mPackageManagerInternal.getApplicationEnabledState(any(), anyInt()))
                 .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_ENABLED);
diff --git a/services/tests/PackageManagerServiceTests/appenumeration/src/com/android/server/pm/test/appenumeration/CrossUserPackageVisibilityTests.java b/services/tests/PackageManagerServiceTests/appenumeration/src/com/android/server/pm/test/appenumeration/CrossUserPackageVisibilityTests.java
index b7a0cf3..e33ca77 100644
--- a/services/tests/PackageManagerServiceTests/appenumeration/src/com/android/server/pm/test/appenumeration/CrossUserPackageVisibilityTests.java
+++ b/services/tests/PackageManagerServiceTests/appenumeration/src/com/android/server/pm/test/appenumeration/CrossUserPackageVisibilityTests.java
@@ -138,14 +138,6 @@
     }
 
     @Test
-    public void testGetUserMinAspectRatio_withCrossUserId() {
-        final int crossUserId = UserHandle.myUserId() + 1;
-        assertThrows(SecurityException.class,
-                () -> mIPackageManager.getUserMinAspectRatio(
-                        mInstrumentation.getContext().getPackageName(), crossUserId));
-    }
-
-    @Test
     public void testIsPackageSignedByKeySet_cannotDetectCrossUserPkg() throws Exception {
         final KeySet keySet = mIPackageManager.getSigningKeySet(mContext.getPackageName());
         assertThrows(IllegalArgumentException.class,
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
index da7a6a1..4752f81 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
@@ -268,6 +268,14 @@
                 mDisplayDeviceConfig.getHdrBrightnessFromSdr(0.62f, 1.25f),
                 SMALL_DELTA);
 
+        // Low/High zone thermal maps
+        assertEquals(new SurfaceControl.RefreshRateRange(30, 40),
+                mDisplayDeviceConfig.getLowBlockingZoneThermalMap()
+                .get(Temperature.THROTTLING_CRITICAL));
+        assertEquals(new SurfaceControl.RefreshRateRange(40, 60),
+                mDisplayDeviceConfig.getHighBlockingZoneThermalMap()
+                .get(Temperature.THROTTLING_EMERGENCY));
+
         // Todo: Add asserts for DensityMapping,
         // HighBrightnessModeData AmbientLightSensor, RefreshRateLimitations and ProximitySensor.
     }
@@ -530,6 +538,24 @@
                + "        </refreshRateRange>\n"
                + "    </refreshRateThrottlingPoint>\n"
                + "</refreshRateThrottlingMap>\n"
+               + "<refreshRateThrottlingMap id=\"thermalLow\">\n"
+               + "    <refreshRateThrottlingPoint>\n"
+               + "        <thermalStatus>critical</thermalStatus>\n"
+               + "        <refreshRateRange>\n"
+               + "            <minimum>30</minimum>\n"
+               + "            <maximum>40</maximum>\n"
+               + "        </refreshRateRange>\n"
+               + "    </refreshRateThrottlingPoint>\n"
+               + "</refreshRateThrottlingMap>\n"
+               + "<refreshRateThrottlingMap id=\"thermalHigh\">\n"
+               + "    <refreshRateThrottlingPoint>\n"
+               + "        <thermalStatus>emergency</thermalStatus>\n"
+               + "        <refreshRateRange>\n"
+               + "            <minimum>40</minimum>\n"
+               + "            <maximum>60</maximum>\n"
+               + "        </refreshRateRange>\n"
+               + "    </refreshRateThrottlingPoint>\n"
+               + "</refreshRateThrottlingMap>\n"
                + "<refreshRateThrottlingMap id=\"test\">\n"
                + "    <refreshRateThrottlingPoint>\n"
                + "        <thermalStatus>emergency</thermalStatus>\n"
@@ -822,6 +848,8 @@
                 +       "<defaultRefreshRateInHbmSunlight>83</defaultRefreshRateInHbmSunlight>\n"
                 +       "<lowerBlockingZoneConfigs>\n"
                 +           "<defaultRefreshRate>75</defaultRefreshRate>\n"
+                +           "<refreshRateThermalThrottlingId>thermalLow"
+                +           "</refreshRateThermalThrottlingId>\n"
                 +           "<blockingZoneThreshold>\n"
                 +               "<displayBrightnessPoint>\n"
                 +                   "<lux>50</lux>\n"
@@ -843,6 +871,8 @@
                 +       "</lowerBlockingZoneConfigs>\n"
                 +       "<higherBlockingZoneConfigs>\n"
                 +           "<defaultRefreshRate>90</defaultRefreshRate>\n"
+                +           "<refreshRateThermalThrottlingId>thermalHigh"
+                +           "</refreshRateThermalThrottlingId>\n"
                 +           "<blockingZoneThreshold>\n"
                 +               "<displayBrightnessPoint>\n"
                 +                   "<lux>70</lux>\n"
diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
index 15f13cd..89a1e13 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
@@ -39,6 +39,7 @@
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
@@ -1100,6 +1101,196 @@
     }
 
     @Test
+    public void testLockFpsForHighZoneWithThermalCondition() throws Exception {
+        // First, configure brightness zones or DMD won't register for sensor data.
+        final FakeDeviceConfig config = mInjector.getDeviceConfig();
+        config.setHighDisplayBrightnessThresholds(new int[] { 200 });
+        config.setHighAmbientBrightnessThresholds(new int[] { 8000 });
+
+        DisplayModeDirector director =
+                createDirectorFromRefreshRateArray(new float[] {60.f, 90.f, 120.f}, 0);
+        setPeakRefreshRate(120 /*fps*/);
+        director.getSettingsObserver().setDefaultRefreshRate(120);
+        director.getBrightnessObserver().setDefaultDisplayState(Display.STATE_ON);
+
+        // Set the thresholds for High Zone
+        DisplayDeviceConfig ddcMock = mock(DisplayDeviceConfig.class);
+        when(ddcMock.getDefaultHighBlockingZoneRefreshRate()).thenReturn(90);
+        when(ddcMock.getHighDisplayBrightnessThresholds()).thenReturn(new float[] { 200 });
+        when(ddcMock.getHighAmbientBrightnessThresholds()).thenReturn(new float[] { 8000 });
+        when(ddcMock.getDefaultLowBlockingZoneRefreshRate()).thenReturn(90);
+        when(ddcMock.getLowDisplayBrightnessThresholds()).thenReturn(new float[] {});
+        when(ddcMock.getLowAmbientBrightnessThresholds()).thenReturn(new float[] {});
+
+        // Set the thermal condition for refresh rate range
+        when(ddcMock.getHighBlockingZoneThermalMap()).thenReturn(
+                new SparseArray<RefreshRateRange>() {{
+                    put(Temperature.THROTTLING_CRITICAL, new RefreshRateRange(60, 60));
+                }}
+        );
+        director.defaultDisplayDeviceUpdated(ddcMock); // set the ddc
+
+        Sensor lightSensor = createLightSensor();
+        SensorManager sensorManager = createMockSensorManager(lightSensor);
+        director.start(sensorManager);
+
+        // Get the display listener so that we can send it new brightness events
+        ArgumentCaptor<DisplayListener> displayListenerCaptor =
+                  ArgumentCaptor.forClass(DisplayListener.class);
+        verify(mInjector).registerDisplayListener(displayListenerCaptor.capture(),
+                any(Handler.class),
+                eq(DisplayManager.EVENT_FLAG_DISPLAY_CHANGED
+                    | DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS));
+        DisplayListener displayListener = displayListenerCaptor.getValue();
+
+        // Get the sensor listener so that we can give it new light sensor events
+        ArgumentCaptor<SensorEventListener> listenerCaptor =
+                ArgumentCaptor.forClass(SensorEventListener.class);
+        verify(sensorManager, Mockito.timeout(TimeUnit.SECONDS.toMillis(1)))
+                .registerListener(
+                        listenerCaptor.capture(),
+                        eq(lightSensor),
+                        anyInt(),
+                        any(Handler.class));
+        SensorEventListener sensorListener = listenerCaptor.getValue();
+
+        // Get the thermal listener so that we can give it new thermal conditions
+        ArgumentCaptor<IThermalEventListener> thermalListenerCaptor =
+                ArgumentCaptor.forClass(IThermalEventListener.class);
+        verify(mInjector, atLeastOnce()).registerThermalServiceListener(
+                thermalListenerCaptor.capture());
+        List<IThermalEventListener> thermalListeners = thermalListenerCaptor.getAllValues();
+
+        setBrightness(100, 100, displayListener);
+        // Sensor reads 2000 lux,
+        sensorListener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, 2000));
+
+        Vote vote = director.getVote(Display.DEFAULT_DISPLAY, Vote.PRIORITY_FLICKER_REFRESH_RATE);
+        assertThat(vote).isNull();
+        vote = director.getVote(Display.DEFAULT_DISPLAY, Vote.PRIORITY_FLICKER_REFRESH_RATE_SWITCH);
+        assertThat(vote).isNull();
+
+        // We expect DisplayModeDirector to act on BrightnessInfo.adjustedBrightness; set only this
+        // parameter to the necessary threshold
+        setBrightness(255, 255, displayListener);
+        // Sensor reads 9000 lux,
+        sensorListener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, 9000));
+
+        vote = director.getVote(Display.DEFAULT_DISPLAY, Vote.PRIORITY_FLICKER_REFRESH_RATE);
+        assertVoteForPhysicalRefreshRate(vote, 90 /*fps*/);
+        vote = director.getVote(Display.DEFAULT_DISPLAY, Vote.PRIORITY_FLICKER_REFRESH_RATE_SWITCH);
+        assertThat(vote).isNotNull();
+        assertThat(vote.disableRefreshRateSwitching).isTrue();
+
+        // Set critical and check new refresh rate
+        Temperature temp = getSkinTemp(Temperature.THROTTLING_CRITICAL);
+        for (var listener : thermalListeners) {
+            listener.notifyThrottling(temp);
+        }
+
+        vote = director.getVote(Display.DEFAULT_DISPLAY, Vote.PRIORITY_FLICKER_REFRESH_RATE);
+        assertVoteForPhysicalRefreshRate(vote, 60 /*fps*/);
+        vote = director.getVote(Display.DEFAULT_DISPLAY, Vote.PRIORITY_FLICKER_REFRESH_RATE_SWITCH);
+        assertThat(vote).isNotNull();
+        assertThat(vote.disableRefreshRateSwitching).isTrue();
+    }
+
+    @Test
+    public void testLockFpsForLowZoneWithThermalCondition() throws Exception {
+        // First, configure brightness zones or DMD won't register for sensor data.
+        final FakeDeviceConfig config = mInjector.getDeviceConfig();
+        config.setHighDisplayBrightnessThresholds(new int[] { 200 });
+        config.setHighAmbientBrightnessThresholds(new int[] { 8000 });
+
+        DisplayModeDirector director =
+                createDirectorFromRefreshRateArray(new float[] {60.f, 90.f, 120.f}, 0);
+        setPeakRefreshRate(120 /*fps*/);
+        director.getSettingsObserver().setDefaultRefreshRate(120);
+        director.getBrightnessObserver().setDefaultDisplayState(Display.STATE_ON);
+
+        // Set the thresholds for Low Zone
+        DisplayDeviceConfig ddcMock = mock(DisplayDeviceConfig.class);
+        when(ddcMock.getDefaultLowBlockingZoneRefreshRate()).thenReturn(90);
+        when(ddcMock.getHighDisplayBrightnessThresholds()).thenReturn(new float[] { 200 });
+        when(ddcMock.getHighAmbientBrightnessThresholds()).thenReturn(new float[] { 8000 });
+        when(ddcMock.getDefaultLowBlockingZoneRefreshRate()).thenReturn(90);
+        when(ddcMock.getLowDisplayBrightnessThresholds()).thenReturn(new float[] { 10 });
+        when(ddcMock.getLowAmbientBrightnessThresholds()).thenReturn(new float[] { 10 });
+
+        // Set the thermal condition for refresh rate range
+        when(ddcMock.getLowBlockingZoneThermalMap()).thenReturn(
+                new SparseArray<RefreshRateRange>() {{
+                    put(Temperature.THROTTLING_CRITICAL, new RefreshRateRange(60, 60));
+                }}
+        );
+        director.defaultDisplayDeviceUpdated(ddcMock); // set the ddc
+
+        Sensor lightSensor = createLightSensor();
+        SensorManager sensorManager = createMockSensorManager(lightSensor);
+        director.start(sensorManager);
+
+        // Get the display listener so that we can send it new brightness events
+        ArgumentCaptor<DisplayListener> displayListenerCaptor =
+                  ArgumentCaptor.forClass(DisplayListener.class);
+        verify(mInjector).registerDisplayListener(displayListenerCaptor.capture(),
+                any(Handler.class),
+                eq(DisplayManager.EVENT_FLAG_DISPLAY_CHANGED
+                    | DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS));
+        DisplayListener displayListener = displayListenerCaptor.getValue();
+
+        // Get the sensor listener so that we can give it new light sensor events
+        ArgumentCaptor<SensorEventListener> listenerCaptor =
+                ArgumentCaptor.forClass(SensorEventListener.class);
+        verify(sensorManager, Mockito.timeout(TimeUnit.SECONDS.toMillis(1)))
+                .registerListener(
+                        listenerCaptor.capture(),
+                        eq(lightSensor),
+                        anyInt(),
+                        any(Handler.class));
+        SensorEventListener sensorListener = listenerCaptor.getValue();
+
+        // Get the thermal listener so that we can give it new thermal conditions
+        ArgumentCaptor<IThermalEventListener> thermalListenerCaptor =
+                ArgumentCaptor.forClass(IThermalEventListener.class);
+        verify(mInjector, atLeastOnce()).registerThermalServiceListener(
+                thermalListenerCaptor.capture());
+        List<IThermalEventListener> thermalListeners = thermalListenerCaptor.getAllValues();
+
+        setBrightness(100, 100, displayListener);
+        // Sensor reads 2000 lux,
+        sensorListener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, 2000));
+
+        Vote vote = director.getVote(Display.DEFAULT_DISPLAY, Vote.PRIORITY_FLICKER_REFRESH_RATE);
+        assertThat(vote).isNull();
+        vote = director.getVote(Display.DEFAULT_DISPLAY, Vote.PRIORITY_FLICKER_REFRESH_RATE_SWITCH);
+        assertThat(vote).isNull();
+
+        // We expect DisplayModeDirector to act on BrightnessInfo.adjustedBrightness; set only this
+        // parameter to the necessary threshold
+        setBrightness(5, 5, displayListener);
+        // Sensor reads 9 lux,
+        sensorListener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, 9));
+
+        vote = director.getVote(Display.DEFAULT_DISPLAY, Vote.PRIORITY_FLICKER_REFRESH_RATE);
+        assertVoteForPhysicalRefreshRate(vote, 90 /*fps*/);
+        vote = director.getVote(Display.DEFAULT_DISPLAY, Vote.PRIORITY_FLICKER_REFRESH_RATE_SWITCH);
+        assertThat(vote).isNotNull();
+        assertThat(vote.disableRefreshRateSwitching).isTrue();
+
+        // Set critical and check new refresh rate
+        Temperature temp = getSkinTemp(Temperature.THROTTLING_CRITICAL);
+        for (var listener : thermalListeners) {
+            listener.notifyThrottling(temp);
+        }
+
+        vote = director.getVote(Display.DEFAULT_DISPLAY, Vote.PRIORITY_FLICKER_REFRESH_RATE);
+        assertVoteForPhysicalRefreshRate(vote, 60 /*fps*/);
+        vote = director.getVote(Display.DEFAULT_DISPLAY, Vote.PRIORITY_FLICKER_REFRESH_RATE_SWITCH);
+        assertThat(vote).isNotNull();
+        assertThat(vote.disableRefreshRateSwitching).isTrue();
+    }
+
+    @Test
     public void testSensorRegistration() {
         // First, configure brightness zones or DMD won't register for sensor data.
         final FakeDeviceConfig config = mInjector.getDeviceConfig();
@@ -2902,6 +3093,10 @@
         }
 
         @Override
+        public void unregisterThermalServiceListener(IThermalEventListener listener) {
+        }
+
+        @Override
         public boolean supportsFrameRateOverride() {
             return true;
         }
diff --git a/services/tests/mockingservicestests/src/com/android/server/backup/UserBackupManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/backup/UserBackupManagerServiceTest.java
index dc1c6d5..c942cf4 100644
--- a/services/tests/mockingservicestests/src/com/android/server/backup/UserBackupManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/backup/UserBackupManagerServiceTest.java
@@ -52,7 +52,7 @@
 import com.android.server.backup.transport.BackupTransportClient;
 import com.android.server.backup.transport.TransportConnection;
 import com.android.server.backup.utils.BackupEligibilityRules;
-import com.android.server.backup.utils.BackupManagerMonitorUtils;
+import com.android.server.backup.utils.BackupManagerMonitorEventSender;
 
 import com.google.common.collect.ImmutableSet;
 
@@ -86,6 +86,7 @@
     @Mock BackupTransportClient mBackupTransport;
     @Mock BackupEligibilityRules mBackupEligibilityRules;
     @Mock LifecycleOperationStorage mOperationStorage;
+    @Mock BackupManagerMonitorEventSender mBackupManagerMonitorEventSender;
 
     private MockitoSession mSession;
     private TestBackupService mService;
@@ -94,7 +95,7 @@
     public void setUp() throws Exception {
         mSession = mockitoSession()
                 .initMocks(this)
-                .mockStatic(BackupManagerMonitorUtils.class)
+                .mockStatic(BackupManagerMonitorEventSender.class)
                 .mockStatic(FeatureFlagUtils.class)
                 // TODO(b/263239775): Remove unnecessary stubbing.
                 .strictness(Strictness.LENIENT)
@@ -246,9 +247,9 @@
                 new DataTypeResult(/* dataType */ "type_2"));
         mService.reportDelayedRestoreResult(TEST_PACKAGE, results);
 
-        verify(() -> BackupManagerMonitorUtils.sendAgentLoggingResults(
-                eq(mBackupManagerMonitor), eq(packageInfo), eq(results), eq(
-                        BackupAnnotations.OperationType.RESTORE)));
+
+        verify(mBackupManagerMonitorEventSender).sendAgentLoggingResults(
+                eq(packageInfo), eq(results), eq(BackupAnnotations.OperationType.RESTORE));
     }
 
     private static PackageInfo getPackageInfo(String packageName) {
@@ -258,7 +259,7 @@
         return packageInfo;
     }
 
-    private static class TestBackupService extends UserBackupManagerService {
+    private class TestBackupService extends UserBackupManagerService {
         boolean isEnabledStatePersisted = false;
         boolean shouldUseNewBackupEligibilityRules = false;
 
@@ -293,6 +294,11 @@
             return mWorkerThread;
         }
 
+        @Override
+        BackupManagerMonitorEventSender getBMMEventSender(IBackupManagerMonitor monitor) {
+            return mBackupManagerMonitorEventSender;
+        }
+
         private void waitForAsyncOperation() {
             if (mWorkerThread == null) {
                 return;
diff --git a/services/tests/mockingservicestests/src/com/android/server/backup/utils/BackupManagerMonitorDumpsysUtilsTest.java b/services/tests/mockingservicestests/src/com/android/server/backup/utils/BackupManagerMonitorDumpsysUtilsTest.java
new file mode 100644
index 0000000..8e17b3a
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/backup/utils/BackupManagerMonitorDumpsysUtilsTest.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.backup.utils;
+
+import static org.junit.Assert.assertTrue;
+
+import android.app.backup.BackupManagerMonitor;
+import android.os.Bundle;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+import java.io.File;
+
+public class BackupManagerMonitorDumpsysUtilsTest {
+    private File mTempFile;
+    private TestBackupManagerMonitorDumpsysUtils mBackupManagerMonitorDumpsysUtils;
+    @Rule
+    public TemporaryFolder tmp = new TemporaryFolder();
+
+    @Before
+    public void setUp() throws Exception {
+        mTempFile = tmp.newFile("testbmmevents.txt");
+        mBackupManagerMonitorDumpsysUtils = new TestBackupManagerMonitorDumpsysUtils();
+    }
+
+
+    @Test
+    public void parseBackupManagerMonitorEventForDumpsys_bundleIsNull_noLogsWrittenToFile()
+            throws Exception {
+        mBackupManagerMonitorDumpsysUtils.parseBackupManagerMonitorRestoreEventForDumpsys(null);
+
+        assertTrue(mTempFile.length() == 0);
+
+    }
+
+    @Test
+    public void parseBackupManagerMonitorEventForDumpsys_missingID_noLogsWrittenToFile()
+            throws Exception {
+        Bundle event = new Bundle();
+        event.putInt(BackupManagerMonitor.EXTRA_LOG_EVENT_CATEGORY, 1);
+        mBackupManagerMonitorDumpsysUtils.parseBackupManagerMonitorRestoreEventForDumpsys(event);
+
+        assertTrue(mTempFile.length() == 0);
+    }
+
+    @Test
+    public void parseBackupManagerMonitorEventForDumpsys_missingCategory_noLogsWrittenToFile()
+            throws Exception {
+        Bundle event = new Bundle();
+        event.putInt(BackupManagerMonitor.EXTRA_LOG_EVENT_ID, 1);
+        mBackupManagerMonitorDumpsysUtils.parseBackupManagerMonitorRestoreEventForDumpsys(event);
+
+        assertTrue(mTempFile.length() == 0);
+    }
+
+    private class TestBackupManagerMonitorDumpsysUtils
+            extends BackupManagerMonitorDumpsysUtils {
+        TestBackupManagerMonitorDumpsysUtils() {
+            super();
+        }
+
+        @Override
+        public File getBMMEventsFile() {
+            return mTempFile;
+        }
+    }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/backup/utils/BackupManagerMonitorUtilsTest.java b/services/tests/mockingservicestests/src/com/android/server/backup/utils/BackupManagerMonitorEventSenderTest.java
similarity index 67%
rename from services/tests/mockingservicestests/src/com/android/server/backup/utils/BackupManagerMonitorUtilsTest.java
rename to services/tests/mockingservicestests/src/com/android/server/backup/utils/BackupManagerMonitorEventSenderTest.java
index 093ad3c..3af2932 100644
--- a/services/tests/mockingservicestests/src/com/android/server/backup/utils/BackupManagerMonitorUtilsTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/backup/utils/BackupManagerMonitorEventSenderTest.java
@@ -30,11 +30,11 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 
 import android.app.IBackupAgent;
-import android.app.backup.BackupAnnotations;
 import android.app.backup.BackupAnnotations.OperationType;
 import android.app.backup.BackupManagerMonitor;
 import android.app.backup.BackupRestoreEventLogger;
@@ -62,39 +62,65 @@
 @SmallTest
 @Presubmit
 @RunWith(AndroidJUnit4.class)
-public class BackupManagerMonitorUtilsTest {
+public class BackupManagerMonitorEventSenderTest {
     @Mock private IBackupManagerMonitor mMonitorMock;
+    @Mock private BackupManagerMonitorDumpsysUtils mBackupManagerMonitorDumpsysUtilsMock;
+
+    private BackupManagerMonitorEventSender mBackupManagerMonitorEventSender;
 
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
+        mBackupManagerMonitorEventSender = new BackupManagerMonitorEventSender(mMonitorMock,
+                mBackupManagerMonitorDumpsysUtilsMock);
     }
 
     @Test
-    public void monitorEvent_monitorIsNull_returnsNull() throws Exception {
-        IBackupManagerMonitor result = BackupManagerMonitorUtils.monitorEvent(null, 0, null, 0,
-                null);
+    public void monitorEvent_monitorIsNull_sendBundleToDumpsys() throws Exception {
+        Bundle extras = new Bundle();
+        extras.putInt(EXTRA_LOG_OPERATION_TYPE, OperationType.RESTORE);
+        mBackupManagerMonitorEventSender.setMonitor(null);
+        mBackupManagerMonitorEventSender.monitorEvent(0, null, 0, extras);
+        IBackupManagerMonitor monitor = mBackupManagerMonitorEventSender.getMonitor();
 
-        assertThat(result).isNull();
+        verify(mBackupManagerMonitorDumpsysUtilsMock).parseBackupManagerMonitorRestoreEventForDumpsys(any(
+                Bundle.class));
     }
 
     @Test
-    public void monitorEvent_monitorOnEventThrows_returnsNull() throws Exception {
+    public void monitorEvent_monitorIsNull_doNotCallOnEvent() throws Exception {
+        mBackupManagerMonitorEventSender = new BackupManagerMonitorEventSender(null);
+        mBackupManagerMonitorEventSender.monitorEvent(0, null, 0, null);
+        IBackupManagerMonitor monitor = mBackupManagerMonitorEventSender.getMonitor();
+
+        verify(mMonitorMock, never()).onEvent(any(Bundle.class));
+    }
+
+    @Test
+    public void monitorEvent_monitorOnEventThrows_setsMonitorToNull() throws Exception {
         doThrow(new RemoteException()).when(mMonitorMock).onEvent(any(Bundle.class));
 
-        IBackupManagerMonitor result = BackupManagerMonitorUtils.monitorEvent(mMonitorMock, 0, null,
-                0, null);
+        mBackupManagerMonitorEventSender.monitorEvent(0, null, 0, null);
+        IBackupManagerMonitor monitor = mBackupManagerMonitorEventSender.getMonitor();
 
         verify(mMonitorMock).onEvent(any(Bundle.class));
-        assertThat(result).isNull();
+        assertThat(monitor).isNull();
+    }
+
+    @Test
+    public void monitorEvent_extrasAreNull_doNotSendBundleToDumpsys() throws Exception {
+        mBackupManagerMonitorEventSender.monitorEvent(1, null, 2, null);
+
+        verify(mBackupManagerMonitorDumpsysUtilsMock, never())
+                .parseBackupManagerMonitorRestoreEventForDumpsys(any(Bundle.class));
     }
 
     @Test
     public void monitorEvent_packageAndExtrasAreNull_fillsBundleCorrectly() throws Exception {
-        IBackupManagerMonitor result = BackupManagerMonitorUtils.monitorEvent(mMonitorMock, 1, null,
-                2, null);
+        mBackupManagerMonitorEventSender.monitorEvent(1, null, 2, null);
+        IBackupManagerMonitor monitor = mBackupManagerMonitorEventSender.getMonitor();
 
-        assertThat(result).isEqualTo(mMonitorMock);
+        assertThat(monitor).isEqualTo(mMonitorMock);
         ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
         verify(mMonitorMock).onEvent(bundleCaptor.capture());
         Bundle eventBundle = bundleCaptor.getValue();
@@ -112,10 +138,10 @@
         extras.putInt("key1", 4);
         extras.putString("key2", "value2");
 
-        IBackupManagerMonitor result = BackupManagerMonitorUtils.monitorEvent(mMonitorMock, 1,
-                packageInfo, 2, extras);
+        mBackupManagerMonitorEventSender.monitorEvent(1, packageInfo, 2, extras);
+        IBackupManagerMonitor monitor = mBackupManagerMonitorEventSender.getMonitor();
 
-        assertThat(result).isEqualTo(mMonitorMock);
+        assertThat(monitor).isEqualTo(mMonitorMock);
         ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
         verify(mMonitorMock).onEvent(bundleCaptor.capture());
         Bundle eventBundle = bundleCaptor.getValue();
@@ -130,7 +156,8 @@
     }
 
     @Test
-    public void monitorEvent_packageAndExtrasAreNotNull_fillsBundleCorrectlyLong() throws Exception {
+    public void monitorEvent_packageAndExtrasAreNotNull_fillsBundleCorrectlyLong()
+            throws Exception {
         PackageInfo packageInfo = new PackageInfo();
         packageInfo.packageName = "test.package";
         packageInfo.versionCode = 3;
@@ -139,10 +166,10 @@
         extras.putInt("key1", 4);
         extras.putString("key2", "value2");
 
-        IBackupManagerMonitor result = BackupManagerMonitorUtils.monitorEvent(mMonitorMock, 1,
-                packageInfo, 2, extras);
+        mBackupManagerMonitorEventSender.monitorEvent(1, packageInfo, 2, extras);
+        IBackupManagerMonitor monitor = mBackupManagerMonitorEventSender.getMonitor();
 
-        assertThat(result).isEqualTo(mMonitorMock);
+        assertThat(monitor).isEqualTo(mMonitorMock);
         ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
         verify(mMonitorMock).onEvent(bundleCaptor.capture());
         Bundle eventBundle = bundleCaptor.getValue();
@@ -158,15 +185,45 @@
     }
 
     @Test
+    public void monitorEvent_eventOpTypeIsRestore_sendBundleToDumpsys() throws Exception {
+        Bundle extras = new Bundle();
+        extras.putInt(EXTRA_LOG_OPERATION_TYPE, OperationType.RESTORE);
+        mBackupManagerMonitorEventSender.monitorEvent(1, null, 2, extras);
+
+        verify(mBackupManagerMonitorDumpsysUtilsMock).parseBackupManagerMonitorRestoreEventForDumpsys(any(
+                Bundle.class));
+    }
+
+    @Test
+    public void monitorEvent_eventOpTypeIsBackup_doNotSendBundleToDumpsys() throws Exception {
+        Bundle extras = new Bundle();
+        extras.putInt(EXTRA_LOG_OPERATION_TYPE, OperationType.BACKUP);
+        mBackupManagerMonitorEventSender.monitorEvent(1, null, 2, extras);
+
+        verify(mBackupManagerMonitorDumpsysUtilsMock, never())
+                .parseBackupManagerMonitorRestoreEventForDumpsys(any(Bundle.class));
+    }
+
+    @Test
+    public void monitorEvent_eventOpTypeIsUnknown_doNotSendBundleToDumpsys() throws Exception {
+        Bundle extras = new Bundle();
+        extras.putInt(EXTRA_LOG_OPERATION_TYPE, OperationType.UNKNOWN);
+        mBackupManagerMonitorEventSender.monitorEvent(1, null, 2, extras);
+
+        verify(mBackupManagerMonitorDumpsysUtilsMock, never())
+                .parseBackupManagerMonitorRestoreEventForDumpsys(any(Bundle.class));
+    }
+
+    @Test
     public void monitorAgentLoggingResults_onBackup_fillsBundleCorrectly() throws Exception {
         PackageInfo packageInfo = new PackageInfo();
         packageInfo.packageName = "test.package";
         // Mock an agent that returns a logging result.
         IBackupAgent agent = setUpLoggingAgentForOperation(OperationType.BACKUP);
 
-        IBackupManagerMonitor monitor =
-                BackupManagerMonitorUtils.monitorAgentLoggingResults(
-                        mMonitorMock, packageInfo, agent);
+
+        mBackupManagerMonitorEventSender.monitorAgentLoggingResults(packageInfo, agent);
+        IBackupManagerMonitor monitor = mBackupManagerMonitorEventSender.getMonitor();
 
         assertCorrectBundleSentToMonitor(monitor, OperationType.BACKUP);
     }
@@ -178,9 +235,8 @@
         // Mock an agent that returns a logging result.
         IBackupAgent agent = setUpLoggingAgentForOperation(OperationType.RESTORE);
 
-        IBackupManagerMonitor monitor =
-                BackupManagerMonitorUtils.monitorAgentLoggingResults(
-                        mMonitorMock, packageInfo, agent);
+        mBackupManagerMonitorEventSender.monitorAgentLoggingResults(packageInfo, agent);
+        IBackupManagerMonitor monitor = mBackupManagerMonitorEventSender.getMonitor();
 
         assertCorrectBundleSentToMonitor(monitor, OperationType.RESTORE);
     }
@@ -217,9 +273,9 @@
         List<BackupRestoreEventLogger.DataTypeResult> loggingResults = new ArrayList<>();
         loggingResults.add(new BackupRestoreEventLogger.DataTypeResult("testLoggingResult"));
 
-        IBackupManagerMonitor monitor = BackupManagerMonitorUtils.sendAgentLoggingResults(
-                mMonitorMock, packageInfo, loggingResults, OperationType.BACKUP);
-
+        mBackupManagerMonitorEventSender.sendAgentLoggingResults(
+                packageInfo, loggingResults, OperationType.BACKUP);
+        IBackupManagerMonitor monitor = mBackupManagerMonitorEventSender.getMonitor();
         assertCorrectBundleSentToMonitor(monitor, OperationType.BACKUP);
     }
 
@@ -230,8 +286,9 @@
         List<BackupRestoreEventLogger.DataTypeResult> loggingResults = new ArrayList<>();
         loggingResults.add(new BackupRestoreEventLogger.DataTypeResult("testLoggingResult"));
 
-        IBackupManagerMonitor monitor = BackupManagerMonitorUtils.sendAgentLoggingResults(
-                mMonitorMock, packageInfo, loggingResults, OperationType.RESTORE);
+        mBackupManagerMonitorEventSender.sendAgentLoggingResults(
+                packageInfo, loggingResults, OperationType.RESTORE);
+        IBackupManagerMonitor monitor = mBackupManagerMonitorEventSender.getMonitor();
 
         assertCorrectBundleSentToMonitor(monitor, OperationType.RESTORE);
     }
@@ -262,7 +319,7 @@
     public void putMonitoringExtraString_bundleExists_fillsBundleCorrectly() throws Exception {
         Bundle bundle = new Bundle();
 
-        Bundle result = BackupManagerMonitorUtils.putMonitoringExtra(bundle, "key", "value");
+        Bundle result = mBackupManagerMonitorEventSender.putMonitoringExtra(bundle, "key", "value");
 
         assertThat(result).isEqualTo(bundle);
         assertThat(result.size()).isEqualTo(1);
@@ -272,7 +329,7 @@
     @Test
     public void putMonitoringExtraString_bundleDoesNotExist_fillsBundleCorrectly()
             throws Exception {
-        Bundle result = BackupManagerMonitorUtils.putMonitoringExtra(null, "key", "value");
+        Bundle result = mBackupManagerMonitorEventSender.putMonitoringExtra(null, "key", "value");
 
         assertThat(result).isNotNull();
         assertThat(result.size()).isEqualTo(1);
@@ -284,7 +341,7 @@
     public void putMonitoringExtraLong_bundleExists_fillsBundleCorrectly() throws Exception {
         Bundle bundle = new Bundle();
 
-        Bundle result = BackupManagerMonitorUtils.putMonitoringExtra(bundle, "key", 123);
+        Bundle result = mBackupManagerMonitorEventSender.putMonitoringExtra(bundle, "key", 123);
 
         assertThat(result).isEqualTo(bundle);
         assertThat(result.size()).isEqualTo(1);
@@ -293,7 +350,7 @@
 
     @Test
     public void putMonitoringExtraLong_bundleDoesNotExist_fillsBundleCorrectly() throws Exception {
-        Bundle result = BackupManagerMonitorUtils.putMonitoringExtra(null, "key", 123);
+        Bundle result = mBackupManagerMonitorEventSender.putMonitoringExtra(null, "key", 123);
 
         assertThat(result).isNotNull();
         assertThat(result.size()).isEqualTo(1);
@@ -304,7 +361,7 @@
     public void putMonitoringExtraBoolean_bundleExists_fillsBundleCorrectly() throws Exception {
         Bundle bundle = new Bundle();
 
-        Bundle result = BackupManagerMonitorUtils.putMonitoringExtra(bundle, "key", true);
+        Bundle result = mBackupManagerMonitorEventSender.putMonitoringExtra(bundle, "key", true);
 
         assertThat(result).isEqualTo(bundle);
         assertThat(result.size()).isEqualTo(1);
@@ -314,10 +371,10 @@
     @Test
     public void putMonitoringExtraBoolean_bundleDoesNotExist_fillsBundleCorrectly()
             throws Exception {
-        Bundle result = BackupManagerMonitorUtils.putMonitoringExtra(null, "key", true);
+        Bundle result = mBackupManagerMonitorEventSender.putMonitoringExtra(null, "key", true);
 
         assertThat(result).isNotNull();
         assertThat(result.size()).isEqualTo(1);
         assertThat(result.getBoolean("key")).isTrue();
     }
-}
\ No newline at end of file
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/AuthenticationStatsCollectorTest.java b/services/tests/servicestests/src/com/android/server/biometrics/AuthenticationStatsCollectorTest.java
index 746fb53..64e776e 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/AuthenticationStatsCollectorTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthenticationStatsCollectorTest.java
@@ -16,6 +16,8 @@
 
 package com.android.server.biometrics;
 
+import static com.android.server.biometrics.AuthenticationStatsCollector.MAXIMUM_ENROLLMENT_NOTIFICATIONS;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.ArgumentMatchers.any;
@@ -44,9 +46,11 @@
 import com.android.server.biometrics.sensors.BiometricNotification;
 
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
 
 import java.io.File;
 
@@ -54,6 +58,9 @@
 @SmallTest
 public class AuthenticationStatsCollectorTest {
 
+    @Rule
+    public MockitoRule mockitoRule = MockitoJUnit.rule();
+
     private AuthenticationStatsCollector mAuthenticationStatsCollector;
     private static final float FRR_THRESHOLD = 0.2f;
     private static final int USER_ID_1 = 1;
@@ -75,7 +82,6 @@
 
     @Before
     public void setUp() {
-        MockitoAnnotations.initMocks(this);
 
         when(mContext.getResources()).thenReturn(mResources);
         when(mResources.getFraction(eq(R.fraction.config_biometricNotificationFrrThreshold),
@@ -130,6 +136,33 @@
         assertThat(authenticationStats.getEnrollmentNotifications()).isEqualTo(0);
     }
 
+    /**
+     * Our current use case does not need the counters to update after the notification
+     * limit is reached. If we need these counters to continue counting in the future,
+     * a separate privacy review must be done.
+     */
+    @Test
+    public void authenticate_notificationExceeded_mapMustNotBeUpdated() {
+
+        mAuthenticationStatsCollector.setAuthenticationStatsForUser(USER_ID_1,
+                new AuthenticationStats(USER_ID_1, 400 /* totalAttempts */,
+                        40 /* rejectedAttempts */,
+                        MAXIMUM_ENROLLMENT_NOTIFICATIONS /* enrollmentNotifications */,
+                        0 /* modality */));
+
+        mAuthenticationStatsCollector.authenticate(USER_ID_1, false /* authenticated */);
+
+        AuthenticationStats authenticationStats =
+                mAuthenticationStatsCollector.getAuthenticationStatsForUser(USER_ID_1);
+
+        assertThat(authenticationStats.getUserId()).isEqualTo(USER_ID_1);
+        // Assert that counters haven't been updated.
+        assertThat(authenticationStats.getTotalAttempts()).isEqualTo(400);
+        assertThat(authenticationStats.getRejectedAttempts()).isEqualTo(40);
+        assertThat(authenticationStats.getEnrollmentNotifications())
+                .isEqualTo(MAXIMUM_ENROLLMENT_NOTIFICATIONS);
+    }
+
     @Test
     public void authenticate_frrNotExceeded_notificationNotExceeded_shouldNotSendNotification() {
 
@@ -156,7 +189,8 @@
 
         mAuthenticationStatsCollector.setAuthenticationStatsForUser(USER_ID_1,
                 new AuthenticationStats(USER_ID_1, 500 /* totalAttempts */,
-                        400 /* rejectedAttempts */, 2 /* enrollmentNotifications */,
+                        400 /* rejectedAttempts */,
+                        MAXIMUM_ENROLLMENT_NOTIFICATIONS /* enrollmentNotifications */,
                         0 /* modality */));
 
         mAuthenticationStatsCollector.authenticate(USER_ID_1, false /* authenticated */);
@@ -164,12 +198,12 @@
         // Assert that no notification should be sent.
         verify(mBiometricNotification, never()).sendFaceEnrollNotification(any());
         verify(mBiometricNotification, never()).sendFpEnrollNotification(any());
-        // Assert that data has been reset.
+        // Assert that data hasn't been reset.
         AuthenticationStats authenticationStats = mAuthenticationStatsCollector
                 .getAuthenticationStatsForUser(USER_ID_1);
-        assertThat(authenticationStats.getTotalAttempts()).isEqualTo(0);
-        assertThat(authenticationStats.getRejectedAttempts()).isEqualTo(0);
-        assertThat(authenticationStats.getFrr()).isWithin(0f).of(-1.0f);
+        assertThat(authenticationStats.getTotalAttempts()).isEqualTo(500);
+        assertThat(authenticationStats.getRejectedAttempts()).isEqualTo(400);
+        assertThat(authenticationStats.getFrr()).isWithin(0f).of(0.8f);
     }
 
     @Test
diff --git a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/AutoShowTest.java b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/AutoShowTest.java
index 807f0c6..e60d8ef 100644
--- a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/AutoShowTest.java
+++ b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/AutoShowTest.java
@@ -23,6 +23,7 @@
 import static com.android.inputmethod.stresstest.ImeStressTestUtil.callOnMainSync;
 import static com.android.inputmethod.stresstest.ImeStressTestUtil.getWindowAndSoftInputFlagParameters;
 import static com.android.inputmethod.stresstest.ImeStressTestUtil.hasUnfocusableWindowFlags;
+import static com.android.inputmethod.stresstest.ImeStressTestUtil.requestFocusAndVerify;
 import static com.android.inputmethod.stresstest.ImeStressTestUtil.verifyImeAlwaysHiddenWithWindowFlagSet;
 import static com.android.inputmethod.stresstest.ImeStressTestUtil.verifyImeIsAlwaysHidden;
 import static com.android.inputmethod.stresstest.ImeStressTestUtil.verifyWindowAndViewFocus;
@@ -225,7 +226,7 @@
         Intent intent1 = createIntent(mWindowFocusFlags, mSoftInputFlags, Collections.emptyList());
         TestActivity firstActivity = TestActivity.start(intent1);
         // Request view focus after app starts
-        mInstrumentation.runOnMainSync(firstActivity::requestFocus);
+        requestFocusAndVerify(firstActivity);
 
         Intent intent2 =
                 createIntent(
@@ -252,7 +253,7 @@
         Intent intent1 = createIntent(mWindowFocusFlags, mSoftInputFlags, Collections.emptyList());
         TestActivity activity = TestActivity.start(intent1);
         // Request view focus after app starts
-        mInstrumentation.runOnMainSync(activity::requestFocus);
+        requestFocusAndVerify(activity);
 
         // Create second TestActivity
         Intent intent2 =
@@ -284,7 +285,7 @@
         Intent intent = createIntent(mWindowFocusFlags, mSoftInputFlags, Collections.emptyList());
         TestActivity activity = TestActivity.start(intent);
         // Request view focus after app starts
-        mInstrumentation.runOnMainSync(activity::requestFocus);
+        requestFocusAndVerify(activity);
 
         // Find the editText and click it
         UiObject2 editTextUiObject =
diff --git a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/DefaultImeVisibilityTest.java b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/DefaultImeVisibilityTest.java
index 320daee..2ac25f2 100644
--- a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/DefaultImeVisibilityTest.java
+++ b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/DefaultImeVisibilityTest.java
@@ -23,6 +23,7 @@
 import static com.android.inputmethod.stresstest.ImeStressTestUtil.REQUEST_FOCUS_ON_CREATE;
 import static com.android.inputmethod.stresstest.ImeStressTestUtil.TestActivity.createIntent;
 import static com.android.inputmethod.stresstest.ImeStressTestUtil.callOnMainSync;
+import static com.android.inputmethod.stresstest.ImeStressTestUtil.requestFocusAndVerify;
 import static com.android.inputmethod.stresstest.ImeStressTestUtil.verifyWindowAndViewFocus;
 import static com.android.inputmethod.stresstest.ImeStressTestUtil.waitOnMainUntilImeIsHidden;
 import static com.android.inputmethod.stresstest.ImeStressTestUtil.waitOnMainUntilImeIsShown;
@@ -96,7 +97,7 @@
         UiDevice uiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
         eventually(
                 () ->
-                        assertWithMessage("Display rotation should be updated.")
+                        assertWithMessage("Display rotation should have been updated")
                                 .that(uiDevice.getDisplayRotation())
                                 .isEqualTo(mIsPortrait ? 0 : 1),
                 TIMEOUT);
@@ -104,7 +105,7 @@
         for (int i = 0; i < NUM_TEST_ITERATIONS; i++) {
             // TODO(b/291752364): Remove the explicit focus request once the issue with view focus
             //  change between fullscreen IME and actual editText is fixed.
-            callOnMainSync(editText::requestFocus);
+            requestFocusAndVerify(activity);
             verifyWindowAndViewFocus(editText, true, true);
             callOnMainSync(activity::showImeWithInputMethodManager);
             waitOnMainUntilImeIsShown(editText);
diff --git a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeOpenCloseStressTest.java b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeOpenCloseStressTest.java
index 5c02124..5368025 100644
--- a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeOpenCloseStressTest.java
+++ b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeOpenCloseStressTest.java
@@ -29,6 +29,7 @@
 import static com.android.inputmethod.stresstest.ImeStressTestUtil.getWindowAndSoftInputFlagParameters;
 import static com.android.inputmethod.stresstest.ImeStressTestUtil.hasUnfocusableWindowFlags;
 import static com.android.inputmethod.stresstest.ImeStressTestUtil.isImeShown;
+import static com.android.inputmethod.stresstest.ImeStressTestUtil.requestFocusAndVerify;
 import static com.android.inputmethod.stresstest.ImeStressTestUtil.verifyImeAlwaysHiddenWithWindowFlagSet;
 import static com.android.inputmethod.stresstest.ImeStressTestUtil.verifyImeIsAlwaysHidden;
 import static com.android.inputmethod.stresstest.ImeStressTestUtil.verifyWindowAndViewFocus;
@@ -38,6 +39,9 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.junit.Assume.assumeFalse;
+import static org.junit.Assume.assumeTrue;
+
 import android.app.Instrumentation;
 import android.content.Intent;
 import android.os.Build;
@@ -96,7 +100,8 @@
         Intent intent = createIntent(mWindowFocusFlags, mSoftInputFlags, Collections.emptyList());
         TestActivity activity = TestActivity.start(intent);
         // Request focus after app starts to avoid triggering auto-show behavior.
-        mInstrumentation.runOnMainSync(activity::requestFocus);
+        requestFocusAndVerify(activity);
+
         // Test only once if window flags set to save time.
         int iterNum = hasUnfocusableWindowFlags(activity) ? 1 : NUM_TEST_ITERATIONS;
         for (int i = 0; i < iterNum; i++) {
@@ -106,21 +111,19 @@
             verifyShowBehavior(activity);
 
             callOnMainSync(activity::hideImeWithInputMethodManager);
-
             verifyHideBehavior(activity);
         }
     }
 
     @Test
     public void testShowHideWithInputMethodManager_waitingAnimationEnd() {
+        assumeFalse("Has unfocusable window flags", hasUnfocusableWindowFlags(mWindowFocusFlags));
+
         Intent intent = createIntent(mWindowFocusFlags, mSoftInputFlags, Collections.emptyList());
         TestActivity activity = TestActivity.start(intent);
         // Request focus after app starts to avoid triggering auto-show behavior.
-        mInstrumentation.runOnMainSync(activity::requestFocus);
+        requestFocusAndVerify(activity);
 
-        if (hasUnfocusableWindowFlags(activity)) {
-            return; // Skip to save time.
-        }
         activity.enableAnimationMonitoring();
         EditText editText = activity.getEditText();
         for (int i = 0; i < NUM_TEST_ITERATIONS; i++) {
@@ -128,12 +131,12 @@
             Log.i(TAG, msgPrefix + "start");
             callOnMainSync(activity::showImeWithInputMethodManager);
             waitOnMainUntil(
-                    msgPrefix + "IME should be visible",
+                    msgPrefix + "IME should have been shown",
                     () -> !activity.isAnimating() && isImeShown(editText));
 
             callOnMainSync(activity::hideImeWithInputMethodManager);
             waitOnMainUntil(
-                    msgPrefix + "IME should be hidden",
+                    msgPrefix + "IME should have been hidden",
                     () -> !activity.isAnimating() && !isImeShown(editText));
         }
     }
@@ -141,13 +144,13 @@
     @Test
     public void testShowHideWithInputMethodManager_intervalAfterHide() {
         // Regression test for b/221483132
+        assumeFalse("Has unfocusable window flags", hasUnfocusableWindowFlags(mWindowFocusFlags));
+
         Intent intent = createIntent(mWindowFocusFlags, mSoftInputFlags, Collections.emptyList());
         TestActivity activity = TestActivity.start(intent);
         // Request focus after app starts to avoid triggering auto-show behavior.
-        mInstrumentation.runOnMainSync(activity::requestFocus);
-        if (hasUnfocusableWindowFlags(activity)) {
-            return; // Skip to save time.
-        }
+        requestFocusAndVerify(activity);
+
         // Intervals = 10, 20, 30, ..., 100, 150, 200, ...
         List<Integer> intervals = new ArrayList<>();
         for (int i = 10; i < 100; i += 10) intervals.add(i);
@@ -165,14 +168,12 @@
 
     @Test
     public void testShowHideWithInputMethodManager_inSameFrame() {
+        assumeFalse("Has unfocusable window flags", hasUnfocusableWindowFlags(mWindowFocusFlags));
         Intent intent = createIntent(mWindowFocusFlags, mSoftInputFlags, Collections.emptyList());
         TestActivity activity = TestActivity.start(intent);
         // Request focus after app starts to avoid triggering auto-show behavior.
-        mInstrumentation.runOnMainSync(activity::requestFocus);
+        requestFocusAndVerify(activity);
 
-        if (hasUnfocusableWindowFlags(activity)) {
-            return; // Skip to save time.
-        }
         // hidden -> show -> hide
         mInstrumentation.runOnMainSync(
                 () -> {
@@ -256,13 +257,12 @@
 
     @Test
     public void testShowHideWithWindowInsetsController_waitingVisibilityChange() {
-        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
-            return;
-        }
+        assumeTrue("Is at least Android R", Build.VERSION.SDK_INT >= Build.VERSION_CODES.R);
         Intent intent = createIntent(mWindowFocusFlags, mSoftInputFlags, Collections.emptyList());
         TestActivity activity = TestActivity.start(intent);
         // Request focus after app starts to avoid triggering auto-show behavior.
-        mInstrumentation.runOnMainSync(activity::requestFocus);
+        requestFocusAndVerify(activity);
+
         // Test only once if window flags set to save time.
         int iterNum = hasUnfocusableWindowFlags(activity) ? 1 : NUM_TEST_ITERATIONS;
         for (int i = 0; i < iterNum; i++) {
@@ -277,17 +277,13 @@
 
     @Test
     public void testShowHideWithWindowInsetsController_waitingAnimationEnd() {
-        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
-            return;
-        }
+        assumeTrue("Is at least Android R", Build.VERSION.SDK_INT >= Build.VERSION_CODES.R);
+        assumeFalse("Has unfocusable window flags", hasUnfocusableWindowFlags(mWindowFocusFlags));
         Intent intent = createIntent(mWindowFocusFlags, mSoftInputFlags, Collections.emptyList());
         TestActivity activity = TestActivity.start(intent);
         // Request focus after app starts to avoid triggering auto-show behavior.
-        mInstrumentation.runOnMainSync(activity::requestFocus);
+        requestFocusAndVerify(activity);
 
-        if (hasUnfocusableWindowFlags(activity)) {
-            return; // Skip to save time.
-        }
         activity.enableAnimationMonitoring();
         EditText editText = activity.getEditText();
         for (int i = 0; i < NUM_TEST_ITERATIONS; i++) {
@@ -295,29 +291,25 @@
             Log.i(TAG, msgPrefix + "start");
             mInstrumentation.runOnMainSync(activity::showImeWithWindowInsetsController);
             waitOnMainUntil(
-                    msgPrefix + "IME should be visible",
+                    msgPrefix + "IME should have been shown",
                     () -> !activity.isAnimating() && isImeShown(editText));
 
             mInstrumentation.runOnMainSync(activity::hideImeWithWindowInsetsController);
             waitOnMainUntil(
-                    msgPrefix + "IME should be hidden",
+                    msgPrefix + "IME should have been hidden",
                     () -> !activity.isAnimating() && !isImeShown(editText));
         }
     }
 
     @Test
     public void testShowHideWithWindowInsetsController_intervalAfterHide() {
-        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
-            return;
-        }
+        assumeTrue("Is at least Android R", Build.VERSION.SDK_INT >= Build.VERSION_CODES.R);
+        assumeFalse("Has unfocusable window flags", hasUnfocusableWindowFlags(mWindowFocusFlags));
         Intent intent = createIntent(mWindowFocusFlags, mSoftInputFlags, Collections.emptyList());
         TestActivity activity = TestActivity.start(intent);
         // Request focus after app starts to avoid triggering auto-show behavior.
-        mInstrumentation.runOnMainSync(activity::requestFocus);
+        requestFocusAndVerify(activity);
 
-        if (hasUnfocusableWindowFlags(activity)) {
-            return; // Skip to save time.
-        }
         // Intervals = 10, 20, 30, ..., 100, 150, 200, ...
         List<Integer> intervals = new ArrayList<>();
         for (int i = 10; i < 100; i += 10) intervals.add(i);
@@ -335,17 +327,13 @@
 
     @Test
     public void testShowHideWithWindowInsetsController_inSameFrame() {
-        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
-            return;
-        }
+        assumeTrue("Is at least Android R", Build.VERSION.SDK_INT >= Build.VERSION_CODES.R);
+        assumeFalse("Has unfocusable window flags", hasUnfocusableWindowFlags(mWindowFocusFlags));
         Intent intent = createIntent(mWindowFocusFlags, mSoftInputFlags, Collections.emptyList());
         TestActivity activity = TestActivity.start(intent);
         // Request focus after app starts to avoid triggering auto-show behavior.
-        mInstrumentation.runOnMainSync(activity::requestFocus);
+        requestFocusAndVerify(activity);
 
-        if (hasUnfocusableWindowFlags(activity)) {
-            return; // Skip to save time.
-        }
         // hidden -> show -> hide
         mInstrumentation.runOnMainSync(
                 () -> {
@@ -377,9 +365,7 @@
 
     @Test
     public void testShowWithWindowInsetsController_onCreate_requestFocus() {
-        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
-            return;
-        }
+        assumeTrue("Is at least Android R", Build.VERSION.SDK_INT >= Build.VERSION_CODES.R);
         // Show with InputMethodManager at onCreate()
         Intent intent =
                 createIntent(
@@ -394,10 +380,8 @@
 
     @Test
     public void testShowWithWindowInsetsController_onCreate_notRequestFocus() {
-        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
-            return;
-        }
-        // Show and hide with InputMethodManager at onCreate()
+        assumeTrue("Is at least Android R", Build.VERSION.SDK_INT >= Build.VERSION_CODES.R);
+        // Show and hide with WindowInsetsController at onCreate()
         Intent intent =
                 createIntent(
                         mWindowFocusFlags,
@@ -411,10 +395,8 @@
 
     @Test
     public void testShowWithWindowInsetsController_afterStart_notRequestFocus() {
-        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
-            return;
-        }
-        // Show and hide with InputMethodManager at onCreate()
+        assumeTrue("Is at least Android R", Build.VERSION.SDK_INT >= Build.VERSION_CODES.R);
+        // Show and hide with WindowInsetsController at onCreate()
         Intent intent = createIntent(mWindowFocusFlags, mSoftInputFlags, Collections.emptyList());
         TestActivity activity = TestActivity.start(intent);
         mInstrumentation.runOnMainSync(activity::showImeWithWindowInsetsController);
@@ -425,7 +407,8 @@
 
     /**
      * Test IME hidden by calling show and hide IME consecutively with
-     * {@link android.view.WindowInsetsController} APIs in {@link android.app.Activity#onCreate}.
+     * {@link android.view.WindowInsetsController} APIs in
+     * {@link android.app.Activity#onCreate}.
      *
      * <p> Note for developers: Use {@link WindowManager.LayoutParams#SOFT_INPUT_STATE_UNCHANGED}
      * window flag to avoid some softInputMode visibility flags may take presence over
@@ -436,13 +419,11 @@
      */
     @Test
     public void testHideWithWindowInsetsController_onCreate_requestFocus() {
-        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
-            return;
-        }
+        assumeTrue("Is at least Android R", Build.VERSION.SDK_INT >= Build.VERSION_CODES.R);
         if (mSoftInputFlags != SOFT_INPUT_STATE_UNCHANGED) {
             return;
         }
-        // Show and hide with InputMethodManager at onCreate()
+        // Show and hide with WindowInsetsController at onCreate()
         Intent intent =
                 createIntent(
                         mWindowFocusFlags,
diff --git a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeStressTestUtil.java b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeStressTestUtil.java
index 12556bc..c0c60ef 100644
--- a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeStressTestUtil.java
+++ b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeStressTestUtil.java
@@ -40,6 +40,7 @@
 import android.widget.EditText;
 import android.widget.LinearLayout;
 
+import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.test.platform.app.InstrumentationRegistry;
 
@@ -149,6 +150,14 @@
     }
 
     /**
+     * Requests EditText view focus on the main thread, and assert this returns {@code true}.
+     */
+    public static void requestFocusAndVerify(TestActivity activity) {
+        boolean result = callOnMainSync(activity::requestFocus);
+        assertWithMessage("View focus request should have succeeded").that(result).isTrue();
+    }
+
+    /**
      * Waits until {@code pred} returns true, or throws on timeout.
      *
      * <p>The given {@code pred} will be called on the main thread.
@@ -161,7 +170,7 @@
     public static void waitOnMainUntilImeIsShown(View view) {
         eventually(
                 () ->
-                        assertWithMessage("IME should be shown")
+                        assertWithMessage("IME should have been shown")
                                 .that(callOnMainSync(() -> isImeShown(view)))
                                 .isTrue(),
                 TIMEOUT);
@@ -171,27 +180,28 @@
     public static void waitOnMainUntilImeIsHidden(View view) {
         eventually(
                 () ->
-                        assertWithMessage("IME should be hidden")
+                        assertWithMessage("IME should have been hidden")
                                 .that(callOnMainSync(() -> isImeShown(view)))
                                 .isFalse(),
                 TIMEOUT);
     }
 
-    /** Waits until window get focus, or throws on timeout. */
+    /** Waits until window gains focus, or throws on timeout. */
     public static void waitOnMainUntilWindowGainsFocus(View view) {
         eventually(
                 () ->
-                        assertWithMessage("Window should gain focus")
+                        assertWithMessage(
+                                "Window should have gained focus; value of hasWindowFocus:")
                                 .that(callOnMainSync(view::hasWindowFocus))
                                 .isTrue(),
                 TIMEOUT);
     }
 
-    /** Waits until view get focus, or throws on timeout. */
+    /** Waits until view gains focus, or throws on timeout. */
     public static void waitOnMainUntilViewGainsFocus(View view) {
         eventually(
                 () ->
-                        assertWithMessage("View should gain focus")
+                        assertWithMessage("View should have gained focus; value of hasFocus:")
                                 .that(callOnMainSync(view::hasFocus))
                                 .isTrue(),
                 TIMEOUT);
@@ -201,7 +211,7 @@
     public static void verifyImeIsAlwaysHidden(View view) {
         always(
                 () ->
-                        assertWithMessage("IME should be hidden")
+                        assertWithMessage("IME should have been hidden")
                                 .that(callOnMainSync(() -> isImeShown(view)))
                                 .isFalse(),
                 TIMEOUT);
@@ -211,7 +221,8 @@
     public static void verifyWindowNeverGainsFocus(View view) {
         always(
                 () ->
-                        assertWithMessage("window should never gain focus")
+                        assertWithMessage(
+                                "Window should not have gained focus; value of hasWindowFocus:")
                                 .that(callOnMainSync(view::hasWindowFocus))
                                 .isFalse(),
                 TIMEOUT);
@@ -221,7 +232,7 @@
     public static void verifyViewNeverGainsFocus(View view) {
         always(
                 () ->
-                        assertWithMessage("view should never gain ime focus")
+                        assertWithMessage("View should not have gained focus; value of hasFocus:")
                                 .that(callOnMainSync(view::hasFocus))
                                 .isFalse(),
                 TIMEOUT);
@@ -254,8 +265,23 @@
         }
     }
 
+    /**
+     * Returns {@code true} if the activity can't receive IME focus, based on its window flags,
+     * and {@code false} otherwise.
+     *
+     * @param activity the activity to check.
+     */
     public static boolean hasUnfocusableWindowFlags(Activity activity) {
-        int windowFlags = activity.getWindow().getAttributes().flags;
+        return hasUnfocusableWindowFlags(activity.getWindow().getAttributes().flags);
+    }
+
+    /**
+     * Returns {@code true} if the activity can't receive IME focus, based on its window flags,
+     * and {@code false} otherwise.
+     *
+     * @param windowFlags the window flags to check.
+     */
+    public static boolean hasUnfocusableWindowFlags(int windowFlags) {
         return (windowFlags & LayoutParams.FLAG_NOT_FOCUSABLE) != 0
                 || (windowFlags & LayoutParams.FLAG_ALT_FOCUSABLE_IM) != 0
                 || (windowFlags & LayoutParams.FLAG_LOCAL_FOCUS_MODE) != 0;
@@ -302,22 +328,26 @@
 
         private final WindowInsetsAnimation.Callback mWindowInsetsAnimationCallback =
                 new WindowInsetsAnimation.Callback(DISPATCH_MODE_STOP) {
+                    @NonNull
                     @Override
                     public WindowInsetsAnimation.Bounds onStart(
-                            WindowInsetsAnimation animation, WindowInsetsAnimation.Bounds bounds) {
+                            @NonNull WindowInsetsAnimation animation,
+                            @NonNull WindowInsetsAnimation.Bounds bounds) {
                         mIsAnimating = true;
                         return super.onStart(animation, bounds);
                     }
 
                     @Override
-                    public void onEnd(WindowInsetsAnimation animation) {
+                    public void onEnd(@NonNull WindowInsetsAnimation animation) {
                         super.onEnd(animation);
                         mIsAnimating = false;
                     }
 
+                    @NonNull
                     @Override
                     public WindowInsets onProgress(
-                            WindowInsets insets, List<WindowInsetsAnimation> runningAnimations) {
+                            @NonNull WindowInsets insets,
+                            @NonNull List<WindowInsetsAnimation> runningAnimations) {
                         return insets;
                     }
                 };
@@ -394,7 +424,7 @@
                     getInputMethodManager()
                             .showSoftInput(mEditText, 0 /* flags */);
             if (showResult) {
-                Log.i(TAG, "IMM#showSoftInput successfully");
+                Log.i(TAG, "IMM#showSoftInput succeeded");
             } else {
                 Log.i(TAG, "IMM#showSoftInput failed");
             }
@@ -407,7 +437,7 @@
                     getInputMethodManager()
                             .hideSoftInputFromWindow(mEditText.getWindowToken(), 0 /* flags */);
             if (hideResult) {
-                Log.i(TAG, "IMM#hideSoftInput successfully");
+                Log.i(TAG, "IMM#hideSoftInput succeeded");
             } else {
                 Log.i(TAG, "IMM#hideSoftInput failed");
             }
@@ -421,7 +451,7 @@
             }
             Log.i(TAG, "showImeWithWIC()");
             WindowInsetsController windowInsetsController = mEditText.getWindowInsetsController();
-            assertWithMessage("WindowInsetsController shouldn't be null.")
+            assertWithMessage("WindowInsetsController")
                     .that(windowInsetsController)
                     .isNotNull();
             windowInsetsController.show(WindowInsets.Type.ime());
@@ -434,7 +464,7 @@
             }
             Log.i(TAG, "hideImeWithWIC()");
             WindowInsetsController windowInsetsController = mEditText.getWindowInsetsController();
-            assertWithMessage("WindowInsetsController shouldn't be null.")
+            assertWithMessage("WindowInsetsController")
                     .that(windowInsetsController)
                     .isNotNull();
             windowInsetsController.hide(WindowInsets.Type.ime());
@@ -482,13 +512,14 @@
             return mIsAnimating;
         }
 
-        public void requestFocus() {
-            boolean requestFocusResult = getEditText().requestFocus();
+        public boolean requestFocus() {
+            boolean requestFocusResult = mEditText.requestFocus();
             if (requestFocusResult) {
-                Log.i(TAG, "Request focus successfully");
+                Log.i(TAG, "View#requestFocus succeeded");
             } else {
-                Log.i(TAG, "Request focus failed");
+                Log.i(TAG, "View#requestFocus failed");
             }
+            return requestFocusResult;
         }
     }
 }