Merge "Changes AM.startUserInBgOnSecondaryDisplay() to check if display is valid."
diff --git a/core/java/android/companion/virtual/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java
index d4c9a42..fadfa5c 100644
--- a/core/java/android/companion/virtual/VirtualDeviceManager.java
+++ b/core/java/android/companion/virtual/VirtualDeviceManager.java
@@ -445,8 +445,8 @@
                 @Nullable Executor executor,
                 @Nullable AudioConfigurationChangeCallback callback) {
             if (mVirtualAudioDevice == null) {
-                mVirtualAudioDevice = new VirtualAudioDevice(
-                        mContext, mVirtualDevice, display, executor, callback);
+                mVirtualAudioDevice = new VirtualAudioDevice(mContext, mVirtualDevice, display,
+                        executor, callback, () -> mVirtualAudioDevice = null);
             }
             return mVirtualAudioDevice;
         }
diff --git a/core/java/android/companion/virtual/audio/VirtualAudioDevice.java b/core/java/android/companion/virtual/audio/VirtualAudioDevice.java
index 0db7b5f..e200a11 100644
--- a/core/java/android/companion/virtual/audio/VirtualAudioDevice.java
+++ b/core/java/android/companion/virtual/audio/VirtualAudioDevice.java
@@ -64,11 +64,24 @@
         void onRecordingConfigChanged(@NonNull List<AudioRecordingConfiguration> configs);
     }
 
+    /**
+     * Interface to be notified when {@link #close()} is called.
+     *
+     * @hide
+     */
+    public interface CloseListener {
+        /**
+         * Notifies when {@link #close()} is called.
+         */
+        void onClosed();
+    }
+
     private final Context mContext;
     private final IVirtualDevice mVirtualDevice;
     private final VirtualDisplay mVirtualDisplay;
     private final AudioConfigurationChangeCallback mCallback;
     private final Executor mExecutor;
+    private final CloseListener mListener;
     @Nullable
     private VirtualAudioSession mOngoingSession;
 
@@ -77,12 +90,13 @@
      */
     public VirtualAudioDevice(Context context, IVirtualDevice virtualDevice,
             @NonNull VirtualDisplay virtualDisplay, @Nullable Executor executor,
-            @Nullable AudioConfigurationChangeCallback callback) {
+            @Nullable AudioConfigurationChangeCallback callback, @Nullable CloseListener listener) {
         mContext = context;
         mVirtualDevice = virtualDevice;
         mVirtualDisplay = virtualDisplay;
         mExecutor = executor;
         mCallback = callback;
+        mListener = listener;
     }
 
     /**
@@ -169,6 +183,10 @@
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
             }
+
+            if (mListener != null) {
+                mListener.onClosed();
+            }
         }
     }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt
index 2ccf121..f9d1e88 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt
@@ -46,6 +46,66 @@
     }
 }
 
+fun FlickerTestParameter.splitScreenEntered(
+    component1: IComponentMatcher,
+    component2: IComponentMatcher,
+    fromOtherApp: Boolean
+) {
+    if (fromOtherApp) {
+        appWindowIsInvisibleAtStart(component1)
+    } else {
+        appWindowIsVisibleAtStart(component1)
+    }
+    appWindowIsInvisibleAtStart(component2)
+    splitScreenDividerIsInvisibleAtStart()
+
+    appWindowIsVisibleAtEnd(component1)
+    appWindowIsVisibleAtEnd(component2)
+    splitScreenDividerIsVisibleAtEnd()
+}
+
+fun FlickerTestParameter.splitScreenDismissed(
+    component1: IComponentMatcher,
+    component2: IComponentMatcher,
+    toHome: Boolean
+) {
+    appWindowIsVisibleAtStart(component1)
+    appWindowIsVisibleAtStart(component2)
+    splitScreenDividerIsVisibleAtStart()
+
+    appWindowIsInvisibleAtEnd(component1)
+    if (toHome) {
+        appWindowIsInvisibleAtEnd(component2)
+    } else {
+        appWindowIsVisibleAtEnd(component2)
+    }
+    splitScreenDividerIsInvisibleAtEnd()
+}
+
+fun FlickerTestParameter.splitScreenDividerIsVisibleAtStart() {
+    assertLayersStart {
+        this.isVisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
+    }
+}
+
+fun FlickerTestParameter.splitScreenDividerIsVisibleAtEnd() {
+    assertLayersEnd {
+        this.isVisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
+    }
+}
+
+fun FlickerTestParameter.splitScreenDividerIsInvisibleAtStart() {
+    assertLayersStart {
+        this.isInvisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
+    }
+}
+
+fun FlickerTestParameter.splitScreenDividerIsInvisibleAtEnd() {
+    assertLayersEnd {
+        this.isInvisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
+    }
+}
+
 fun FlickerTestParameter.splitScreenDividerBecomesVisible() {
     layerBecomesVisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
 }
@@ -271,6 +331,14 @@
     }
 }
 
+fun FlickerTestParameter.appWindowIsVisibleAtStart(
+    component: IComponentMatcher
+) {
+    assertWmStart {
+        this.isAppWindowVisible(component)
+    }
+}
+
 fun FlickerTestParameter.appWindowIsVisibleAtEnd(
     component: IComponentMatcher
 ) {
@@ -279,6 +347,22 @@
     }
 }
 
+fun FlickerTestParameter.appWindowIsInvisibleAtStart(
+    component: IComponentMatcher
+) {
+    assertWmStart {
+        this.isAppWindowInvisible(component)
+    }
+}
+
+fun FlickerTestParameter.appWindowIsInvisibleAtEnd(
+    component: IComponentMatcher
+) {
+    assertWmEnd {
+        this.isAppWindowInvisible(component)
+    }
+}
+
 fun FlickerTestParameter.appWindowKeepVisible(
     component: IComponentMatcher
 ) {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt
index 7dbd279..5b044e3 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt
@@ -27,9 +27,13 @@
 import com.android.server.wm.flicker.annotation.Group1
 import com.android.server.wm.flicker.dsl.FlickerBuilder
 import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT
+import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
+import com.android.wm.shell.flicker.appWindowIsVisibleAtStart
 import com.android.wm.shell.flicker.appWindowKeepVisible
 import com.android.wm.shell.flicker.layerKeepVisible
 import com.android.wm.shell.flicker.splitAppLayerBoundsKeepVisible
+import com.android.wm.shell.flicker.splitScreenDividerIsVisibleAtEnd
+import com.android.wm.shell.flicker.splitScreenDividerIsVisibleAtStart
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -64,36 +68,44 @@
     @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
+    fun cujCompleted() {
+        testSpec.appWindowIsVisibleAtStart(primaryApp)
+        testSpec.appWindowIsVisibleAtStart(textEditApp)
+        testSpec.splitScreenDividerIsVisibleAtStart()
+
+        testSpec.appWindowIsVisibleAtEnd(primaryApp)
+        testSpec.appWindowIsVisibleAtEnd(textEditApp)
+        testSpec.splitScreenDividerIsVisibleAtEnd()
+
+        // The validation of copied text is already done in SplitScreenUtils.copyContentInSplit()
+    }
+
+    @Presubmit
+    @Test
     fun splitScreenDividerKeepVisible() = testSpec.layerKeepVisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun primaryAppLayerKeepVisible() = testSpec.layerKeepVisible(primaryApp)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun textEditAppLayerKeepVisible() = testSpec.layerKeepVisible(textEditApp)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun primaryAppBoundsKeepVisible() = testSpec.splitAppLayerBoundsKeepVisible(
         primaryApp, landscapePosLeft = tapl.isTablet, portraitPosTop = false)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun textEditAppBoundsKeepVisible() = testSpec.splitAppLayerBoundsKeepVisible(
         textEditApp, landscapePosLeft = !tapl.isTablet, portraitPosTop = true)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun primaryAppWindowKeepVisible() = testSpec.appWindowKeepVisible(primaryApp)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun textEditAppWindowKeepVisible() = testSpec.appWindowKeepVisible(textEditApp)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByDivider.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByDivider.kt
index 3646fd7..838026f 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByDivider.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByDivider.kt
@@ -33,6 +33,7 @@
 import com.android.wm.shell.flicker.layerBecomesInvisible
 import com.android.wm.shell.flicker.layerIsVisibleAtEnd
 import com.android.wm.shell.flicker.splitAppLayerBoundsBecomesInvisible
+import com.android.wm.shell.flicker.splitScreenDismissed
 import com.android.wm.shell.flicker.splitScreenDividerBecomesInvisible
 import org.junit.FixMethodOrder
 import org.junit.Test
@@ -75,25 +76,25 @@
     @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
+    fun cujCompleted() = testSpec.splitScreenDismissed(primaryApp, secondaryApp, toHome = false)
+
+    @Presubmit
+    @Test
     fun splitScreenDividerBecomesInvisible() = testSpec.splitScreenDividerBecomesInvisible()
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun primaryAppLayerBecomesInvisible() = testSpec.layerBecomesInvisible(primaryApp)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun secondaryAppLayerIsVisibleAtEnd() = testSpec.layerIsVisibleAtEnd(secondaryApp)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun primaryAppBoundsBecomesInvisible() = testSpec.splitAppLayerBoundsBecomesInvisible(
         primaryApp, landscapePosLeft = tapl.isTablet, portraitPosTop = false)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun secondaryAppBoundsIsFullscreenAtEnd() {
@@ -116,12 +117,10 @@
         }
     }
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun primaryAppWindowBecomesInvisible() = testSpec.appWindowBecomesInvisible(primaryApp)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun secondaryAppWindowIsVisibleAtEnd() = testSpec.appWindowIsVisibleAtEnd(secondaryApp)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt
index 80abedd..a764206 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt
@@ -30,6 +30,7 @@
 import com.android.wm.shell.flicker.appWindowBecomesInvisible
 import com.android.wm.shell.flicker.layerBecomesInvisible
 import com.android.wm.shell.flicker.splitAppLayerBoundsBecomesInvisible
+import com.android.wm.shell.flicker.splitScreenDismissed
 import com.android.wm.shell.flicker.splitScreenDividerBecomesInvisible
 import org.junit.FixMethodOrder
 import org.junit.Test
@@ -64,13 +65,15 @@
                     .waitForAndVerify()
             }
         }
-
     @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
+    fun cujCompleted() = testSpec.splitScreenDismissed(primaryApp, secondaryApp, toHome = true)
+
+    @Presubmit
+    @Test
     fun splitScreenDividerBecomesInvisible() = testSpec.splitScreenDividerBecomesInvisible()
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun primaryAppLayerBecomesInvisible() = testSpec.layerBecomesInvisible(primaryApp)
@@ -86,18 +89,15 @@
     fun primaryAppBoundsBecomesInvisible() = testSpec.splitAppLayerBoundsBecomesInvisible(
         primaryApp, landscapePosLeft = tapl.isTablet, portraitPosTop = false)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun secondaryAppBoundsBecomesInvisible() = testSpec.splitAppLayerBoundsBecomesInvisible(
         secondaryApp, landscapePosLeft = !tapl.isTablet, portraitPosTop = true)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun primaryAppWindowBecomesInvisible() = testSpec.appWindowBecomesInvisible(primaryApp)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun secondaryAppWindowBecomesInvisible() = testSpec.appWindowBecomesInvisible(secondaryApp)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt
index 2915787..ba02317 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt
@@ -28,9 +28,13 @@
 import com.android.server.wm.flicker.annotation.Group1
 import com.android.server.wm.flicker.dsl.FlickerBuilder
 import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT
+import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
+import com.android.wm.shell.flicker.appWindowIsVisibleAtStart
 import com.android.wm.shell.flicker.appWindowKeepVisible
 import com.android.wm.shell.flicker.layerKeepVisible
 import com.android.wm.shell.flicker.splitAppLayerBoundsChanges
+import com.android.wm.shell.flicker.splitScreenDividerIsVisibleAtEnd
+import com.android.wm.shell.flicker.splitScreenDividerIsVisibleAtStart
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -63,14 +67,27 @@
     @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
+    fun cujCompleted() {
+        testSpec.appWindowIsVisibleAtStart(primaryApp)
+        testSpec.appWindowIsVisibleAtStart(secondaryApp)
+        testSpec.splitScreenDividerIsVisibleAtStart()
+
+        testSpec.appWindowIsVisibleAtEnd(primaryApp)
+        testSpec.appWindowIsVisibleAtEnd(secondaryApp)
+        testSpec.splitScreenDividerIsVisibleAtEnd()
+
+        // TODO(b/246490534): Add validation for resized app after withAppTransitionIdle is
+        // robust enough to get the correct end state.
+    }
+
+    @Presubmit
+    @Test
     fun splitScreenDividerKeepVisible() = testSpec.layerKeepVisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun primaryAppLayerKeepVisible() = testSpec.layerKeepVisible(primaryApp)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun secondaryAppLayerVisibilityChanges() {
@@ -83,23 +100,19 @@
         }
     }
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun primaryAppWindowKeepVisible() = testSpec.appWindowKeepVisible(primaryApp)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun secondaryAppWindowKeepVisible() = testSpec.appWindowKeepVisible(secondaryApp)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun primaryAppBoundsChanges() = testSpec.splitAppLayerBoundsChanges(
         primaryApp, landscapePosLeft = true, portraitPosTop = false)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun secondaryAppBoundsChanges() = testSpec.splitAppLayerBoundsChanges(
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt
index 8e041a7..bb44789 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt
@@ -35,6 +35,7 @@
 import com.android.wm.shell.flicker.splitAppLayerBoundsBecomesVisibleByDrag
 import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
 import com.android.wm.shell.flicker.splitScreenDividerBecomesVisible
+import com.android.wm.shell.flicker.splitScreenEntered
 import org.junit.Assume
 import org.junit.Before
 import org.junit.FixMethodOrder
@@ -82,13 +83,16 @@
     @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
+    fun cujCompleted() = testSpec.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = false)
+
+    @Presubmit
+    @Test
     fun splitScreenDividerBecomesVisible() {
         Assume.assumeFalse(isShellTransitionsEnabled)
         testSpec.splitScreenDividerBecomesVisible()
     }
 
     // TODO(b/245472831): Back to splitScreenDividerBecomesVisible after shell transition ready.
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun splitScreenDividerIsVisibleAtEnd_ShellTransit() {
@@ -98,12 +102,10 @@
         }
     }
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun primaryAppLayerIsVisibleAtEnd() = testSpec.layerIsVisibleAtEnd(primaryApp)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun secondaryAppLayerBecomesVisible() {
@@ -127,24 +129,20 @@
         testSpec.layerBecomesVisible(secondaryApp)
     }
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun primaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
         primaryApp, landscapePosLeft = false, portraitPosTop = false)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun secondaryAppBoundsBecomesVisible() = testSpec.splitAppLayerBoundsBecomesVisibleByDrag(
         secondaryApp)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun primaryAppWindowIsVisibleAtEnd() = testSpec.appWindowIsVisibleAtEnd(primaryApp)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun secondaryAppWindowBecomesVisible() = testSpec.appWindowBecomesVisible(secondaryApp)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt
index 2ee12f1..e208196 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt
@@ -34,6 +34,7 @@
 import com.android.wm.shell.flicker.splitAppLayerBoundsBecomesVisibleByDrag
 import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
 import com.android.wm.shell.flicker.splitScreenDividerBecomesVisible
+import com.android.wm.shell.flicker.splitScreenEntered
 import org.junit.Assume
 import org.junit.Before
 import org.junit.FixMethodOrder
@@ -87,13 +88,16 @@
     @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
+    fun cujCompleted() = testSpec.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = false)
+
+    @Presubmit
+    @Test
     fun splitScreenDividerBecomesVisible() {
         Assume.assumeFalse(isShellTransitionsEnabled)
         testSpec.splitScreenDividerBecomesVisible()
     }
 
     // TODO(b/245472831): Back to splitScreenDividerBecomesVisible after shell transition ready.
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun splitScreenDividerIsVisibleAtEnd_ShellTransit() {
@@ -103,12 +107,10 @@
         }
     }
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun primaryAppLayerIsVisibleAtEnd() = testSpec.layerIsVisibleAtEnd(primaryApp)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun secondaryAppLayerBecomesVisible() {
@@ -132,24 +134,20 @@
         testSpec.layerBecomesVisible(sendNotificationApp)
     }
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun primaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
         primaryApp, landscapePosLeft = false, portraitPosTop = false)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun secondaryAppBoundsBecomesVisible() = testSpec.splitAppLayerBoundsBecomesVisibleByDrag(
         sendNotificationApp)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun primaryAppWindowIsVisibleAtEnd() = testSpec.appWindowIsVisibleAtEnd(primaryApp)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun secondaryAppWindowIsVisibleAtEnd() = testSpec.appWindowIsVisibleAtEnd(sendNotificationApp)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt
index a11874e..84d2e6a 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt
@@ -35,6 +35,7 @@
 import com.android.wm.shell.flicker.splitAppLayerBoundsBecomesVisibleByDrag
 import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
 import com.android.wm.shell.flicker.splitScreenDividerBecomesVisible
+import com.android.wm.shell.flicker.splitScreenEntered
 import org.junit.Assume
 import org.junit.Before
 import org.junit.FixMethodOrder
@@ -85,13 +86,16 @@
     @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
+    fun cujCompleted() = testSpec.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = false)
+
+    @Presubmit
+    @Test
     fun splitScreenDividerBecomesVisible() {
         Assume.assumeFalse(isShellTransitionsEnabled)
         testSpec.splitScreenDividerBecomesVisible()
     }
 
     // TODO(b/245472831): Back to splitScreenDividerBecomesVisible after shell transition ready.
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun splitScreenDividerIsVisibleAtEnd_ShellTransit() {
@@ -101,12 +105,10 @@
         }
     }
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun primaryAppLayerIsVisibleAtEnd() = testSpec.layerIsVisibleAtEnd(primaryApp)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun secondaryAppLayerBecomesVisible() {
@@ -130,24 +132,20 @@
         testSpec.layerBecomesVisible(secondaryApp)
     }
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun primaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
         primaryApp, landscapePosLeft = false, portraitPosTop = false)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun secondaryAppBoundsBecomesVisible() = testSpec.splitAppLayerBoundsBecomesVisibleByDrag(
         secondaryApp)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun primaryAppWindowIsVisibleAtEnd() = testSpec.appWindowIsVisibleAtEnd(primaryApp)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun secondaryAppWindowBecomesVisible() = testSpec.appWindowBecomesVisible(secondaryApp)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenFromOverview.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenFromOverview.kt
index 6064b52..23623aa 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenFromOverview.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenFromOverview.kt
@@ -31,6 +31,7 @@
 import com.android.wm.shell.flicker.splitAppLayerBoundsBecomesVisible
 import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
 import com.android.wm.shell.flicker.splitScreenDividerBecomesVisible
+import com.android.wm.shell.flicker.splitScreenEntered
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -71,36 +72,34 @@
     @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
+    fun cujCompleted() = testSpec.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = true)
+
+    @Presubmit
+    @Test
     fun splitScreenDividerBecomesVisible() = testSpec.splitScreenDividerBecomesVisible()
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun primaryAppLayerIsVisibleAtEnd() = testSpec.layerIsVisibleAtEnd(primaryApp)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun secondaryAppLayerBecomesVisible() = testSpec.layerBecomesVisible(secondaryApp)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun primaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
         primaryApp, landscapePosLeft = tapl.isTablet, portraitPosTop = false)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun secondaryAppBoundsBecomesVisible() = testSpec.splitAppLayerBoundsBecomesVisible(
         secondaryApp, landscapePosLeft = !tapl.isTablet, portraitPosTop = true)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun primaryAppWindowBecomesVisible() = testSpec.appWindowBecomesVisible(primaryApp)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun secondaryAppWindowBecomesVisible() = testSpec.appWindowBecomesVisible(secondaryApp)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt
index f06dd66f6..025bb408 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt
@@ -31,9 +31,12 @@
 import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
 import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT
 import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
+import com.android.wm.shell.flicker.appWindowIsVisibleAtStart
 import com.android.wm.shell.flicker.layerIsVisibleAtEnd
 import com.android.wm.shell.flicker.layerKeepVisible
 import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
+import com.android.wm.shell.flicker.splitScreenDividerIsVisibleAtEnd
+import com.android.wm.shell.flicker.splitScreenDividerIsVisibleAtStart
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -111,19 +114,31 @@
     @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
+    fun cujCompleted() {
+        testSpec.appWindowIsVisibleAtStart(primaryApp)
+        testSpec.appWindowIsVisibleAtStart(secondaryApp)
+        testSpec.splitScreenDividerIsVisibleAtStart()
+
+        testSpec.appWindowIsVisibleAtEnd(primaryApp)
+        testSpec.appWindowIsVisibleAtEnd(secondaryApp)
+        testSpec.splitScreenDividerIsVisibleAtEnd()
+
+        // TODO(b/246490534): Add validation for switched app after withAppTransitionIdle is
+        // robust enough to get the correct end state.
+    }
+
+    @Presubmit
+    @Test
     fun splitScreenDividerKeepVisible() = testSpec.layerKeepVisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun primaryAppLayerIsVisibleAtEnd() = testSpec.layerIsVisibleAtEnd(primaryApp)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun secondaryAppLayerIsVisibleAtEnd() = testSpec.layerIsVisibleAtEnd(secondaryApp)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun primaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
@@ -136,12 +151,10 @@
     fun secondaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
         secondaryApp, landscapePosLeft = tapl.isTablet, portraitPosTop = false)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun primaryAppWindowIsVisibleAtEnd() = testSpec.appWindowIsVisibleAtEnd(primaryApp)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun secondaryAppWindowIsVisibleAtEnd() = testSpec.appWindowIsVisibleAtEnd(secondaryApp)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromAnotherApp.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromAnotherApp.kt
index 5c30116..9947a53 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromAnotherApp.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromAnotherApp.kt
@@ -30,6 +30,7 @@
 import com.android.wm.shell.flicker.layerBecomesVisible
 import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
 import com.android.wm.shell.flicker.splitScreenDividerBecomesVisible
+import com.android.wm.shell.flicker.splitScreenEntered
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -69,36 +70,34 @@
     @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
+    fun cujCompleted() = testSpec.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = true)
+
+    @Presubmit
+    @Test
     fun splitScreenDividerBecomesVisible() = testSpec.splitScreenDividerBecomesVisible()
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun primaryAppLayerBecomesVisible() = testSpec.layerBecomesVisible(primaryApp)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun secondaryAppLayerBecomesVisible() = testSpec.layerBecomesVisible(secondaryApp)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun primaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
         primaryApp, landscapePosLeft = tapl.isTablet, portraitPosTop = false)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun secondaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
         secondaryApp, landscapePosLeft = !tapl.isTablet, portraitPosTop = true)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun primaryAppWindowBecomesVisible() = testSpec.appWindowBecomesVisible(primaryApp)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun secondaryAppWindowBecomesVisible() = testSpec.appWindowBecomesVisible(secondaryApp)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt
index 9c66a37..3716dc9 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt
@@ -30,6 +30,7 @@
 import com.android.wm.shell.flicker.layerBecomesVisible
 import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
 import com.android.wm.shell.flicker.splitScreenDividerBecomesVisible
+import com.android.wm.shell.flicker.splitScreenEntered
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -68,36 +69,34 @@
     @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
+    fun cujCompleted() = testSpec.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = true)
+
+    @Presubmit
+    @Test
     fun splitScreenDividerBecomesVisible() = testSpec.splitScreenDividerBecomesVisible()
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun primaryAppLayerBecomesVisible() = testSpec.layerBecomesVisible(primaryApp)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun secondaryAppLayerBecomesVisible() = testSpec.layerBecomesVisible(secondaryApp)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun primaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
         primaryApp, landscapePosLeft = tapl.isTablet, portraitPosTop = false)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun secondaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
         secondaryApp, landscapePosLeft = !tapl.isTablet, portraitPosTop = true)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun primaryAppWindowBecomesVisible() = testSpec.appWindowBecomesVisible(primaryApp)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun secondaryAppWindowBecomesVisible() = testSpec.appWindowBecomesVisible(secondaryApp)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt
index e8862bd..db07f21 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt
@@ -30,6 +30,7 @@
 import com.android.wm.shell.flicker.layerBecomesVisible
 import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
 import com.android.wm.shell.flicker.splitScreenDividerBecomesVisible
+import com.android.wm.shell.flicker.splitScreenEntered
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -70,36 +71,34 @@
     @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
+    fun cujCompleted() = testSpec.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = true)
+
+    @Presubmit
+    @Test
     fun splitScreenDividerBecomesVisible() = testSpec.splitScreenDividerBecomesVisible()
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun primaryAppLayerBecomesVisible() = testSpec.layerBecomesVisible(primaryApp)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun secondaryAppLayerBecomesVisible() = testSpec.layerBecomesVisible(secondaryApp)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun primaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
         primaryApp, landscapePosLeft = tapl.isTablet, portraitPosTop = false)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun secondaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
         secondaryApp, landscapePosLeft = !tapl.isTablet, portraitPosTop = true)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun primaryAppWindowBecomesVisible() = testSpec.appWindowBecomesVisible(primaryApp)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun secondaryAppWindowBecomesVisible() = testSpec.appWindowBecomesVisible(secondaryApp)
diff --git a/media/native/midi/Android.bp b/media/native/midi/Android.bp
index 7acb8c7..a991a71f 100644
--- a/media/native/midi/Android.bp
+++ b/media/native/midi/Android.bp
@@ -74,4 +74,8 @@
     symbol_file: "libamidi.map.txt",
 
     first_version: "29",
+    export_header_libs: [
+        "amidi",
+    ],
+
 }
diff --git a/native/android/Android.bp b/native/android/Android.bp
index 32b7a07..8594ba5 100644
--- a/native/android/Android.bp
+++ b/native/android/Android.bp
@@ -27,6 +27,9 @@
     symbol_file: "libandroid.map.txt",
     first_version: "9",
     unversioned_until: "current",
+    export_header_libs: [
+        "libandroid_headers",
+    ],
 }
 
 cc_defaults {
diff --git a/packages/SettingsLib/Spa/TEST_MAPPING b/packages/SettingsLib/Spa/TEST_MAPPING
index ef3db4a..b4b65d4 100644
--- a/packages/SettingsLib/Spa/TEST_MAPPING
+++ b/packages/SettingsLib/Spa/TEST_MAPPING
@@ -2,6 +2,9 @@
   "presubmit": [
     {
       "name": "SpaLibTests"
+    },
+    {
+      "name": "SpaPrivilegedLibTests"
     }
   ]
 }
diff --git a/packages/SettingsLib/SpaPrivileged/Android.bp b/packages/SettingsLib/SpaPrivileged/Android.bp
index 082ce97..e7e37e4 100644
--- a/packages/SettingsLib/SpaPrivileged/Android.bp
+++ b/packages/SettingsLib/SpaPrivileged/Android.bp
@@ -45,3 +45,9 @@
         "-J-Xmx4G",
     ],
 }
+
+// Expose the srcs to tests, so the tests can access the internal classes.
+filegroup {
+    name: "SpaPrivilegedLib_srcs",
+    srcs: ["src/**/*.kt"],
+}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppsRepository.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt
similarity index 80%
rename from packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppsRepository.kt
rename to packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt
index bb94b33..ee89003 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppsRepository.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt
@@ -21,7 +21,6 @@
 import android.content.pm.ApplicationInfo
 import android.content.pm.PackageManager
 import android.content.pm.ResolveInfo
-import android.content.pm.UserInfo
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.async
 import kotlinx.coroutines.coroutineScope
@@ -30,14 +29,25 @@
 import kotlinx.coroutines.flow.flowOn
 import kotlinx.coroutines.flow.map
 
-class AppsRepository(context: Context) {
+/**
+ * The config used to load the App List.
+ */
+internal data class AppListConfig(
+    val userId: Int,
+    val showInstantApps: Boolean,
+)
+
+/**
+ * The repository to load the App List data.
+ */
+internal class AppListRepository(context: Context) {
     private val packageManager = context.packageManager
 
-    fun loadApps(userInfoFlow: Flow<UserInfo>): Flow<List<ApplicationInfo>> = userInfoFlow
+    fun loadApps(configFlow: Flow<AppListConfig>): Flow<List<ApplicationInfo>> = configFlow
         .map { loadApps(it) }
         .flowOn(Dispatchers.Default)
 
-    private suspend fun loadApps(userInfo: UserInfo): List<ApplicationInfo> {
+    private suspend fun loadApps(config: AppListConfig): List<ApplicationInfo> {
         return coroutineScope {
             val hiddenSystemModulesDeferred = async {
                 packageManager.getInstalledModules(0)
@@ -50,11 +60,11 @@
                     PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS).toLong()
             )
             val installedApplicationsAsUser =
-                packageManager.getInstalledApplicationsAsUser(flags, userInfo.id)
+                packageManager.getInstalledApplicationsAsUser(flags, config.userId)
 
             val hiddenSystemModules = hiddenSystemModulesDeferred.await()
             installedApplicationsAsUser.filter { app ->
-                app.isInAppList(hiddenSystemModules)
+                app.isInAppList(config.showInstantApps, hiddenSystemModules)
             }
         }
     }
@@ -63,9 +73,7 @@
         userIdFlow: Flow<Int>,
         showSystemFlow: Flow<Boolean>,
     ): Flow<(app: ApplicationInfo) -> Boolean> =
-        userIdFlow.combine(showSystemFlow) { userId, showSystem ->
-            showSystemPredicate(userId, showSystem)
-        }
+        userIdFlow.combine(showSystemFlow, ::showSystemPredicate)
 
     private suspend fun showSystemPredicate(
         userId: Int,
@@ -102,12 +110,15 @@
     }
 
     companion object {
-        private fun ApplicationInfo.isInAppList(hiddenSystemModules: Set<String>) =
-            when {
-                packageName in hiddenSystemModules -> false
-                enabled -> true
-                enabledSetting == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER -> true
-                else -> false
-            }
+        private fun ApplicationInfo.isInAppList(
+            showInstantApps: Boolean,
+            hiddenSystemModules: Set<String>,
+        ) = when {
+            !showInstantApps && isInstantApp -> false
+            packageName in hiddenSystemModules -> false
+            enabled -> true
+            isDisabledUntilUsed -> true
+            else -> false
+        }
     }
 }
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListViewModel.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListViewModel.kt
index 9265158..1e487da 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListViewModel.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListViewModel.kt
@@ -18,7 +18,6 @@
 
 import android.app.Application
 import android.content.pm.ApplicationInfo
-import android.content.pm.UserInfo
 import android.icu.text.Collator
 import androidx.lifecycle.AndroidViewModel
 import androidx.lifecycle.viewModelScope
@@ -48,28 +47,29 @@
 internal class AppListViewModel<T : AppRecord>(
     application: Application,
 ) : AndroidViewModel(application) {
-    val userInfo = StateFlowBridge<UserInfo>()
+    val appListConfig = StateFlowBridge<AppListConfig>()
     val listModel = StateFlowBridge<AppListModel<T>>()
     val showSystem = StateFlowBridge<Boolean>()
     val option = StateFlowBridge<Int>()
     val searchQuery = StateFlowBridge<String>()
 
-    private val appsRepository = AppsRepository(application)
+    private val appListRepository = AppListRepository(application)
     private val appRepository = AppRepositoryImpl(application)
     private val collator = Collator.getInstance().freeze()
     private val labelMap = ConcurrentHashMap<String, String>()
     private val scope = viewModelScope + Dispatchers.Default
 
-    private val userIdFlow = userInfo.flow.map { it.id }
+    private val userIdFlow = appListConfig.flow.map { it.userId }
 
     private val recordListFlow = listModel.flow
-        .flatMapLatest { it.transform(userIdFlow, appsRepository.loadApps(userInfo.flow)) }
+        .flatMapLatest { it.transform(userIdFlow, appListRepository.loadApps(appListConfig.flow)) }
         .shareIn(scope = scope, started = SharingStarted.Eagerly, replay = 1)
 
-    private val systemFilteredFlow = appsRepository.showSystemPredicate(userIdFlow, showSystem.flow)
-        .combine(recordListFlow) { showAppPredicate, recordList ->
-            recordList.filter { showAppPredicate(it.app) }
-        }
+    private val systemFilteredFlow =
+        appListRepository.showSystemPredicate(userIdFlow, showSystem.flow)
+            .combine(recordListFlow) { showAppPredicate, recordList ->
+                recordList.filter { showAppPredicate(it.app) }
+            }
 
     val appListDataFlow = option.flow.flatMapLatest(::filterAndSort)
         .combine(searchQuery.flow) { appListData, searchQuery ->
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/Apps.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/ApplicationInfos.kt
similarity index 90%
rename from packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/Apps.kt
rename to packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/ApplicationInfos.kt
index 96e3e77..f9f75fb 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/Apps.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/ApplicationInfos.kt
@@ -32,8 +32,8 @@
 fun ApplicationInfo.hasFlag(flag: Int): Boolean = (flags and flag) > 0
 
 /** Checks whether the application is disabled until used. */
-fun ApplicationInfo.isDisabledUntilUsed(): Boolean =
-    enabledSetting == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED
+val ApplicationInfo.isDisabledUntilUsed: Boolean
+    get() = enabledSetting == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED
 
 /** Converts to the route string which used in navigation. */
 fun ApplicationInfo.toRoute() = "$packageName/$userId"
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt
index 315dc5d..6318b4e 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt
@@ -16,7 +16,6 @@
 
 package com.android.settingslib.spaprivileged.template.app
 
-import android.content.pm.UserInfo
 import androidx.compose.foundation.layout.PaddingValues
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.lazy.LazyColumn
@@ -33,6 +32,7 @@
 import com.android.settingslib.spa.framework.theme.SettingsDimension
 import com.android.settingslib.spa.widget.ui.PlaceholderTitle
 import com.android.settingslib.spaprivileged.R
+import com.android.settingslib.spaprivileged.model.app.AppListConfig
 import com.android.settingslib.spaprivileged.model.app.AppListData
 import com.android.settingslib.spaprivileged.model.app.AppListModel
 import com.android.settingslib.spaprivileged.model.app.AppListViewModel
@@ -41,17 +41,22 @@
 
 private const val TAG = "AppList"
 
+/**
+ * The template to render an App List.
+ *
+ * This UI element will take the remaining space on the screen to show the App List.
+ */
 @Composable
 internal fun <T : AppRecord> AppList(
-    userInfo: UserInfo,
+    appListConfig: AppListConfig,
     listModel: AppListModel<T>,
     showSystem: State<Boolean>,
     option: State<Int>,
     searchQuery: State<String>,
     appItem: @Composable (itemState: AppListItemModel<T>) -> Unit,
 ) {
-    LogCompositions(TAG, userInfo.id.toString())
-    val appListData = loadAppEntries(userInfo, listModel, showSystem, option, searchQuery)
+    LogCompositions(TAG, appListConfig.userId.toString())
+    val appListData = loadAppEntries(appListConfig, listModel, showSystem, option, searchQuery)
     AppListWidget(appListData, listModel, appItem)
 }
 
@@ -85,14 +90,14 @@
 
 @Composable
 private fun <T : AppRecord> loadAppEntries(
-    userInfo: UserInfo,
+    appListConfig: AppListConfig,
     listModel: AppListModel<T>,
     showSystem: State<Boolean>,
     option: State<Int>,
     searchQuery: State<String>,
 ): State<AppListData<T>?> {
-    val viewModel: AppListViewModel<T> = viewModel(key = userInfo.id.toString())
-    viewModel.userInfo.setIfAbsent(userInfo)
+    val viewModel: AppListViewModel<T> = viewModel(key = appListConfig.userId.toString())
+    viewModel.appListConfig.setIfAbsent(appListConfig)
     viewModel.listModel.setIfAbsent(listModel)
     viewModel.showSystem.Sync(showSystem)
     viewModel.option.Sync(option)
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListPage.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListPage.kt
index d537ec2..fb03d2c 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListPage.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListPage.kt
@@ -36,14 +36,19 @@
 import com.android.settingslib.spa.widget.scaffold.SettingsScaffold
 import com.android.settingslib.spa.widget.ui.Spinner
 import com.android.settingslib.spaprivileged.R
+import com.android.settingslib.spaprivileged.model.app.AppListConfig
 import com.android.settingslib.spaprivileged.model.app.AppListModel
 import com.android.settingslib.spaprivileged.model.app.AppRecord
 import com.android.settingslib.spaprivileged.template.common.WorkProfilePager
 
+/**
+ * The full screen template for an App List page.
+ */
 @Composable
 fun <T : AppRecord> AppListPage(
     title: String,
     listModel: AppListModel<T>,
+    showInstantApps: Boolean = false,
     primaryUserOnly: Boolean = false,
     appItem: @Composable (itemState: AppListItemModel<T>) -> Unit,
 ) {
@@ -62,7 +67,10 @@
                 val selectedOption = rememberSaveable { mutableStateOf(0) }
                 Spinner(options, selectedOption.value) { selectedOption.value = it }
                 AppList(
-                    userInfo = userInfo,
+                    appListConfig = AppListConfig(
+                        userId = userInfo.id,
+                        showInstantApps = showInstantApps,
+                    ),
                     listModel = listModel,
                     showSystem = showSystem,
                     option = selectedOption,
diff --git a/packages/SettingsLib/SpaPrivileged/tests/Android.bp b/packages/SettingsLib/SpaPrivileged/tests/Android.bp
new file mode 100644
index 0000000..940a1fe
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/tests/Android.bp
@@ -0,0 +1,46 @@
+//
+// Copyright (C) 2022 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+package {
+    default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_test {
+    name: "SpaPrivilegedLibTests",
+    certificate: "platform",
+    platform_apis: true,
+    test_suites: ["device-tests"],
+
+    srcs: [
+        ":SpaPrivilegedLib_srcs",
+        "src/**/*.kt",
+    ],
+
+    static_libs: [
+        "SpaPrivilegedLib",
+        "androidx.compose.runtime_runtime",
+        "androidx.compose.ui_ui-test-junit4",
+        "androidx.compose.ui_ui-test-manifest",
+        "androidx.test.ext.junit",
+        "androidx.test.runner",
+        "mockito-target-minus-junit4",
+        "truth-prebuilt",
+    ],
+    kotlincflags: [
+        "-Xjvm-default=all",
+        "-Xopt-in=kotlin.RequiresOptIn",
+    ],
+}
diff --git a/packages/SettingsLib/SpaPrivileged/tests/AndroidManifest.xml b/packages/SettingsLib/SpaPrivileged/tests/AndroidManifest.xml
new file mode 100644
index 0000000..c4f490e
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/tests/AndroidManifest.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  Copyright (C) 2022 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.settingslib.spaprivileged.tests">
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation
+        android:name="androidx.test.runner.AndroidJUnitRunner"
+        android:label="Tests for SpaPrivilegedLib"
+        android:targetPackage="com.android.settingslib.spaprivileged.tests" />
+</manifest>
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt
new file mode 100644
index 0000000..c010c68
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spaprivileged.model.app
+
+import android.content.Context
+import android.content.pm.ApplicationInfo
+import android.content.pm.PackageManager
+import android.content.pm.PackageManager.ApplicationInfoFlags
+import android.content.pm.PackageManager.ResolveInfoFlags
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.toList
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.runBlocking
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.any
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.eq
+import org.mockito.Mockito.`when` as whenever
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoRule
+
+private const val USER_ID = 0
+
+@RunWith(AndroidJUnit4::class)
+class AppListRepositoryTest {
+
+    @JvmField
+    @Rule
+    val mockito: MockitoRule = MockitoJUnit.rule()
+
+    @Mock
+    private lateinit var context: Context
+
+    @Mock
+    private lateinit var packageManager: PackageManager
+
+    private lateinit var repository: AppListRepository
+
+    private val normalApp = ApplicationInfo().apply {
+        packageName = "normal"
+        enabled = true
+    }
+
+    private val instantApp = ApplicationInfo().apply {
+        packageName = "instant"
+        enabled = true
+        privateFlags = ApplicationInfo.PRIVATE_FLAG_INSTANT
+    }
+
+    @Before
+    fun setUp() {
+        whenever(context.packageManager).thenReturn(packageManager)
+        whenever(packageManager.getInstalledModules(anyInt())).thenReturn(emptyList())
+        whenever(
+            packageManager.getInstalledApplicationsAsUser(any<ApplicationInfoFlags>(), eq(USER_ID))
+        ).thenReturn(listOf(normalApp, instantApp))
+        whenever(
+            packageManager.queryIntentActivitiesAsUser(any(), any<ResolveInfoFlags>(), eq(USER_ID))
+        ).thenReturn(emptyList())
+
+        repository = AppListRepository(context)
+    }
+
+    @Test
+    fun notShowInstantApps(): Unit = runBlocking {
+        val appListConfig = AppListConfig(userId = USER_ID, showInstantApps = false)
+
+        val appListFlow = repository.loadApps(flowOf(appListConfig))
+
+        launch {
+            val flowValues = mutableListOf<List<ApplicationInfo>>()
+            appListFlow.toList(flowValues)
+            assertThat(flowValues).hasSize(1)
+
+            assertThat(flowValues[0]).containsExactly(normalApp)
+        }
+    }
+
+    @Test
+    fun showInstantApps(): Unit = runBlocking {
+        val appListConfig = AppListConfig(userId = USER_ID, showInstantApps = true)
+
+        val appListFlow = repository.loadApps(flowOf(appListConfig))
+
+        launch {
+            val flowValues = mutableListOf<List<ApplicationInfo>>()
+            appListFlow.toList(flowValues)
+            assertThat(flowValues).hasSize(1)
+
+            assertThat(flowValues[0]).containsExactly(normalApp, instantApp)
+        }
+    }
+}
diff --git a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewCapture.kt b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewCapture.kt
index cdedc64..9766514 100644
--- a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewCapture.kt
+++ b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewCapture.kt
@@ -93,8 +93,8 @@
             Futures.addCallback(
                 captureToBitmap(window),
                 object : FutureCallback<Bitmap> {
-                    override fun onSuccess(result: Bitmap) {
-                        continuation.resumeWith(Result.success(result))
+                    override fun onSuccess(result: Bitmap?) {
+                        continuation.resumeWith(Result.success(result!!))
                     }
 
                     override fun onFailure(t: Throwable) {
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index 072d17f..403ad9b 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -105,6 +105,7 @@
 import android.os.Build;
 import android.os.Bundle;
 import android.os.Handler;
+import android.os.HandlerExecutor;
 import android.os.IBinder;
 import android.os.PackageTagsList;
 import android.os.Process;
@@ -374,10 +375,17 @@
     public AppOpsUidStateTracker getUidStateTracker() {
         if (mUidStateTracker == null) {
             mUidStateTracker = new AppOpsUidStateTrackerImpl(
-                    LocalServices.getService(ActivityManagerInternal.class), mHandler,
+                    LocalServices.getService(ActivityManagerInternal.class),
+                    mHandler,
+                    r -> {
+                        synchronized (AppOpsService.this) {
+                            r.run();
+                        }
+                    },
                     Clock.SYSTEM_CLOCK, mConstants);
 
-            mUidStateTracker.addUidStateChangedCallback(mHandler, this::onUidStateChanged);
+            mUidStateTracker.addUidStateChangedCallback(new HandlerExecutor(mHandler),
+                    this::onUidStateChanged);
         }
         return mUidStateTracker;
     }
@@ -4809,6 +4817,8 @@
         pw.println("    Only output the watcher sections.");
         pw.println("  --history");
         pw.println("    Only output history.");
+        pw.println("  --uid-state-change-logs");
+        pw.println("    Include logs about uid state changes.");
     }
 
     private void dumpStatesLocked(@NonNull PrintWriter pw, @Nullable String filterAttributionTag,
@@ -4946,6 +4956,7 @@
         // TODO ntmyren: Remove the dumpHistory and dumpFilter
         boolean dumpHistory = false;
         boolean includeDiscreteOps = false;
+        boolean dumpUidStateChangeLogs = false;
         int nDiscreteOps = 10;
         @HistoricalOpsRequestFilter int dumpFilter = 0;
         boolean dumpAll = false;
@@ -5028,6 +5039,8 @@
                 } else if (arg.length() > 0 && arg.charAt(0) == '-') {
                     pw.println("Unknown option: " + arg);
                     return;
+                } else if ("--uid-state-change-logs".equals(arg)) {
+                    dumpUidStateChangeLogs = true;
                 } else {
                     pw.println("Unknown command: " + arg);
                     return;
@@ -5363,6 +5376,12 @@
                     pw.println("  AppOps policy not set.");
                 }
             }
+
+            if (dumpAll || dumpUidStateChangeLogs) {
+                pw.println();
+                pw.println("Uid State Changes Event Log:");
+                getUidStateTracker().dumpEvents(pw);
+            }
         }
 
         // Must not hold the appops lock
@@ -5375,14 +5394,6 @@
             mHistoricalRegistry.dumpDiscreteData(pw, dumpUid, dumpPackage, dumpAttributionTag,
                     dumpFilter, dumpOp, sdf, date, "  ", nDiscreteOps);
         }
-
-        if (dumpAll) {
-            pw.println();
-            pw.println("Uid State Changes Event Log:");
-            if (mUidStateTracker != null) {
-                mUidStateTracker.dumpEvents(pw);
-            }
-        }
     }
 
     @Override
diff --git a/services/core/java/com/android/server/appop/AppOpsUidStateTracker.java b/services/core/java/com/android/server/appop/AppOpsUidStateTracker.java
index 3da121b..742bf4b 100644
--- a/services/core/java/com/android/server/appop/AppOpsUidStateTracker.java
+++ b/services/core/java/com/android/server/appop/AppOpsUidStateTracker.java
@@ -30,10 +30,11 @@
 import static android.app.AppOpsManager.UID_STATE_PERSISTENT;
 import static android.app.AppOpsManager.UID_STATE_TOP;
 
-import android.os.Handler;
+import android.annotation.CallbackExecutor;
 import android.util.SparseArray;
 
 import java.io.PrintWriter;
+import java.util.concurrent.Executor;
 
 interface AppOpsUidStateTracker {
 
@@ -95,7 +96,7 @@
     /**
      * Listen to changes in {@link android.app.AppOpsManager.UidState}
      */
-    void addUidStateChangedCallback(Handler handler,
+    void addUidStateChangedCallback(@CallbackExecutor Executor executor,
             UidStateChangedCallback callback);
 
     /**
diff --git a/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java b/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java
index 52b67b5..3c281d1 100644
--- a/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java
+++ b/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java
@@ -44,17 +44,18 @@
 import android.util.SparseLongArray;
 import android.util.TimeUtils;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.os.Clock;
 import com.android.internal.util.function.pooled.PooledLambda;
 
 import java.io.PrintWriter;
-import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
 
 class AppOpsUidStateTrackerImpl implements AppOpsUidStateTracker {
 
     private static final String LOG_TAG = AppOpsUidStateTrackerImpl.class.getSimpleName();
 
-    private final Handler mHandler;
+    private final DelayableExecutor mExecutor;
     private final Clock mClock;
     private ActivityManagerInternal mActivityManagerInternal;
     private AppOpsService.Constants mConstants;
@@ -68,18 +69,46 @@
     private SparseLongArray mPendingCommitTime = new SparseLongArray();
     private SparseBooleanArray mPendingGone = new SparseBooleanArray();
 
-    private ArrayMap<UidStateChangedCallback, Handler> mUidStateChangedCallbacks = new ArrayMap<>();
+    private ArrayMap<UidStateChangedCallback, Executor>
+            mUidStateChangedCallbacks = new ArrayMap<>();
 
     private final EventLog mEventLog;
 
+    @VisibleForTesting
+    interface DelayableExecutor extends Executor {
+
+        void execute(Runnable runnable);
+
+        void executeDelayed(Runnable runnable, long delay);
+    }
+
     AppOpsUidStateTrackerImpl(ActivityManagerInternal activityManagerInternal,
-            Handler handler, Clock clock, AppOpsService.Constants constants) {
+            Handler handler, Executor lockingExecutor, Clock clock,
+            AppOpsService.Constants constants) {
+
+        this(activityManagerInternal, new DelayableExecutor() {
+            @Override
+            public void execute(Runnable runnable) {
+                handler.post(() -> lockingExecutor.execute(runnable));
+            }
+
+            @Override
+            public void executeDelayed(Runnable runnable, long delay) {
+                handler.postDelayed(() -> lockingExecutor.execute(runnable), delay);
+            }
+        }, clock, constants, handler.getLooper().getThread());
+    }
+
+    @VisibleForTesting
+    AppOpsUidStateTrackerImpl(ActivityManagerInternal activityManagerInternal,
+            DelayableExecutor executor, Clock clock, AppOpsService.Constants constants,
+            Thread executorThread) {
         mActivityManagerInternal = activityManagerInternal;
-        mHandler = handler;
+        mExecutor = executor;
         mClock = clock;
         mConstants = constants;
 
-        mEventLog = new EventLog(handler);
+        mEventLog = new EventLog(executor, executorThread);
     }
 
     @Override
@@ -157,11 +186,12 @@
     }
 
     @Override
-    public void addUidStateChangedCallback(Handler handler, UidStateChangedCallback callback) {
+    public void addUidStateChangedCallback(Executor executor, UidStateChangedCallback callback) {
         if (mUidStateChangedCallbacks.containsKey(callback)) {
             throw new IllegalStateException("Callback is already registered.");
         }
-        mUidStateChangedCallbacks.put(callback, handler);
+
+        mUidStateChangedCallbacks.put(callback, executor);
     }
 
     @Override
@@ -232,7 +262,7 @@
                 final long commitTime = mClock.elapsedRealtime() + settleTime;
                 mPendingCommitTime.put(uid, commitTime);
 
-                mHandler.sendMessageDelayed(PooledLambda.obtainMessage(
+                mExecutor.executeDelayed(PooledLambda.obtainRunnable(
                                 AppOpsUidStateTrackerImpl::updateUidPendingStateIfNeeded, this,
                                 uid), settleTime + 1);
             }
@@ -323,10 +353,11 @@
 
             for (int i = 0; i < mUidStateChangedCallbacks.size(); i++) {
                 UidStateChangedCallback cb = mUidStateChangedCallbacks.keyAt(i);
-                Handler h = mUidStateChangedCallbacks.valueAt(i);
+                Executor executor = mUidStateChangedCallbacks.valueAt(i);
 
-                h.sendMessage(PooledLambda.obtainMessage(UidStateChangedCallback::onUidStateChanged,
-                        cb, uid, pendingUidState, foregroundChange));
+                executor.execute(PooledLambda.obtainRunnable(
+                        UidStateChangedCallback::onUidStateChanged, cb, uid, pendingUidState,
+                        foregroundChange));
             }
         }
 
@@ -366,7 +397,8 @@
         // Memory usage: 24 * size bytes
         private static final int EVAL_FOREGROUND_MODE_MAX_SIZE = 200;
 
-        private final Handler mHandler;
+        private final DelayableExecutor mExecutor;
+        private final Thread mExecutorThread;
 
         private int[][] mUpdateUidProcStateLog = new int[UPDATE_UID_PROC_STATE_LOG_MAX_SIZE][3];
         private long[] mUpdateUidProcStateLogTimestamps =
@@ -384,15 +416,16 @@
         private int mEvalForegroundModeLogSize = 0;
         private int mEvalForegroundModeLogHead = 0;
 
-        EventLog(Handler handler) {
-            mHandler = handler;
+        EventLog(DelayableExecutor executor, Thread executorThread) {
+            mExecutor = executor;
+            mExecutorThread = executorThread;
         }
 
         void logUpdateUidProcState(int uid, int procState, int capability) {
             if (UPDATE_UID_PROC_STATE_LOG_MAX_SIZE == 0) {
                 return;
             }
-            mHandler.sendMessage(PooledLambda.obtainMessage(EventLog::logUpdateUidProcStateAsync,
+            mExecutor.execute(PooledLambda.obtainRunnable(EventLog::logUpdateUidProcStateAsync,
                     this, System.currentTimeMillis(), uid, procState, capability));
         }
 
@@ -416,7 +449,7 @@
             if (COMMIT_UID_STATE_LOG_MAX_SIZE == 0) {
                 return;
             }
-            mHandler.sendMessage(PooledLambda.obtainMessage(EventLog::logCommitUidStateAsync,
+            mExecutor.execute(PooledLambda.obtainRunnable(EventLog::logCommitUidStateAsync,
                     this, System.currentTimeMillis(), uid, uidState, capability, visible));
         }
 
@@ -442,7 +475,7 @@
             if (EVAL_FOREGROUND_MODE_MAX_SIZE == 0) {
                 return;
             }
-            mHandler.sendMessage(PooledLambda.obtainMessage(EventLog::logEvalForegroundModeAsync,
+            mExecutor.execute(PooledLambda.obtainRunnable(EventLog::logEvalForegroundModeAsync,
                     this, System.currentTimeMillis(), uid, uidState, capability, code, result));
         }
 
@@ -466,22 +499,6 @@
         }
 
         void dumpEvents(PrintWriter pw) {
-            if (Thread.currentThread() != mHandler.getLooper().getThread()) {
-                // All operations are done on the handler's thread
-                CountDownLatch latch = new CountDownLatch(1);
-                mHandler.post(() -> {
-                    dumpEvents(pw);
-                    latch.countDown();
-                });
-
-                try {
-                    latch.await();
-                } catch (InterruptedException e) {
-                    throw new RuntimeException(e);
-                }
-                return;
-            }
-
             int updateIdx = 0;
             int commitIdx = 0;
             int evalIdx = 0;
@@ -536,13 +553,13 @@
             pw.print(" UPDATE_UID_PROC_STATE");
 
             pw.print(" uid=");
-            pw.print(uid);
+            pw.print(String.format("%-8d", uid));
 
             pw.print(" procState=");
             pw.print(String.format("%-30s", ActivityManager.procStateToString(procState)));
 
             pw.print(" capability=");
-            pw.print(ActivityManager.getCapabilitiesSummary(capability));
+            pw.print(ActivityManager.getCapabilitiesSummary(capability) + " ");
 
             pw.println();
         }
@@ -559,13 +576,13 @@
             pw.print(" COMMIT_UID_STATE     ");
 
             pw.print(" uid=");
-            pw.print(uid);
+            pw.print(String.format("%-8d", uid));
 
             pw.print(" uidState=");
             pw.print(String.format("%-30s", AppOpsManager.uidStateToString(uidState)));
 
             pw.print(" capability=");
-            pw.print(ActivityManager.getCapabilitiesSummary(capability));
+            pw.print(ActivityManager.getCapabilitiesSummary(capability) + " ");
 
             pw.print(" visibleAppWidget=");
             pw.print(visibleAppWidget);
@@ -586,13 +603,13 @@
             pw.print(" EVAL_FOREGROUND_MODE ");
 
             pw.print(" uid=");
-            pw.print(uid);
+            pw.print(String.format("%-8d", uid));
 
             pw.print(" uidState=");
             pw.print(String.format("%-30s", AppOpsManager.uidStateToString(uidState)));
 
             pw.print(" capability=");
-            pw.print(ActivityManager.getCapabilitiesSummary(capability));
+            pw.print(ActivityManager.getCapabilitiesSummary(capability) + " ");
 
             pw.print(" code=");
             pw.print(String.format("%-20s", AppOpsManager.opToName(code)));
diff --git a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUidStateTrackerTest.java b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUidStateTrackerTest.java
index 86e12647..e1713b0 100644
--- a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUidStateTrackerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUidStateTrackerTest.java
@@ -34,12 +34,9 @@
 import static com.android.server.appop.AppOpsUidStateTracker.processStateToUidState;
 
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.atLeast;
 import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.never;
@@ -49,25 +46,22 @@
 import android.app.ActivityManager;
 import android.app.ActivityManagerInternal;
 import android.app.AppOpsManager;
-import android.os.Handler;
-import android.os.Message;
 import android.util.SparseArray;
 
 import com.android.dx.mockito.inline.extended.ExtendedMockito;
 import com.android.dx.mockito.inline.extended.StaticMockitoSession;
 import com.android.internal.os.Clock;
 import com.android.server.appop.AppOpsUidStateTracker.UidStateChangedCallback;
+import com.android.server.appop.AppOpsUidStateTrackerImpl.DelayableExecutor;
 
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
-import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 import org.mockito.Mockito;
 import org.mockito.quality.Strictness;
 
-import java.util.concurrent.atomic.AtomicLong;
-import java.util.concurrent.atomic.AtomicReference;
+import java.util.PriorityQueue;
 
 public class AppOpsUidStateTrackerTest {
 
@@ -81,12 +75,11 @@
     ActivityManagerInternal mAmi;
 
     @Mock
-    Handler mHandler;
-
-    @Mock
     AppOpsService.Constants mConstants;
 
-    AppOpsUidStateTrackerTestClock mClock = new AppOpsUidStateTrackerTestClock();
+    AppOpsUidStateTrackerTestExecutor mExecutor = new AppOpsUidStateTrackerTestExecutor();
+
+    AppOpsUidStateTrackerTestClock mClock = new AppOpsUidStateTrackerTestClock(mExecutor);
 
     AppOpsUidStateTracker mIntf;
 
@@ -101,7 +94,8 @@
         mConstants.TOP_STATE_SETTLE_TIME = 10 * 1000L;
         mConstants.FG_SERVICE_STATE_SETTLE_TIME = 5 * 1000L;
         mConstants.BG_STATE_SETTLE_TIME = 1 * 1000L;
-        mIntf = new AppOpsUidStateTrackerImpl(mAmi, mHandler, mClock, mConstants);
+        mIntf = new AppOpsUidStateTrackerImpl(mAmi, mExecutor, mClock, mConstants,
+                Thread.currentThread());
     }
 
     @After
@@ -263,18 +257,10 @@
         // Still in foreground due to settle time
         assertForeground(UID);
 
-        AtomicReference<Message> messageAtomicReference = new AtomicReference<>();
-        AtomicLong delayAtomicReference = new AtomicLong();
+        mClock.advanceTime(mConstants.TOP_STATE_SETTLE_TIME - 1);
+        assertForeground(UID);
 
-        getPostDelayedMessageArguments(messageAtomicReference, delayAtomicReference);
-        Message message = messageAtomicReference.get();
-        long delay = delayAtomicReference.get();
-
-        assertNotNull(message);
-        assertEquals(mConstants.TOP_STATE_SETTLE_TIME + 1, delay);
-
-        mClock.advanceTime(mConstants.TOP_STATE_SETTLE_TIME + 1);
-        message.getCallback().run();
+        mClock.advanceTime(1);
         assertBackground(UID);
     }
 
@@ -291,18 +277,10 @@
         // Still in foreground due to settle time
         assertForeground(UID);
 
-        AtomicReference<Message> messageAtomicReference = new AtomicReference<>();
-        AtomicLong delayAtomicReference = new AtomicLong();
+        mClock.advanceTime(mConstants.FG_SERVICE_STATE_SETTLE_TIME - 1);
+        assertForeground(UID);
 
-        getPostDelayedMessageArguments(messageAtomicReference, delayAtomicReference);
-        Message message = messageAtomicReference.get();
-        long delay = delayAtomicReference.get();
-
-        assertNotNull(message);
-        assertEquals(mConstants.FG_SERVICE_STATE_SETTLE_TIME + 1, delay);
-
-        mClock.advanceTime(mConstants.FG_SERVICE_STATE_SETTLE_TIME + 1);
-        message.getCallback().run();
+        mClock.advanceTime(1);
         assertBackground(UID);
     }
 
@@ -319,14 +297,8 @@
         // Still in foreground due to settle time
         assertForeground(UID);
 
-        AtomicReference<Message> messageAtomicReference = new AtomicReference<>();
-
-        getPostDelayedMessageArguments(messageAtomicReference, null);
-        Message message = messageAtomicReference.get();
-
         // 1 ms short of settle time
         mClock.advanceTime(mConstants.FG_SERVICE_STATE_SETTLE_TIME - 1);
-        message.getCallback().run();
         assertForeground(UID);
     }
 
@@ -471,8 +443,6 @@
                 .topState()
                 .update();
 
-        getLatestPostMessageArgument().getCallback().run();
-
         verify(cb).onUidStateChanged(eq(UID), eq(UID_STATE_TOP), eq(true));
     }
 
@@ -484,8 +454,6 @@
                 .foregroundServiceState()
                 .update();
 
-        getLatestPostMessageArgument().getCallback().run();
-
         verify(cb).onUidStateChanged(eq(UID), eq(UID_STATE_FOREGROUND_SERVICE), eq(true));
     }
 
@@ -497,8 +465,6 @@
                 .foregroundState()
                 .update();
 
-        getLatestPostMessageArgument().getCallback().run();
-
         verify(cb).onUidStateChanged(eq(UID), eq(UID_STATE_FOREGROUND), eq(true));
     }
 
@@ -510,8 +476,6 @@
                 .backgroundState()
                 .update();
 
-        getLatestPostMessageArgument().getCallback().run();
-
         verify(cb).onUidStateChanged(eq(UID), eq(UID_STATE_BACKGROUND), eq(false));
     }
 
@@ -679,7 +643,6 @@
                 .nonExistentState()
                 .update();
 
-        verify(mHandler, never()).post(any());
         verify(cb, never()).onUidStateChanged(anyInt(), anyInt(), anyBoolean());
     }
 
@@ -695,7 +658,6 @@
                 .nonExistentState()
                 .update();
 
-        getLatestPostMessageArgument().getCallback().run();
         verify(cb, atLeastOnce()).onUidStateChanged(eq(UID), eq(UID_STATE_CACHED), eq(false));
     }
 
@@ -711,7 +673,6 @@
                 .nonExistentState()
                 .update();
 
-        getLatestPostMessageArgument().getCallback().run();
         verify(cb, atLeastOnce()).onUidStateChanged(eq(UID), eq(UID_STATE_CACHED), eq(true));
     }
 
@@ -727,7 +688,6 @@
                 .nonExistentState()
                 .update();
 
-        getLatestPostMessageArgument().getCallback().run();
         verify(cb, atLeastOnce()).onUidStateChanged(eq(UID), eq(UID_STATE_CACHED), eq(true));
     }
 
@@ -743,10 +703,32 @@
                 .nonExistentState()
                 .update();
 
-        getLatestPostMessageArgument().getCallback().run();
         verify(cb, atLeastOnce()).onUidStateChanged(eq(UID), eq(UID_STATE_CACHED), eq(true));
     }
 
+    @Test
+    public void testUidStateChangedBackgroundThenForegroundImmediately() {
+        procStateBuilder(UID)
+            .topState()
+            .update();
+
+        UidStateChangedCallback cb = addUidStateChangeCallback();
+
+        procStateBuilder(UID)
+            .backgroundState()
+            .update();
+
+        mClock.advanceTime(mConstants.TOP_STATE_SETTLE_TIME - 1);
+
+        procStateBuilder(UID)
+            .topState()
+            .update();
+
+        mClock.advanceTime(1);
+
+        verify(cb, never()).onUidStateChanged(anyInt(), anyInt(), anyBoolean());
+    }
+
     public void testUidStateChangedCallback(int initialState, int finalState) {
         int initialUidState = processStateToUidState(initialState);
         int finalUidState = processStateToUidState(finalState);
@@ -767,13 +749,9 @@
                 .update();
 
         if (finalUidStateIsBackgroundAndLessImportant) {
-            AtomicReference<Message> delayedMessage = new AtomicReference<>();
-            getPostDelayedMessageArguments(delayedMessage, new AtomicLong());
             mClock.advanceTime(mConstants.TOP_STATE_SETTLE_TIME + 1);
-            delayedMessage.get().getCallback().run();
         }
 
-        getLatestPostMessageArgument().getCallback().run();
         verify(cb, atLeastOnce())
                 .onUidStateChanged(eq(UID), eq(finalUidState), eq(foregroundChange));
     }
@@ -781,7 +759,7 @@
     private UidStateChangedCallback addUidStateChangeCallback() {
         UidStateChangedCallback cb =
                 Mockito.mock(UidStateChangedCallback.class);
-        mIntf.addUidStateChangedCallback(mHandler, cb);
+        mIntf.addUidStateChangedCallback(r -> r.run(), cb);
         return cb;
     }
 
@@ -795,30 +773,6 @@
         assertEquals(MODE_IGNORED, mIntf.evalMode(uid, OP_NO_CAPABILITIES, MODE_FOREGROUND));
     }
 
-    private void getPostDelayedMessageArguments(AtomicReference<Message> message,
-            AtomicLong delay) {
-
-        ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
-        ArgumentCaptor<Long> delayCaptor = ArgumentCaptor.forClass(Long.class);
-
-        verify(mHandler).sendMessageDelayed(messageCaptor.capture(), delayCaptor.capture());
-
-        if (message != null) {
-            message.set(messageCaptor.getValue());
-        }
-        if (delay != null) {
-            delay.set(delayCaptor.getValue());
-        }
-    }
-
-    private Message getLatestPostMessageArgument() {
-        ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
-
-        verify(mHandler, atLeast(1)).sendMessage(messageCaptor.capture());
-
-        return messageCaptor.getValue();
-    }
-
     private UidProcStateUpdateBuilder procStateBuilder(int uid) {
         return new UidProcStateUpdateBuilder(mIntf, uid);
     }
@@ -896,8 +850,14 @@
 
     private static class AppOpsUidStateTrackerTestClock extends Clock {
 
+        private AppOpsUidStateTrackerTestExecutor mExecutor;
         long mElapsedRealTime = 0x5f3759df;
 
+        AppOpsUidStateTrackerTestClock(AppOpsUidStateTrackerTestExecutor executor) {
+            mExecutor = executor;
+            executor.setUptime(mElapsedRealTime);
+        }
+
         @Override
         public long elapsedRealtime() {
             return mElapsedRealTime;
@@ -905,6 +865,53 @@
 
         void advanceTime(long time) {
             mElapsedRealTime += time;
+            mExecutor.setUptime(mElapsedRealTime); // assume uptime == elapsedtime
+        }
+    }
+
+    private static class AppOpsUidStateTrackerTestExecutor implements DelayableExecutor {
+
+        private static class QueueElement implements Comparable<QueueElement> {
+
+            private long mExecutionTime;
+            private Runnable mRunnable;
+
+            private QueueElement(long executionTime, Runnable runnable) {
+                mExecutionTime = executionTime;
+                mRunnable = runnable;
+            }
+
+            @Override
+            public int compareTo(QueueElement queueElement) {
+                return Long.compare(mExecutionTime, queueElement.mExecutionTime);
+            }
+        }
+
+        private long mUptime = 0;
+
+        private PriorityQueue<QueueElement> mDelayedMessages = new PriorityQueue();
+
+        @Override
+        public void execute(Runnable runnable) {
+            runnable.run();
+        }
+
+        @Override
+        public void executeDelayed(Runnable runnable, long delay) {
+            if (delay <= 0) {
+                execute(runnable);
+            }
+
+            mDelayedMessages.add(new QueueElement(mUptime + delay, runnable));
+        }
+
+        private void setUptime(long uptime) {
+            while (!mDelayedMessages.isEmpty()
+                    && mDelayedMessages.peek().mExecutionTime <= uptime) {
+                mDelayedMessages.poll().mRunnable.run();
+            }
+
+            mUptime = uptime;
         }
     }
 }
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
index 91b868d..be7c112 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
@@ -136,9 +136,6 @@
     private final RemoteCallbackList<IVoiceInteractionSessionListener>
             mVoiceInteractionSessionListeners = new RemoteCallbackList<>();
 
-    // TODO(b/226201975): remove once RoleService supports pre-created users
-    private final ArrayList<UserHandle> mIgnoredPreCreatedUsers = new ArrayList<>();
-
     public VoiceInteractionManagerService(Context context) {
         super(context);
         mContext = context;
@@ -309,24 +306,14 @@
             return hotwordDetectionConnection.mIdentity;
         }
 
+        // TODO(b/226201975): remove this method once RoleService supports pre-created users
         @Override
         public void onPreCreatedUserConversion(int userId) {
-            Slogf.d(TAG, "onPreCreatedUserConversion(%d)", userId);
-
-            for (int i = 0; i < mIgnoredPreCreatedUsers.size(); i++) {
-                UserHandle preCreatedUser = mIgnoredPreCreatedUsers.get(i);
-                if (preCreatedUser.getIdentifier() == userId) {
-                    Slogf.d(TAG, "Updating role on pre-created user %d", userId);
-                    mServiceStub.mRoleObserver.onRoleHoldersChanged(RoleManager.ROLE_ASSISTANT,
-                            preCreatedUser);
-                    mIgnoredPreCreatedUsers.remove(i);
-                    return;
-                }
-            }
-            Slogf.w(TAG, "onPreCreatedUserConversion(%d): not available on "
-                    + "mIgnoredPreCreatedUserIds (%s)", userId, mIgnoredPreCreatedUsers);
+            Slogf.d(TAG, "onPreCreatedUserConversion(%d): calling onRoleHoldersChanged() again",
+                    userId);
+            mServiceStub.mRoleObserver.onRoleHoldersChanged(RoleManager.ROLE_ASSISTANT,
+                                                UserHandle.of(userId));
         }
-
     }
 
     // implementation entry point and binder service
@@ -809,8 +796,10 @@
             if (TextUtils.isEmpty(curInteractor)) {
                 return null;
             }
-            if (DEBUG) Slog.d(TAG, "getCurInteractor curInteractor=" + curInteractor
+            if (DEBUG) {
+                Slog.d(TAG, "getCurInteractor curInteractor=" + curInteractor
                     + " user=" + userHandle);
+            }
             return ComponentName.unflattenFromString(curInteractor);
         }
 
@@ -818,8 +807,9 @@
             Settings.Secure.putStringForUser(mContext.getContentResolver(),
                     Settings.Secure.VOICE_INTERACTION_SERVICE,
                     comp != null ? comp.flattenToShortString() : "", userHandle);
-            if (DEBUG) Slog.d(TAG, "setCurInteractor comp=" + comp
-                    + " user=" + userHandle);
+            if (DEBUG) {
+                Slog.d(TAG, "setCurInteractor comp=" + comp + " user=" + userHandle);
+            }
         }
 
         ComponentName findAvailRecognizer(String prefPackage, int userHandle) {
@@ -1917,7 +1907,6 @@
                 pw.println("  mTemporarilyDisabled: " + mTemporarilyDisabled);
                 pw.println("  mCurUser: " + mCurUser);
                 pw.println("  mCurUserSupported: " + mCurUserSupported);
-                pw.println("  mIgnoredPreCreatedUsers: " + mIgnoredPreCreatedUsers);
                 dumpSupportedUsers(pw, "  ");
                 mDbHelper.dump(pw);
                 if (mImpl == null) {
@@ -2031,6 +2020,11 @@
 
                 List<String> roleHolders = mRm.getRoleHoldersAsUser(roleName, user);
 
+                if (DEBUG) {
+                    Slogf.d(TAG, "onRoleHoldersChanged(%s, %s): roleHolders=%s", roleName, user,
+                            roleHolders);
+                }
+
                 // TODO(b/226201975): this method is beling called when a pre-created user is added,
                 // at which point it doesn't have any role holders. But it's not called again when
                 // the actual user is added (i.e., when the  pre-created user is converted), so we
@@ -2041,9 +2035,9 @@
                 if (roleHolders.isEmpty()) {
                     UserInfo userInfo = mUserManagerInternal.getUserInfo(user.getIdentifier());
                     if (userInfo != null && userInfo.preCreated) {
-                        Slogf.d(TAG, "onRoleHoldersChanged(): ignoring pre-created user %s for now",
-                                userInfo.toFullString());
-                        mIgnoredPreCreatedUsers.add(user);
+                        Slogf.d(TAG, "onRoleHoldersChanged(): ignoring pre-created user %s for now,"
+                                + " this method will be called again when it's converted to a real"
+                                + " user", userInfo.toFullString());
                         return;
                     }
                 }