diff options
33 files changed, 659 insertions, 273 deletions
diff --git a/core/java/android/companion/virtual/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java index d4c9a42c8115..fadfa5cd68a5 100644 --- a/core/java/android/companion/virtual/VirtualDeviceManager.java +++ b/core/java/android/companion/virtual/VirtualDeviceManager.java @@ -445,8 +445,8 @@ public final class VirtualDeviceManager { @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 0db7b5fe8289..e200a117e475 100644 --- a/core/java/android/companion/virtual/audio/VirtualAudioDevice.java +++ b/core/java/android/companion/virtual/audio/VirtualAudioDevice.java @@ -64,11 +64,24 @@ public final class VirtualAudioDevice implements Closeable { 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 final class VirtualAudioDevice implements Closeable { */ 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 @@ public final class VirtualAudioDevice implements Closeable { } 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 2ccf1216d7c1..f9d1e88e70b8 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.appPairsDividerBecomesVisible() { } } +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.appWindowBecomesInvisible( } } +fun FlickerTestParameter.appWindowIsVisibleAtStart( + component: IComponentMatcher +) { + assertWmStart { + this.isAppWindowVisible(component) + } +} + fun FlickerTestParameter.appWindowIsVisibleAtEnd( component: IComponentMatcher ) { @@ -279,6 +347,22 @@ fun FlickerTestParameter.appWindowIsVisibleAtEnd( } } +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 7dbd27949bdd..5b044e3fcbc3 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.FlickerTestParameterFactory 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 @@ class CopyContentInSplit(testSpec: FlickerTestParameter) : SplitScreenBase(testS @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 3646fd7f0177..838026fdc6a6 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.appWindowIsVisibleAtEnd 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 @@ class DismissSplitScreenByDivider (testSpec: FlickerTestParameter) : SplitScreen @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 @@ class DismissSplitScreenByDivider (testSpec: FlickerTestParameter) : SplitScreen } } - @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 80abedd476e6..a764206fb33e 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.server.wm.flicker.dsl.FlickerBuilder 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 @@ class DismissSplitScreenByGoHome( .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 @@ class DismissSplitScreenByGoHome( 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 2915787d5a43..ba0231755690 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.FlickerTestParameterFactory 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 @@ class DragDividerToResize (testSpec: FlickerTestParameter) : SplitScreenBase(tes @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 @@ class DragDividerToResize (testSpec: FlickerTestParameter) : SplitScreenBase(tes } } - @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 8e041a703802..bb4478940939 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.layerIsVisibleAtEnd 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 @@ class EnterSplitScreenByDragFromAllApps( @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 @@ class EnterSplitScreenByDragFromAllApps( } } - @IwTest(focusArea = "sysui") @Presubmit @Test fun primaryAppLayerIsVisibleAtEnd() = testSpec.layerIsVisibleAtEnd(primaryApp) - @IwTest(focusArea = "sysui") @Presubmit @Test fun secondaryAppLayerBecomesVisible() { @@ -127,24 +129,20 @@ class EnterSplitScreenByDragFromAllApps( 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 2ee12f1f0af2..e208196000fc 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.layerIsVisibleAtEnd 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 @@ class EnterSplitScreenByDragFromNotification( @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 @@ class EnterSplitScreenByDragFromNotification( } } - @IwTest(focusArea = "sysui") @Presubmit @Test fun primaryAppLayerIsVisibleAtEnd() = testSpec.layerIsVisibleAtEnd(primaryApp) - @IwTest(focusArea = "sysui") @Presubmit @Test fun secondaryAppLayerBecomesVisible() { @@ -132,24 +134,20 @@ class EnterSplitScreenByDragFromNotification( 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 a11874e00228..84d2e6a42d27 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.layerIsVisibleAtEnd 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 @@ class EnterSplitScreenByDragFromTaskbar( @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 @@ class EnterSplitScreenByDragFromTaskbar( } } - @IwTest(focusArea = "sysui") @Presubmit @Test fun primaryAppLayerIsVisibleAtEnd() = testSpec.layerIsVisibleAtEnd(primaryApp) - @IwTest(focusArea = "sysui") @Presubmit @Test fun secondaryAppLayerBecomesVisible() { @@ -130,24 +132,20 @@ class EnterSplitScreenByDragFromTaskbar( 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 6064b52938c8..23623aaf2d00 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.layerIsVisibleAtEnd 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 @@ class EnterSplitScreenFromOverview(testSpec: FlickerTestParameter) : SplitScreen @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 f06dd66f61d2..025bb4085624 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.flicker.helpers.isRotated 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 @@ class SwitchAppByDoubleTapDivider(testSpec: FlickerTestParameter) : SplitScreenB @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 @@ class SwitchAppByDoubleTapDivider(testSpec: FlickerTestParameter) : SplitScreenB 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 5c3011643270..9947a53dda42 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.appWindowBecomesVisible 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 @@ class SwitchBackToSplitFromAnotherApp(testSpec: FlickerTestParameter) : SplitScr @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 9c66a3734da3..3716dc98714c 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.appWindowBecomesVisible 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 @@ class SwitchBackToSplitFromHome(testSpec: FlickerTestParameter) : SplitScreenBas @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 e8862bd4c982..db07f2132e95 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.appWindowBecomesVisible 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 @@ class SwitchBackToSplitFromRecent(testSpec: FlickerTestParameter) : SplitScreenB @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 7acb8c744ba7..a991a71fd3f1 100644 --- a/media/native/midi/Android.bp +++ b/media/native/midi/Android.bp @@ -74,4 +74,8 @@ ndk_library { 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 32b7a0780e63..8594ba5ca2da 100644 --- a/native/android/Android.bp +++ b/native/android/Android.bp @@ -27,6 +27,9 @@ ndk_library { 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 ef3db4aef66d..b4b65d4c0ddc 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 082ce97e3273..e7e37e418185 100644 --- a/packages/SettingsLib/SpaPrivileged/Android.bp +++ b/packages/SettingsLib/SpaPrivileged/Android.bp @@ -45,3 +45,9 @@ java_defaults { "-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 index bb94b33bfa28..ee8900352cf2 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.Intent 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.combine 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 @@ class AppsRepository(context: Context) { 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 @@ class AppsRepository(context: Context) { 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 @@ class AppsRepository(context: Context) { } 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 9265158b3b4a..1e487daa36fb 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 @@ package com.android.settingslib.spaprivileged.model.app 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 data class AppListData<T : AppRecord>( 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 index 96e3e77e9322..f9f75fb55d39 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 @@ val ApplicationInfo.userHandle: UserHandle 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 315dc5d6ff70..6318b4e9c186 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.compose.toState 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 @@ import kotlinx.coroutines.Dispatchers 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 @@ private fun <T : AppRecord> AppListWidget( @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 d537ec258cad..fb03d2c2e2cb 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.MoreOptionsAction 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 @@ fun <T : AppRecord> AppListPage( 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 000000000000..940a1fed817b --- /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 000000000000..c4f490ed398b --- /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 000000000000..c010c68f37a4 --- /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 cdedc64ed0e9..97665145ce76 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 @@ fun View.toBitmap(window: Window? = null): Bitmap { 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 072d17f21829..403ad9b41f67 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.Binder; 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 class AppOpsService extends IAppOpsService.Stub implements PersistenceSch 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 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch 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 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch // 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 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch } 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 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch 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 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch 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 3da121bc73fa..742bf4b6ebc7 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_FOREGROUND_SERVICE; 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 @@ interface AppOpsUidStateTracker { /** * 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 52b67b530c19..3c281d13c769 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.SparseIntArray; 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 @@ class AppOpsUidStateTrackerImpl implements AppOpsUidStateTracker { 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, 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, - Handler handler, Clock clock, AppOpsService.Constants constants) { + 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 @@ class AppOpsUidStateTrackerImpl implements AppOpsUidStateTracker { } @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 @@ class AppOpsUidStateTrackerImpl implements AppOpsUidStateTracker { 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 @@ class AppOpsUidStateTrackerImpl implements AppOpsUidStateTracker { 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 @@ class AppOpsUidStateTrackerImpl implements AppOpsUidStateTracker { // 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 @@ class AppOpsUidStateTrackerImpl implements AppOpsUidStateTracker { 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 @@ class AppOpsUidStateTrackerImpl implements AppOpsUidStateTracker { 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 @@ class AppOpsUidStateTrackerImpl implements AppOpsUidStateTracker { 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 @@ class AppOpsUidStateTrackerImpl implements AppOpsUidStateTracker { } 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 @@ class AppOpsUidStateTrackerImpl implements AppOpsUidStateTracker { 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 @@ class AppOpsUidStateTrackerImpl implements AppOpsUidStateTracker { 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 @@ class AppOpsUidStateTrackerImpl implements AppOpsUidStateTracker { 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 86e126475654..e1713b0beb77 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 android.app.AppOpsManager.UID_STATE_TOP; 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 static org.mockito.Mockito.verify; 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 @@ public class AppOpsUidStateTrackerTest { 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 @@ public class AppOpsUidStateTrackerTest { 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 @@ public class AppOpsUidStateTrackerTest { // Still in foreground due to settle time assertForeground(UID); - AtomicReference<Message> messageAtomicReference = new AtomicReference<>(); - AtomicLong delayAtomicReference = new AtomicLong(); - - 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); + assertForeground(UID); - mClock.advanceTime(mConstants.TOP_STATE_SETTLE_TIME + 1); - message.getCallback().run(); + mClock.advanceTime(1); assertBackground(UID); } @@ -291,18 +277,10 @@ public class AppOpsUidStateTrackerTest { // Still in foreground due to settle time assertForeground(UID); - AtomicReference<Message> messageAtomicReference = new AtomicReference<>(); - AtomicLong delayAtomicReference = new AtomicLong(); - - 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); + assertForeground(UID); - mClock.advanceTime(mConstants.FG_SERVICE_STATE_SETTLE_TIME + 1); - message.getCallback().run(); + mClock.advanceTime(1); assertBackground(UID); } @@ -319,14 +297,8 @@ public class AppOpsUidStateTrackerTest { // 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 @@ public class AppOpsUidStateTrackerTest { .topState() .update(); - getLatestPostMessageArgument().getCallback().run(); - verify(cb).onUidStateChanged(eq(UID), eq(UID_STATE_TOP), eq(true)); } @@ -484,8 +454,6 @@ public class AppOpsUidStateTrackerTest { .foregroundServiceState() .update(); - getLatestPostMessageArgument().getCallback().run(); - verify(cb).onUidStateChanged(eq(UID), eq(UID_STATE_FOREGROUND_SERVICE), eq(true)); } @@ -497,8 +465,6 @@ public class AppOpsUidStateTrackerTest { .foregroundState() .update(); - getLatestPostMessageArgument().getCallback().run(); - verify(cb).onUidStateChanged(eq(UID), eq(UID_STATE_FOREGROUND), eq(true)); } @@ -510,8 +476,6 @@ public class AppOpsUidStateTrackerTest { .backgroundState() .update(); - getLatestPostMessageArgument().getCallback().run(); - verify(cb).onUidStateChanged(eq(UID), eq(UID_STATE_BACKGROUND), eq(false)); } @@ -679,7 +643,6 @@ public class AppOpsUidStateTrackerTest { .nonExistentState() .update(); - verify(mHandler, never()).post(any()); verify(cb, never()).onUidStateChanged(anyInt(), anyInt(), anyBoolean()); } @@ -695,7 +658,6 @@ public class AppOpsUidStateTrackerTest { .nonExistentState() .update(); - getLatestPostMessageArgument().getCallback().run(); verify(cb, atLeastOnce()).onUidStateChanged(eq(UID), eq(UID_STATE_CACHED), eq(false)); } @@ -711,7 +673,6 @@ public class AppOpsUidStateTrackerTest { .nonExistentState() .update(); - getLatestPostMessageArgument().getCallback().run(); verify(cb, atLeastOnce()).onUidStateChanged(eq(UID), eq(UID_STATE_CACHED), eq(true)); } @@ -727,7 +688,6 @@ public class AppOpsUidStateTrackerTest { .nonExistentState() .update(); - getLatestPostMessageArgument().getCallback().run(); verify(cb, atLeastOnce()).onUidStateChanged(eq(UID), eq(UID_STATE_CACHED), eq(true)); } @@ -743,10 +703,32 @@ public class AppOpsUidStateTrackerTest { .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 @@ public class AppOpsUidStateTrackerTest { .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 @@ public class AppOpsUidStateTrackerTest { 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 @@ public class AppOpsUidStateTrackerTest { 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 @@ public class AppOpsUidStateTrackerTest { 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 @@ public class AppOpsUidStateTrackerTest { 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 91b868dd3803..be7c1122ae6e 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 @@ public class VoiceInteractionManagerService extends SystemService { 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 @@ public class VoiceInteractionManagerService extends SystemService { 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 @@ public class VoiceInteractionManagerService extends SystemService { 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 @@ public class VoiceInteractionManagerService extends SystemService { 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 @@ public class VoiceInteractionManagerService extends SystemService { 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 @@ public class VoiceInteractionManagerService extends SystemService { 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 @@ public class VoiceInteractionManagerService extends SystemService { 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; } } |