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;
}
}