summaryrefslogtreecommitdiff
path: root/libs
diff options
context:
space:
mode:
Diffstat (limited to 'libs')
-rw-r--r--libs/WindowManager/Shell/res/values/styles.xml1
-rw-r--r--libs/WindowManager/Shell/shared/res/values/dimen.xml2
-rw-r--r--libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedTaskInfo.java2
-rw-r--r--libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DragZoneFactory.kt2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitState.java12
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandler.kt34
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopDisplayModeController.kt78
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java76
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt84
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/DesksOrganizer.kt16
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/RootTaskDesksOrganizer.kt102
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java45
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java19
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultSurfaceAnimator.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java11
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/FocusTransitionObserver.java19
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java8
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt27
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/AppHandleAndHeaderVisibilityHelper.kt15
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/ButtonBackgroundDrawableUtils.kt12
-rw-r--r--libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/Android.bp1
-rw-r--r--libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/UnminimizeAppFromTaskbar.kt19
-rw-r--r--libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/AndroidTestTemplate.xml4
-rw-r--r--libs/WindowManager/Shell/tests/e2e/utils/src/com/android/wm/shell/SimulatedConnectedDisplayTestRule.kt172
-rw-r--r--libs/WindowManager/Shell/tests/flicker/Android.bp16
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandlerTest.kt20
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopDisplayModeControllerTest.kt463
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicatorTest.kt60
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt80
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/multidesks/RootTaskDesksOrganizerTest.kt167
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java3
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/DefaultTransitionHandlerTest.java3
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/HomeTransitionObserverTest.java3
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java10
-rw-r--r--libs/androidfw/AssetManager2.cpp25
-rw-r--r--libs/androidfw/LoadedArsc.cpp12
-rw-r--r--libs/androidfw/include/androidfw/AssetManager2.h15
-rw-r--r--libs/androidfw/include/androidfw/LoadedArsc.h3
-rw-r--r--libs/androidfw/tests/AssetManager2_bench.cpp2
-rw-r--r--libs/androidfw/tests/AssetManager2_test.cpp17
-rw-r--r--libs/androidfw/tests/data/flagged/AndroidManifest.xml20
-rw-r--r--libs/androidfw/tests/data/flagged/R.h35
-rwxr-xr-xlibs/androidfw/tests/data/flagged/build28
-rw-r--r--libs/androidfw/tests/data/flagged/flagged.apkbin0 -> 1837 bytes
-rw-r--r--libs/androidfw/tests/data/flagged/res/xml/flagged.xml18
-rw-r--r--libs/hwui/Android.bp8
-rw-r--r--libs/hwui/jni/Bitmap.cpp14
49 files changed, 1012 insertions, 779 deletions
diff --git a/libs/WindowManager/Shell/res/values/styles.xml b/libs/WindowManager/Shell/res/values/styles.xml
index 08cda7b94a78..086c8a5651c3 100644
--- a/libs/WindowManager/Shell/res/values/styles.xml
+++ b/libs/WindowManager/Shell/res/values/styles.xml
@@ -51,7 +51,6 @@
<item name="android:clickable">true</item>
<item name="android:focusable">true</item>
<item name="android:orientation">horizontal</item>
- <item name="android:background">?android:attr/selectableItemBackground</item>
</style>
<style name="DesktopModeHandleMenuActionButtonImage">
diff --git a/libs/WindowManager/Shell/shared/res/values/dimen.xml b/libs/WindowManager/Shell/shared/res/values/dimen.xml
index 74b6023bde36..c3987caa87e5 100644
--- a/libs/WindowManager/Shell/shared/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/shared/res/values/dimen.xml
@@ -45,7 +45,7 @@
<dimen name="drop_target_full_screen_padding">20dp</dimen>
<dimen name="drop_target_desktop_window_padding_small">100dp</dimen>
<dimen name="drop_target_desktop_window_padding_large">130dp</dimen>
- <dimen name="drop_target_expanded_view_width">364</dimen>
+ <dimen name="drop_target_expanded_view_width">330</dimen>
<dimen name="drop_target_expanded_view_height">578</dimen>
<dimen name="drop_target_expanded_view_padding_bottom">108</dimen>
<dimen name="drop_target_expanded_view_padding_horizontal">24</dimen>
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedTaskInfo.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedTaskInfo.java
index f68afea92850..006dc1439d6d 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedTaskInfo.java
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedTaskInfo.java
@@ -124,7 +124,7 @@ public class GroupedTaskInfo implements Parcelable {
* Create new for a pair of tasks in split screen
*/
public static GroupedTaskInfo forSplitTasks(@NonNull TaskInfo task1,
- @NonNull TaskInfo task2, @NonNull SplitBounds splitBounds) {
+ @NonNull TaskInfo task2, @Nullable SplitBounds splitBounds) {
return new GroupedTaskInfo(/* deskId = */ -1, /* displayId = */ INVALID_DISPLAY,
List.of(task1, task2),
splitBounds, TYPE_SPLIT, /* minimizedFreeformTaskIds = */ null);
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DragZoneFactory.kt b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DragZoneFactory.kt
index afeaf70c9d62..ffd1f5f3a39e 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DragZoneFactory.kt
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DragZoneFactory.kt
@@ -133,7 +133,7 @@ class DragZoneFactory(
fullScreenDropTargetPadding = 20.dpToPx()
desktopWindowDropTargetPaddingSmall = 100.dpToPx()
desktopWindowDropTargetPaddingLarge = 130.dpToPx()
- expandedViewDropTargetWidth = 364.dpToPx()
+ expandedViewDropTargetWidth = 330.dpToPx()
expandedViewDropTargetHeight = 578.dpToPx()
expandedViewDropTargetPaddingBottom = 108.dpToPx()
expandedViewDropTargetPaddingHorizontal = 24.dpToPx()
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
index 746632f67725..f91154c7a362 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
@@ -1050,7 +1050,8 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
() -> mShellExecutor.execute(this::onBackAnimationFinished));
if (mApps.length >= 1) {
- BackMotionEvent startEvent = mCurrentTracker.createStartEvent(mApps[0]);
+ BackMotionEvent startEvent = mCurrentTracker.createStartEvent(
+ Flags.removeDepartTargetFromMotion() ? null : mApps[0]);
dispatchOnBackStarted(mActiveCallback, startEvent);
if (startEvent.getSwipeEdge() == EDGE_NONE) {
// TODO(b/373544911): onBackStarted is dispatched here so that
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitState.java
index ad0e7fc187e9..394c445787b4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitState.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitState.java
@@ -17,6 +17,10 @@
package com.android.wm.shell.common.split;
import static com.android.wm.shell.shared.split.SplitScreenConstants.NOT_IN_SPLIT;
+import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_10_90;
+import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_90_10;
+import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_3_10_45_45;
+import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_3_45_45_10;
import static com.android.wm.shell.shared.split.SplitScreenConstants.SplitScreenState;
import android.graphics.Rect;
@@ -62,4 +66,12 @@ public class SplitState {
public List<RectF> getCurrentLayout() {
return getLayout(mState);
}
+
+ /** @return {@code true} if at least one app is partially offscreen in the current layout. */
+ public boolean currentStateSupportsOffscreenApps() {
+ return mState == SNAP_TO_2_10_90
+ || mState == SNAP_TO_2_90_10
+ || mState == SNAP_TO_3_10_45_45
+ || mState == SNAP_TO_3_45_45_10;
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
index b3c25d495002..ad509bcc1ceb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
@@ -766,6 +766,7 @@ public abstract class WMShellBaseModule {
@ShellMainThread ShellExecutor mainExecutor,
@ShellMainThread Handler mainHandler,
@ShellAnimationThread ShellExecutor animExecutor,
+ @ShellAnimationThread Handler animHandler,
RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
HomeTransitionObserver homeTransitionObserver,
FocusTransitionObserver focusTransitionObserver) {
@@ -775,7 +776,7 @@ public abstract class WMShellBaseModule {
}
return new Transitions(context, shellInit, shellCommandHandler, shellController, organizer,
pool, displayController, displayInsetsController, mainExecutor, mainHandler,
- animExecutor, rootTaskDisplayAreaOrganizer, homeTransitionObserver,
+ animExecutor, animHandler, rootTaskDisplayAreaOrganizer, homeTransitionObserver,
focusTransitionObserver);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandler.kt
index 25737c4950d6..80c6f2e5ff33 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandler.kt
@@ -49,9 +49,6 @@ class DesktopDisplayEventHandler(
private val desktopDisplayModeController: DesktopDisplayModeController,
) : OnDisplaysChangedListener, OnDeskRemovedListener {
- private val desktopRepository: DesktopRepository
- get() = desktopUserRepositories.current
-
init {
shellInit.addInitCallback({ onInit() }, this)
}
@@ -66,7 +63,7 @@ class DesktopDisplayEventHandler(
object : UserChangeListener {
override fun onUserChanged(newUserId: Int, userContext: Context) {
val displayIds = rootTaskDisplayAreaOrganizer.displayIds
- createDefaultDesksIfNeeded(displayIds.toSet())
+ createDefaultDesksIfNeeded(displayIds.toSet(), newUserId)
}
}
)
@@ -75,15 +72,18 @@ class DesktopDisplayEventHandler(
override fun onDisplayAdded(displayId: Int) {
if (displayId != DEFAULT_DISPLAY) {
- desktopDisplayModeController.refreshDisplayWindowingMode()
+ desktopDisplayModeController.updateExternalDisplayWindowingMode(displayId)
+ // The default display's windowing mode depends on the availability of the external
+ // display. So updating the default display's windowing mode here.
+ desktopDisplayModeController.updateDefaultDisplayWindowingMode()
}
- createDefaultDesksIfNeeded(displayIds = setOf(displayId))
+ createDefaultDesksIfNeeded(displayIds = setOf(displayId), userId = null)
}
override fun onDisplayRemoved(displayId: Int) {
if (displayId != DEFAULT_DISPLAY) {
- desktopDisplayModeController.refreshDisplayWindowingMode()
+ desktopDisplayModeController.updateDefaultDisplayWindowingMode()
}
// TODO: b/362720497 - move desks in closing display to the remaining desk.
@@ -94,28 +94,30 @@ class DesktopDisplayEventHandler(
DesktopExperienceFlags.ENABLE_DISPLAY_CONTENT_MODE_MANAGEMENT.isTrue &&
displayId != DEFAULT_DISPLAY
) {
- desktopDisplayModeController.refreshDisplayWindowingMode()
+ desktopDisplayModeController.updateExternalDisplayWindowingMode(displayId)
+ // The default display's windowing mode depends on the desktop eligibility of the
+ // external display. So updating the default display's windowing mode here.
+ desktopDisplayModeController.updateDefaultDisplayWindowingMode()
}
}
override fun onDeskRemoved(lastDisplayId: Int, deskId: Int) {
- val remainingDesks = desktopRepository.getNumberOfDesks(lastDisplayId)
- if (remainingDesks == 0) {
- logV("All desks removed from display#$lastDisplayId")
- createDefaultDesksIfNeeded(setOf(lastDisplayId))
- }
+ createDefaultDesksIfNeeded(setOf(lastDisplayId), userId = null)
}
- private fun createDefaultDesksIfNeeded(displayIds: Set<Int>) {
+ private fun createDefaultDesksIfNeeded(displayIds: Set<Int>, userId: Int?) {
if (!DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) return
logV("createDefaultDesksIfNeeded displays=%s", displayIds)
mainScope.launch {
desktopRepositoryInitializer.isInitialized.collect { initialized ->
if (!initialized) return@collect
+ val repository =
+ userId?.let { desktopUserRepositories.getProfile(userId) }
+ ?: desktopUserRepositories.current
displayIds
.filter { displayId -> displayId != Display.INVALID_DISPLAY }
.filter { displayId -> supportsDesks(displayId) }
- .filter { displayId -> desktopRepository.getNumberOfDesks(displayId) == 0 }
+ .filter { displayId -> repository.getNumberOfDesks(displayId) == 0 }
.also { displaysNeedingDesk ->
logV(
"createDefaultDesksIfNeeded creating default desks in displays=%s",
@@ -125,7 +127,7 @@ class DesktopDisplayEventHandler(
.forEach { displayId ->
// TODO: b/393978539 - consider activating the desk on creation when
// applicable, such as for connected displays.
- desktopTasksController.createDesk(displayId)
+ desktopTasksController.createDesk(displayId, repository.userId)
}
cancel()
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopDisplayModeController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopDisplayModeController.kt
index ea2fdc0ee8ed..dec489e8fc63 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopDisplayModeController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopDisplayModeController.kt
@@ -56,44 +56,51 @@ class DesktopDisplayModeController(
@ShellMainThread private val mainHandler: Handler,
) {
- private val onTabletModeChangedListener =
- object : InputManager.OnTabletModeChangedListener {
- override fun onTabletModeChanged(whenNanos: Long, inTabletMode: Boolean) {
- refreshDisplayWindowingMode()
- }
- }
-
private val inputDeviceListener =
object : InputManager.InputDeviceListener {
override fun onInputDeviceAdded(deviceId: Int) {
- refreshDisplayWindowingMode()
+ updateDefaultDisplayWindowingMode()
}
override fun onInputDeviceChanged(deviceId: Int) {
- refreshDisplayWindowingMode()
+ updateDefaultDisplayWindowingMode()
}
override fun onInputDeviceRemoved(deviceId: Int) {
- refreshDisplayWindowingMode()
+ updateDefaultDisplayWindowingMode()
}
}
init {
if (DesktopExperienceFlags.FORM_FACTOR_BASED_DESKTOP_FIRST_SWITCH.isTrue) {
- inputManager.registerOnTabletModeChangedListener(
- onTabletModeChangedListener,
- mainHandler,
- )
inputManager.registerInputDeviceListener(inputDeviceListener, mainHandler)
}
}
- fun refreshDisplayWindowingMode() {
+ fun updateExternalDisplayWindowingMode(displayId: Int) {
+ if (!DesktopExperienceFlags.ENABLE_DISPLAY_CONTENT_MODE_MANAGEMENT.isTrue) return
+
+ val desktopModeSupported =
+ displayController.getDisplay(displayId)?.let { display ->
+ DesktopModeStatus.isDesktopModeSupportedOnDisplay(context, display)
+ } ?: false
+ if (!desktopModeSupported) return
+
+ // An external display should always be a freeform display when desktop mode is enabled.
+ updateDisplayWindowingMode(displayId, WINDOWING_MODE_FREEFORM)
+ }
+
+ fun updateDefaultDisplayWindowingMode() {
if (!DesktopExperienceFlags.ENABLE_DISPLAY_WINDOWING_MODE_SWITCHING.isTrue) return
- val targetDisplayWindowingMode = getTargetWindowingModeForDefaultDisplay()
- val tdaInfo = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)
- requireNotNull(tdaInfo) { "DisplayAreaInfo of DEFAULT_DISPLAY must be non-null." }
+ updateDisplayWindowingMode(DEFAULT_DISPLAY, getTargetWindowingModeForDefaultDisplay())
+ }
+
+ private fun updateDisplayWindowingMode(displayId: Int, targetDisplayWindowingMode: Int) {
+ val tdaInfo =
+ requireNotNull(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(displayId)) {
+ "DisplayAreaInfo of display#$displayId must be non-null."
+ }
val currentDisplayWindowingMode = tdaInfo.configuration.windowConfiguration.windowingMode
if (currentDisplayWindowingMode == targetDisplayWindowingMode) {
// Already in the target mode.
@@ -101,15 +108,16 @@ class DesktopDisplayModeController(
}
logV(
- "As an external display is connected, changing default display's windowing mode from" +
- " ${windowingModeToString(currentDisplayWindowingMode)}" +
- " to ${windowingModeToString(targetDisplayWindowingMode)}"
+ "Changing display#%d's windowing mode from %s to %s",
+ displayId,
+ windowingModeToString(currentDisplayWindowingMode),
+ windowingModeToString(targetDisplayWindowingMode),
)
val wct = WindowContainerTransaction()
wct.setWindowingMode(tdaInfo.token, targetDisplayWindowingMode)
shellTaskOrganizer
- .getRunningTasks(DEFAULT_DISPLAY)
+ .getRunningTasks(displayId)
.filter { it.activityType == ACTIVITY_TYPE_STANDARD }
.forEach {
// TODO: b/391965153 - Reconsider the logic under multi-desk window hierarchy
@@ -125,7 +133,7 @@ class DesktopDisplayModeController(
// The override windowing mode of DesktopWallpaper can be UNDEFINED on fullscreen-display
// right after the first launch while its resolved windowing mode is FULLSCREEN. We here
// it has the FULLSCREEN override windowing mode.
- desktopWallpaperActivityTokenProvider.getToken(DEFAULT_DISPLAY)?.let { token ->
+ desktopWallpaperActivityTokenProvider.getToken(displayId)?.let { token ->
wct.setWindowingMode(token, WINDOWING_MODE_FULLSCREEN)
}
transitions.startTransition(TRANSIT_CHANGE, wct, /* handler= */ null)
@@ -139,7 +147,7 @@ class DesktopDisplayModeController(
return true
}
if (DesktopExperienceFlags.FORM_FACTOR_BASED_DESKTOP_FIRST_SWITCH.isTrue) {
- if (isInClamshellMode() || hasAnyMouseDevice()) {
+ if (hasAnyTouchpadDevice() && hasAnyPhysicalKeyboardDevice()) {
return true
}
}
@@ -186,17 +194,25 @@ class DesktopDisplayModeController(
private fun hasExternalDisplay() =
rootTaskDisplayAreaOrganizer.getDisplayIds().any { it != DEFAULT_DISPLAY }
- private fun hasAnyMouseDevice() =
- inputManager.inputDeviceIds.any {
- inputManager.getInputDevice(it)?.supportsSource(InputDevice.SOURCE_MOUSE) == true
+ private fun hasAnyTouchpadDevice() =
+ inputManager.inputDeviceIds.any { deviceId ->
+ inputManager.getInputDevice(deviceId)?.let { device ->
+ device.supportsSource(InputDevice.SOURCE_TOUCHPAD) && device.isEnabled()
+ } ?: false
}
- private fun isInClamshellMode() = inputManager.isInTabletMode() == InputManager.SWITCH_STATE_OFF
+ private fun hasAnyPhysicalKeyboardDevice() =
+ inputManager.inputDeviceIds.any { deviceId ->
+ inputManager.getInputDevice(deviceId)?.let { device ->
+ !device.isVirtual() && device.isFullKeyboard() && device.isEnabled()
+ } ?: false
+ }
private fun isDefaultDisplayDesktopEligible(): Boolean {
- val display = requireNotNull(displayController.getDisplay(DEFAULT_DISPLAY)) {
- "Display object of DEFAULT_DISPLAY must be non-null."
- }
+ val display =
+ requireNotNull(displayController.getDisplay(DEFAULT_DISPLAY)) {
+ "Display object of DEFAULT_DISPLAY must be non-null."
+ }
return DesktopModeStatus.isDesktopModeSupportedOnDisplay(context, display)
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java
index 1c5138f486e4..8bbe36dd6644 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java
@@ -57,7 +57,7 @@ import com.android.wm.shell.shared.bubbles.BubbleAnythingFlagHelper;
import com.android.wm.shell.shared.bubbles.BubbleDropTargetBoundsProvider;
import com.android.wm.shell.windowdecor.tiling.SnapEventHandler;
-import java.util.Arrays;
+import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -307,7 +307,8 @@ public class DesktopModeVisualIndicator {
if (splitRightRegion.contains(x, y)) {
result = IndicatorType.TO_SPLIT_RIGHT_INDICATOR;
}
- if (BubbleAnythingFlagHelper.enableBubbleToFullscreen()) {
+ if (BubbleAnythingFlagHelper.enableBubbleToFullscreen()
+ && mDragStartState == DragStartState.FROM_FULLSCREEN) {
if (calculateBubbleLeftRegion(layout).contains(x, y)) {
result = IndicatorType.TO_BUBBLE_LEFT_INDICATOR;
} else if (calculateBubbleRightRegion(layout).contains(x, y)) {
@@ -415,30 +416,59 @@ public class DesktopModeVisualIndicator {
private List<Pair<Rect, IndicatorType>> initSmallTabletRegions(DisplayLayout layout,
boolean isLeftRightSplit) {
- boolean dragFromFullscreen = mDragStartState == DragStartState.FROM_FULLSCREEN;
- boolean dragFromSplit = mDragStartState == DragStartState.FROM_SPLIT;
- if (isLeftRightSplit && (dragFromFullscreen || dragFromSplit)) {
+ return switch (mDragStartState) {
+ case DragStartState.FROM_FULLSCREEN -> initSmallTabletRegionsFromFullscreen(layout,
+ isLeftRightSplit);
+ case DragStartState.FROM_SPLIT -> initSmallTabletRegionsFromSplit(layout,
+ isLeftRightSplit);
+ default -> Collections.emptyList();
+ };
+ }
+
+ private List<Pair<Rect, IndicatorType>> initSmallTabletRegionsFromFullscreen(
+ DisplayLayout layout, boolean isLeftRightSplit) {
+
+ List<Pair<Rect, IndicatorType>> result = new ArrayList<>();
+ if (BubbleAnythingFlagHelper.enableBubbleToFullscreen()) {
+ result.add(new Pair<>(calculateBubbleLeftRegion(layout), TO_BUBBLE_LEFT_INDICATOR));
+ result.add(new Pair<>(calculateBubbleRightRegion(layout), TO_BUBBLE_RIGHT_INDICATOR));
+ }
+
+ if (isLeftRightSplit) {
int splitRegionWidth = mContext.getResources().getDimensionPixelSize(
com.android.wm.shell.shared.R.dimen.drag_zone_h_split_from_app_width_fold);
- return Arrays.asList(
- new Pair<>(calculateBubbleLeftRegion(layout), TO_BUBBLE_LEFT_INDICATOR),
- new Pair<>(calculateBubbleRightRegion(layout), TO_BUBBLE_RIGHT_INDICATOR),
- new Pair<>(calculateSplitLeftRegion(layout, splitRegionWidth,
- /* captionHeight= */ 0), TO_SPLIT_LEFT_INDICATOR),
- new Pair<>(calculateSplitRightRegion(layout, splitRegionWidth,
- /* captionHeight= */ 0), TO_SPLIT_RIGHT_INDICATOR),
- new Pair<>(new Rect(), TO_FULLSCREEN_INDICATOR) // default to fullscreen
- );
+ result.add(new Pair<>(calculateSplitLeftRegion(layout, splitRegionWidth,
+ /* captionHeight= */ 0), TO_SPLIT_LEFT_INDICATOR));
+ result.add(new Pair<>(calculateSplitRightRegion(layout, splitRegionWidth,
+ /* captionHeight= */ 0), TO_SPLIT_RIGHT_INDICATOR));
}
- if (dragFromFullscreen) {
- // If left/right split is not available, we can only drag fullscreen tasks
- // TODO(b/401352409): add support for top/bottom split zones
- return Arrays.asList(
- new Pair<>(calculateBubbleLeftRegion(layout), TO_BUBBLE_LEFT_INDICATOR),
- new Pair<>(calculateBubbleRightRegion(layout), TO_BUBBLE_RIGHT_INDICATOR),
- new Pair<>(new Rect(), TO_FULLSCREEN_INDICATOR) // default to fullscreen
- );
+ // TODO(b/401352409): add support for top/bottom split zones
+ // default to fullscreen
+ result.add(new Pair<>(new Rect(), TO_FULLSCREEN_INDICATOR));
+ return result;
+ }
+
+ private List<Pair<Rect, IndicatorType>> initSmallTabletRegionsFromSplit(DisplayLayout layout,
+ boolean isLeftRightSplit) {
+ if (!isLeftRightSplit) {
+ // Dragging a top/bottom split is not supported on small tablets
+ return Collections.emptyList();
}
- return Collections.emptyList();
+
+ List<Pair<Rect, IndicatorType>> result = new ArrayList<>();
+ if (BubbleAnythingFlagHelper.enableBubbleAnything()) {
+ result.add(new Pair<>(calculateBubbleLeftRegion(layout), TO_BUBBLE_LEFT_INDICATOR));
+ result.add(new Pair<>(calculateBubbleRightRegion(layout), TO_BUBBLE_RIGHT_INDICATOR));
+ }
+
+ int splitRegionWidth = mContext.getResources().getDimensionPixelSize(
+ com.android.wm.shell.shared.R.dimen.drag_zone_h_split_from_app_width_fold);
+ result.add(new Pair<>(calculateSplitLeftRegion(layout, splitRegionWidth,
+ /* captionHeight= */ 0), TO_SPLIT_LEFT_INDICATOR));
+ result.add(new Pair<>(calculateSplitRightRegion(layout, splitRegionWidth,
+ /* captionHeight= */ 0), TO_SPLIT_RIGHT_INDICATOR));
+ // default to fullscreen
+ result.add(new Pair<>(new Rect(), TO_FULLSCREEN_INDICATOR));
+ return result;
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index 6214f329e0fd..8b1d3fa65ac6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -42,6 +42,7 @@ import android.os.Handler
import android.os.IBinder
import android.os.SystemProperties
import android.os.UserHandle
+import android.os.UserManager
import android.util.Slog
import android.view.Display
import android.view.Display.DEFAULT_DISPLAY
@@ -115,7 +116,6 @@ import com.android.wm.shell.desktopmode.multidesks.DeskTransition
import com.android.wm.shell.desktopmode.multidesks.DesksOrganizer
import com.android.wm.shell.desktopmode.multidesks.DesksTransitionObserver
import com.android.wm.shell.desktopmode.multidesks.OnDeskRemovedListener
-import com.android.wm.shell.desktopmode.multidesks.createDesk
import com.android.wm.shell.desktopmode.persistence.DesktopRepositoryInitializer
import com.android.wm.shell.desktopmode.persistence.DesktopRepositoryInitializer.DeskRecreationFactory
import com.android.wm.shell.draganddrop.DragAndDropController
@@ -163,6 +163,7 @@ import java.util.Optional
import java.util.concurrent.Executor
import java.util.concurrent.TimeUnit
import java.util.function.Consumer
+import kotlin.coroutines.suspendCoroutine
import kotlin.jvm.optionals.getOrNull
/**
@@ -283,14 +284,8 @@ class DesktopTasksController(
if (DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) {
desktopRepositoryInitializer.deskRecreationFactory =
- DeskRecreationFactory { deskUserId, destinationDisplayId, deskId ->
- if (deskUserId != userId) {
- // TODO: b/400984250 - add multi-user support for multi-desk restoration.
- logW("Tried to re-create desk of another user.")
- null
- } else {
- desksOrganizer.createDesk(destinationDisplayId)
- }
+ DeskRecreationFactory { deskUserId, destinationDisplayId, _ ->
+ createDeskSuspending(displayId = destinationDisplayId, userId = deskUserId)
}
}
}
@@ -493,22 +488,55 @@ class DesktopTasksController(
runOnTransitStart?.invoke(transition)
}
- /** Creates a new desk in the given display. */
- fun createDesk(displayId: Int) {
+ /** Adds a new desk to the given display for the given user. */
+ fun createDesk(displayId: Int, userId: Int = this.userId) {
+ logV("addDesk displayId=%d, userId=%d", displayId, userId)
+ val repository = userRepositories.getProfile(userId)
+ createDesk(displayId, userId) { deskId ->
+ if (deskId == null) {
+ logW("Failed to add desk in displayId=%d for userId=%d", displayId, userId)
+ } else {
+ repository.addDesk(displayId = displayId, deskId = deskId)
+ }
+ }
+ }
+
+ private fun createDesk(displayId: Int, userId: Int = this.userId, onResult: (Int?) -> Unit) {
if (displayId == Display.INVALID_DISPLAY) {
logW("createDesk attempt with invalid displayId", displayId)
+ onResult(null)
return
}
- if (DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) {
- desksOrganizer.createDesk(displayId) { deskId ->
- taskRepository.addDesk(displayId = displayId, deskId = deskId)
- }
- } else {
+ if (!DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) {
// In single-desk, the desk reuses the display id.
- taskRepository.addDesk(displayId = displayId, deskId = displayId)
+ logD("createDesk reusing displayId=%d for single-desk", displayId)
+ onResult(displayId)
+ return
+ }
+ if (
+ DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_HSUM.isTrue &&
+ UserManager.isHeadlessSystemUserMode() &&
+ UserHandle.USER_SYSTEM == userId
+ ) {
+ logW("createDesk ignoring attempt for system user")
+ return
+ }
+ desksOrganizer.createDesk(displayId, userId) { deskId ->
+ logD(
+ "createDesk obtained deskId=%d for displayId=%d and userId=%d",
+ deskId,
+ displayId,
+ userId,
+ )
+ onResult(deskId)
}
}
+ private suspend fun createDeskSuspending(displayId: Int, userId: Int = this.userId): Int? =
+ suspendCoroutine { cont ->
+ createDesk(displayId, userId) { deskId -> cont.resumeWith(Result.success(deskId)) }
+ }
+
/** Moves task to desktop mode if task is running, else launches it in desktop mode. */
@JvmOverloads
fun moveTaskToDefaultDeskAndActivate(
@@ -3024,18 +3052,17 @@ class DesktopTasksController(
}
val wct = WindowContainerTransaction()
- if (!DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) {
- tasksToRemove.forEach {
- val task = shellTaskOrganizer.getRunningTaskInfo(it)
- if (task != null) {
- wct.removeTask(task.token)
- } else {
- recentTasksController?.removeBackgroundTask(it)
- }
+ tasksToRemove.forEach {
+ // TODO: b/404595635 - consider moving this block into [DesksOrganizer].
+ val task = shellTaskOrganizer.getRunningTaskInfo(it)
+ if (task != null) {
+ wct.removeTask(task.token)
+ } else {
+ recentTasksController?.removeBackgroundTask(it)
}
- } else {
- // TODO: 362720497 - double check background tasks are also removed.
- desksOrganizer.removeDesk(wct, deskId)
+ }
+ if (DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) {
+ desksOrganizer.removeDesk(wct, deskId, userId)
}
if (!DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue && wct.isEmpty) return
val transition = transitions.startTransition(TRANSIT_CLOSE, wct, /* handler= */ null)
@@ -3601,6 +3628,7 @@ class DesktopTasksController(
pw.println("${prefix}DesktopTasksController")
DesktopModeStatus.dump(pw, innerPrefix, context)
userRepositories.dump(pw, innerPrefix)
+ focusTransitionObserver.dump(pw, innerPrefix)
}
/** The interface for calls from outside the shell, within the host process. */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/DesksOrganizer.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/DesksOrganizer.kt
index 1effcdb20505..605465b15468 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/DesksOrganizer.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/DesksOrganizer.kt
@@ -18,13 +18,11 @@ package com.android.wm.shell.desktopmode.multidesks
import android.app.ActivityManager
import android.window.TransitionInfo
import android.window.WindowContainerTransaction
-import com.android.wm.shell.desktopmode.multidesks.DesksOrganizer.OnCreateCallback
-import kotlin.coroutines.suspendCoroutine
/** An organizer of desk containers in which to host child desktop windows. */
interface DesksOrganizer {
- /** Creates a new desk container in the given display. */
- fun createDesk(displayId: Int, callback: OnCreateCallback)
+ /** Creates a new desk container to use in the given display for the given user. */
+ fun createDesk(displayId: Int, userId: Int, callback: OnCreateCallback)
/** Activates the given desk, making it visible in its display. */
fun activateDesk(wct: WindowContainerTransaction, deskId: Int)
@@ -32,8 +30,8 @@ interface DesksOrganizer {
/** Deactivates the given desk, removing it as the default launch container for new tasks. */
fun deactivateDesk(wct: WindowContainerTransaction, deskId: Int)
- /** Removes the given desk and its desktop windows. */
- fun removeDesk(wct: WindowContainerTransaction, deskId: Int)
+ /** Removes the given desk of the given user. */
+ fun removeDesk(wct: WindowContainerTransaction, deskId: Int, userId: Int)
/** Moves the given task to the given desk. */
fun moveTaskToDesk(
@@ -87,9 +85,3 @@ interface DesksOrganizer {
fun onCreated(deskId: Int)
}
}
-
-/** Creates a new desk container in the given display. */
-suspend fun DesksOrganizer.createDesk(displayId: Int): Int = suspendCoroutine { cont ->
- val onCreateCallback = OnCreateCallback { deskId -> cont.resumeWith(Result.success(deskId)) }
- createDesk(displayId, onCreateCallback)
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/RootTaskDesksOrganizer.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/RootTaskDesksOrganizer.kt
index c30987ac7640..e4edeb95be6d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/RootTaskDesksOrganizer.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/RootTaskDesksOrganizer.kt
@@ -30,6 +30,7 @@ import android.window.TransitionInfo
import android.window.WindowContainerToken
import android.window.WindowContainerTransaction
import androidx.core.util.forEach
+import androidx.core.util.valueIterator
import com.android.internal.annotations.VisibleForTesting
import com.android.internal.protolog.ProtoLog
import com.android.wm.shell.ShellTaskOrganizer
@@ -40,7 +41,13 @@ import com.android.wm.shell.sysui.ShellCommandHandler
import com.android.wm.shell.sysui.ShellInit
import java.io.PrintWriter
-/** A [DesksOrganizer] that uses root tasks as the container of each desk. */
+/**
+ * A [DesksOrganizer] that uses root tasks as the container of each desk.
+ *
+ * Note that root tasks are reusable between multiple users at the same time, and may also be
+ * pre-created to have one ready for the first entry to the default desk, so root-task existence
+ * does not imply a formal desk exists to the user.
+ */
class RootTaskDesksOrganizer(
shellInit: ShellInit,
shellCommandHandler: ShellCommandHandler,
@@ -65,9 +72,26 @@ class RootTaskDesksOrganizer(
}
}
- override fun createDesk(displayId: Int, callback: OnCreateCallback) {
- logV("createDesk in display: %d", displayId)
- createDeskRootRequests += CreateDeskRequest(displayId, callback)
+ override fun createDesk(displayId: Int, userId: Int, callback: OnCreateCallback) {
+ logV("createDesk in displayId=%d userId=%s", displayId, userId)
+ // Find an existing desk that is not yet used by this user.
+ val unassignedDesk =
+ deskRootsByDeskId
+ .valueIterator()
+ .asSequence()
+ .filterNot { desk -> userId in desk.users }
+ .firstOrNull()
+ if (unassignedDesk != null) {
+ unassignedDesk.users.add(userId)
+ callback.onCreated(unassignedDesk.deskId)
+ return
+ }
+ createDeskRoot(displayId, userId, callback)
+ }
+
+ private fun createDeskRoot(displayId: Int, userId: Int, callback: OnCreateCallback) {
+ logV("createDeskRoot in display: %d for user: %d", displayId, userId)
+ createDeskRootRequests += CreateDeskRequest(displayId, userId, callback)
shellTaskOrganizer.createRootTask(
displayId,
WINDOWING_MODE_FREEFORM,
@@ -76,31 +100,52 @@ class RootTaskDesksOrganizer(
)
}
- override fun removeDesk(wct: WindowContainerTransaction, deskId: Int) {
- logV("removeDesk %d", deskId)
- deskRootsByDeskId[deskId]?.let { root -> wct.removeRootTask(root.token) }
- deskMinimizationRootsByDeskId[deskId]?.let { root -> wct.removeRootTask(root.token) }
+ override fun removeDesk(wct: WindowContainerTransaction, deskId: Int, userId: Int) {
+ logV("removeDesk %d for userId=%d", deskId, userId)
+ val deskRoot = deskRootsByDeskId[deskId]
+ if (deskRoot == null) {
+ logW("removeDesk attempted to remove non-existent desk=%d", deskId)
+ return
+ }
+ updateLaunchRoot(wct, deskId, enabled = false)
+ deskRoot.users.remove(userId)
+ if (deskRoot.users.isEmpty()) {
+ // No longer in use by any users, remove it completely.
+ logD("removeDesk %d is no longer used by any users, removing it completely", deskId)
+ wct.removeRootTask(deskRoot.token)
+ deskMinimizationRootsByDeskId[deskId]?.let { root -> wct.removeRootTask(root.token) }
+ }
}
override fun activateDesk(wct: WindowContainerTransaction, deskId: Int) {
logV("activateDesk %d", deskId)
val root = checkNotNull(deskRootsByDeskId[deskId]) { "Root not found for desk: $deskId" }
wct.reorder(root.token, /* onTop= */ true)
- wct.setLaunchRoot(
- /* container= */ root.taskInfo.token,
- /* windowingModes= */ intArrayOf(WINDOWING_MODE_FREEFORM, WINDOWING_MODE_UNDEFINED),
- /* activityTypes= */ intArrayOf(ACTIVITY_TYPE_UNDEFINED, ACTIVITY_TYPE_STANDARD),
- )
+ updateLaunchRoot(wct, deskId, enabled = true)
}
override fun deactivateDesk(wct: WindowContainerTransaction, deskId: Int) {
logV("deactivateDesk %d", deskId)
+ updateLaunchRoot(wct, deskId, enabled = false)
+ }
+
+ private fun updateLaunchRoot(wct: WindowContainerTransaction, deskId: Int, enabled: Boolean) {
val root = checkNotNull(deskRootsByDeskId[deskId]) { "Root not found for desk: $deskId" }
- wct.setLaunchRoot(
- /* container= */ root.taskInfo.token,
- /* windowingModes= */ null,
- /* activityTypes= */ null,
- )
+ root.isLaunchRootRequested = enabled
+ logD("updateLaunchRoot deskId=%d enabled=%b", deskId, enabled)
+ if (enabled) {
+ wct.setLaunchRoot(
+ /* container= */ root.taskInfo.token,
+ /* windowingModes= */ intArrayOf(WINDOWING_MODE_FREEFORM, WINDOWING_MODE_UNDEFINED),
+ /* activityTypes= */ intArrayOf(ACTIVITY_TYPE_UNDEFINED, ACTIVITY_TYPE_STANDARD),
+ )
+ } else {
+ wct.setLaunchRoot(
+ /* container= */ root.taskInfo.token,
+ /* windowingModes= */ null,
+ /* activityTypes= */ null,
+ )
+ }
}
override fun moveTaskToDesk(
@@ -275,7 +320,13 @@ class RootTaskDesksOrganizer(
// Appearing root matches desk request.
val deskId = taskInfo.taskId
logV("Desk #$deskId appeared")
- deskRootsByDeskId[deskId] = DeskRoot(deskId, taskInfo, leash)
+ deskRootsByDeskId[deskId] =
+ DeskRoot(
+ deskId = deskId,
+ taskInfo = taskInfo,
+ leash = leash,
+ users = mutableSetOf(deskRequest.userId),
+ )
createDeskRootRequests.remove(deskRequest)
deskRequest.onCreateCallback.onCreated(deskId)
createDeskMinimizationRoot(displayId = appearingInDisplayId, deskId = deskId)
@@ -430,6 +481,8 @@ class RootTaskDesksOrganizer(
val taskInfo: RunningTaskInfo,
val leash: SurfaceControl,
val children: MutableSet<Int> = mutableSetOf(),
+ val users: MutableSet<Int> = mutableSetOf(),
+ var isLaunchRootRequested: Boolean = false,
) {
val token: WindowContainerToken = taskInfo.token
}
@@ -449,15 +502,24 @@ class RootTaskDesksOrganizer(
private data class CreateDeskRequest(
val displayId: Int,
+ val userId: Int,
val onCreateCallback: OnCreateCallback,
)
private data class CreateDeskMinimizationRootRequest(val displayId: Int, val deskId: Int)
+ private fun logD(msg: String, vararg arguments: Any?) {
+ ProtoLog.d(WM_SHELL_DESKTOP_MODE, "%s: $msg", TAG, *arguments)
+ }
+
private fun logV(msg: String, vararg arguments: Any?) {
ProtoLog.v(WM_SHELL_DESKTOP_MODE, "%s: $msg", TAG, *arguments)
}
+ private fun logW(msg: String, vararg arguments: Any?) {
+ ProtoLog.w(WM_SHELL_DESKTOP_MODE, "%s: $msg", TAG, *arguments)
+ }
+
private fun logE(msg: String, vararg arguments: Any?) {
ProtoLog.e(WM_SHELL_DESKTOP_MODE, "%s: $msg", TAG, *arguments)
}
@@ -473,7 +535,9 @@ class RootTaskDesksOrganizer(
val minimizationRoot = deskMinimizationRootsByDeskId[deskId]
pw.println("$innerPrefix #$deskId visible=${root.taskInfo.isVisible}")
pw.println("$innerPrefix displayId=${root.taskInfo.displayId}")
+ pw.println("$innerPrefix isLaunchRootRequested=${root.isLaunchRootRequested}")
pw.println("$innerPrefix children=${root.children}")
+ pw.println("$innerPrefix users=${root.users}")
pw.println("$innerPrefix minimization root:")
pw.println("$innerPrefix rootId=${minimizationRoot?.rootId}")
if (minimizationRoot != null) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
index 51ef0ec60c3a..9ec1c7d65a6e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
@@ -50,7 +50,9 @@ import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.Rect;
import android.os.Bundle;
+import android.os.Debug;
import android.os.IBinder;
+import android.util.Log;
import android.view.SurfaceControl;
import android.view.WindowManager;
import android.window.TransitionInfo;
@@ -315,6 +317,20 @@ public class PipTransition extends PipTransitionController implements
return startAlphaTypeEnterAnimation(info, startTransaction, finishTransaction,
finishCallback);
}
+
+ TransitionInfo.Change pipActivityChange = PipTransitionUtils
+ .getDeferConfigActivityChange(info, pipChange.getTaskInfo().getToken());
+ if (pipActivityChange == null) {
+ // Legacy-enter and swipe-pip-to-home filters did not resolve a scheduled PiP entry.
+ // Bounds-type enter animation is the last resort, and it requires a config-at-end
+ // activity amongst the list of changes. If no such change, something went wrong.
+ Log.wtf(TAG, String.format("""
+ PipTransition.startAnimation didn't handle a scheduled PiP entry
+ transitionInfo=%s,
+ callers=%s""", info, Debug.getCallers(4)));
+ return false;
+ }
+
return startBoundsTypeEnterAnimation(info, startTransaction, finishTransaction,
finishCallback);
} else if (transition == mExitViaExpandTransition) {
@@ -839,26 +855,15 @@ public class PipTransition extends PipTransitionController implements
return true;
}
- // Sometimes root PiP task can have TF children. These child containers can be collected
- // even if they can promote to their parents: e.g. if they are marked as "organized".
- // So we count the chain of containers under PiP task as one "real" changing target;
- // iterate through changes bottom-to-top to properly identify parents.
- int expectedTargetCount = 1;
- WindowContainerToken lastPipChildToken = pipChange.getContainer();
- for (int i = info.getChanges().size() - 1; i >= 0; --i) {
- TransitionInfo.Change change = info.getChanges().get(i);
- if (change == pipChange || change.getContainer() == null) continue;
- if (change.getParent() != null && change.getParent().equals(lastPipChildToken)) {
- // Allow an extra change since our pinned root task has a child.
- ++expectedTargetCount;
- lastPipChildToken = change.getContainer();
- }
- }
-
- // If the only root task change in the changes list is a opening type PiP task,
- // then this is legacy-enter PiP.
- return info.getChanges().size() == expectedTargetCount
- && TransitionUtil.isOpeningMode(pipChange.getMode());
+ // #getEnterPipTransaction() always attempts to mark PiP activity as config-at-end one.
+ // However, the activity will only actually be marked config-at-end by Core if it is
+ // both isVisible and isVisibleRequested, which is when we can't run bounds animation.
+ //
+ // So we can use the absence of a config-at-end activity as a signal that we should run
+ // a legacy-enter PiP animation instead.
+ return TransitionUtil.isOpeningMode(pipChange.getMode())
+ && PipTransitionUtils.getDeferConfigActivityChange(
+ info, pipChange.getContainer()) == null;
}
return false;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index 10db5ca03637..d240aca522bb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -3618,9 +3618,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
finishEnterSplitScreen(finishT);
addDividerBarToTransition(info, true /* show */);
- if (Flags.enableFlexibleTwoAppSplit()) {
- addAllDimLayersToTransition(info, true /* show */);
- }
+ addAllDimLayersToTransition(info, true /* show */);
return true;
}
@@ -3871,9 +3869,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
}
addDividerBarToTransition(info, false /* show */);
- if (Flags.enableFlexibleTwoAppSplit()) {
- addAllDimLayersToTransition(info, false /* show */);
- }
+ addAllDimLayersToTransition(info, false /* show */);
}
/** Call this when the recents animation canceled during split-screen. */
@@ -3999,8 +3995,15 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
info.addChange(barChange);
}
- /** Add dim layers to the transition, so that they can be hidden/shown when animation starts. */
+ /**
+ * Add dim layers to the transition, so that they can be hidden/shown when animation starts.
+ * They're only added if there is at least one offscreen app.
+ */
private void addAllDimLayersToTransition(@NonNull TransitionInfo info, boolean show) {
+ if (!mSplitState.currentStateSupportsOffscreenApps()) {
+ return;
+ }
+
if (Flags.enableFlexibleSplit()) {
List<StageTaskListener> stages = mStageOrderOperator.getActiveStages();
for (int i = 0; i < stages.size(); i++) {
@@ -4008,7 +4011,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mSplitState.getCurrentLayout().get(i).roundOut(mTempRect1);
addDimLayerToTransition(info, show, stage, mTempRect1);
}
- } else {
+ } else if (enableFlexibleTwoAppSplit()) {
addDimLayerToTransition(info, show, mMainStage, getMainStageBounds());
addDimLayerToTransition(info, show, mSideStage, getSideStageBounds());
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultSurfaceAnimator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultSurfaceAnimator.java
index bff08ba6d88f..3240cbb779c6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultSurfaceAnimator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultSurfaceAnimator.java
@@ -169,7 +169,7 @@ public class DefaultSurfaceAnimator {
needCrop = true;
}
if (needCrop) {
- t.setCrop(leash, mAnimClipRect);
+ t.setWindowCrop(leash, mAnimClipRect);
}
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
index e9200834c5dd..5b6993863c5d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
@@ -133,6 +133,7 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
private final DisplayController mDisplayController;
private final Context mContext;
private final Handler mMainHandler;
+ private final Handler mAnimHandler;
private final ShellExecutor mMainExecutor;
private final ShellExecutor mAnimExecutor;
private final TransitionAnimation mTransitionAnimation;
@@ -171,6 +172,7 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
@NonNull TransactionPool transactionPool,
@NonNull ShellExecutor mainExecutor, @NonNull Handler mainHandler,
@NonNull ShellExecutor animExecutor,
+ @NonNull Handler animHandler,
@NonNull RootTaskDisplayAreaOrganizer rootTDAOrganizer,
@NonNull InteractionJankMonitor interactionJankMonitor) {
mDisplayController = displayController;
@@ -179,6 +181,7 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
mMainHandler = mainHandler;
mMainExecutor = mainExecutor;
mAnimExecutor = animExecutor;
+ mAnimHandler = animHandler;
mTransitionAnimation = new TransitionAnimation(context, false /* debug */, Transitions.TAG);
mCurrentUserId = UserHandle.myUserId();
mDevicePolicyManager = mContext.getSystemService(DevicePolicyManager.class);
@@ -349,10 +352,6 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
mAnimations.put(transition, animations);
final boolean isTaskTransition = isTaskTransition(info);
- if (isTaskTransition) {
- mInteractionJankMonitor.begin(info.getRoot(0).getLeash(), mContext,
- mMainHandler, CUJ_DEFAULT_TASK_TO_TASK_ANIMATION);
- }
final Runnable onAnimFinish = () -> {
if (!animations.isEmpty()) return;
@@ -642,6 +641,10 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
// now start animations. they are started on another thread, so we have to post them
// *after* applying the startTransaction
mAnimExecutor.execute(() -> {
+ if (isTaskTransition) {
+ mInteractionJankMonitor.begin(info.getRoot(0).getLeash(), mContext,
+ mAnimHandler, CUJ_DEFAULT_TASK_TO_TASK_ANIMATION);
+ }
for (int i = 0; i < animations.size(); ++i) {
animations.get(i).start();
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/FocusTransitionObserver.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/FocusTransitionObserver.java
index f0f1ad05008b..b91fb048dc09 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/FocusTransitionObserver.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/FocusTransitionObserver.java
@@ -31,6 +31,7 @@ import android.annotation.NonNull;
import android.app.ActivityManager.RunningTaskInfo;
import android.os.RemoteException;
import android.util.ArraySet;
+import android.util.IndentingPrintWriter;
import android.util.Slog;
import android.util.SparseArray;
import android.window.TransitionInfo;
@@ -38,6 +39,7 @@ import android.window.TransitionInfo;
import com.android.wm.shell.shared.FocusTransitionListener;
import com.android.wm.shell.shared.IFocusTransitionListener;
+import java.io.PrintWriter;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -237,4 +239,21 @@ public class FocusTransitionObserver {
}
return task.displayId == mFocusedDisplayId && isFocusedOnDisplay(task);
}
+
+ /** Dumps focused display and tasks. */
+ public void dump(PrintWriter originalWriter, String prefix) {
+ final IndentingPrintWriter writer =
+ new IndentingPrintWriter(originalWriter, " ", prefix);
+ writer.println("FocusTransitionObserver:");
+ writer.increaseIndent();
+ writer.printf("currentFocusedDisplayId=%d\n", mFocusedDisplayId);
+ writer.println("currentFocusedTaskOnDisplay:");
+ writer.increaseIndent();
+ for (int i = 0; i < mFocusedTaskOnDisplay.size(); i++) {
+ writer.printf("Display #%d: taskId=%d topActivity=%s\n",
+ mFocusedTaskOnDisplay.keyAt(i),
+ mFocusedTaskOnDisplay.valueAt(i).taskId,
+ mFocusedTaskOnDisplay.valueAt(i).topActivity);
+ }
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
index 3dc8733c879d..84724268cfc2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
@@ -215,6 +215,7 @@ public class Transitions implements RemoteCallable<Transitions>,
private final Context mContext;
private final ShellExecutor mMainExecutor;
private final ShellExecutor mAnimExecutor;
+ private final Handler mAnimHandler;
private final TransitionPlayerImpl mPlayerImpl;
private final DefaultTransitionHandler mDefaultTransitionHandler;
private final RemoteTransitionHandler mRemoteTransitionHandler;
@@ -319,11 +320,12 @@ public class Transitions implements RemoteCallable<Transitions>,
@NonNull ShellExecutor mainExecutor,
@NonNull Handler mainHandler,
@NonNull ShellExecutor animExecutor,
+ @NonNull Handler animHandler,
@NonNull HomeTransitionObserver homeTransitionObserver,
@NonNull FocusTransitionObserver focusTransitionObserver) {
this(context, shellInit, new ShellCommandHandler(), shellController, organizer, pool,
displayController, displayInsetsController, mainExecutor, mainHandler, animExecutor,
- new RootTaskDisplayAreaOrganizer(mainExecutor, context, shellInit),
+ animHandler, new RootTaskDisplayAreaOrganizer(mainExecutor, context, shellInit),
homeTransitionObserver, focusTransitionObserver);
}
@@ -338,6 +340,7 @@ public class Transitions implements RemoteCallable<Transitions>,
@NonNull ShellExecutor mainExecutor,
@NonNull Handler mainHandler,
@NonNull ShellExecutor animExecutor,
+ @NonNull Handler animHandler,
@NonNull RootTaskDisplayAreaOrganizer rootTDAOrganizer,
@NonNull HomeTransitionObserver homeTransitionObserver,
@NonNull FocusTransitionObserver focusTransitionObserver) {
@@ -345,11 +348,12 @@ public class Transitions implements RemoteCallable<Transitions>,
mContext = context;
mMainExecutor = mainExecutor;
mAnimExecutor = animExecutor;
+ mAnimHandler = animHandler;
mDisplayController = displayController;
mPlayerImpl = new TransitionPlayerImpl();
mDefaultTransitionHandler = new DefaultTransitionHandler(context, shellInit,
displayController, displayInsetsController, pool, mainExecutor, mainHandler,
- animExecutor, rootTDAOrganizer, InteractionJankMonitor.getInstance());
+ animExecutor, mAnimHandler, rootTDAOrganizer, InteractionJankMonitor.getInstance());
mRemoteTransitionHandler = new RemoteTransitionHandler(mMainExecutor);
mShellCommandHandler = shellCommandHandler;
mShellController = shellController;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt
index cdadce57d610..71bb153e4b1e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt
@@ -508,6 +508,9 @@ class HandleMenu(
private val iconButtonRippleRadius = context.resources.getDimensionPixelSize(
R.dimen.desktop_mode_handle_menu_icon_button_ripple_radius
)
+ private val handleMenuCornerRadius = context.resources.getDimensionPixelSize(
+ R.dimen.desktop_mode_handle_menu_corner_radius
+ )
private val iconButtonDrawableInsetsBase = DrawableInsets(
t = iconButtondrawableBaseInset,
b = iconButtondrawableBaseInset, l = iconButtondrawableBaseInset,
@@ -866,14 +869,21 @@ class HandleMenu(
private fun bindMoreActionsPill(style: MenuStyle) {
moreActionsPill.background.setTint(style.backgroundColor)
-
- arrayOf(
+ val buttons = arrayOf(
screenshotBtn to SHOULD_SHOW_SCREENSHOT_BUTTON,
newWindowBtn to shouldShowNewWindowButton,
manageWindowBtn to shouldShowManageWindowsButton,
changeAspectRatioBtn to shouldShowChangeAspectRatioButton,
restartBtn to shouldShowRestartButton,
- ).forEach { (button, shouldShow) ->
+ )
+ val firstVisible = buttons.find { it.second }?.first
+ val lastVisible = buttons.findLast { it.second }?.first
+
+ buttons.forEach { (button, shouldShow) ->
+ val topRadius =
+ if (button == firstVisible) handleMenuCornerRadius.toFloat() else 0f
+ val bottomRadius =
+ if (button == lastVisible) handleMenuCornerRadius.toFloat() else 0f
button.apply {
isGone = !shouldShow
textView.apply {
@@ -881,6 +891,13 @@ class HandleMenu(
startMarquee()
}
iconView.imageTintList = ColorStateList.valueOf(style.textColor)
+ background = createBackgroundDrawable(
+ color = style.textColor,
+ cornerRadius = floatArrayOf(
+ topRadius, topRadius, topRadius, topRadius,
+ bottomRadius, bottomRadius, bottomRadius, bottomRadius
+ ),
+ drawableInsets = DrawableInsets())
}
}
}
@@ -899,6 +916,10 @@ class HandleMenu(
openInAppOrBrowserBtn.apply {
contentDescription = btnText
+ background = createBackgroundDrawable(
+ color = style.textColor,
+ cornerRadius = handleMenuCornerRadius,
+ drawableInsets = DrawableInsets())
textView.apply {
text = btnText
setTextColor(style.textColor)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/AppHandleAndHeaderVisibilityHelper.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/AppHandleAndHeaderVisibilityHelper.kt
index 39ccf5bd03a7..950eeccf6a4a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/AppHandleAndHeaderVisibilityHelper.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/AppHandleAndHeaderVisibilityHelper.kt
@@ -23,6 +23,7 @@ import android.view.WindowManager
import android.window.DesktopExperienceFlags.ENABLE_BUG_FIXES_FOR_SECONDARY_DISPLAY
import com.android.wm.shell.common.DisplayController
import com.android.wm.shell.desktopmode.DesktopWallpaperActivity.Companion.isWallpaperTask
+import com.android.wm.shell.shared.bubbles.BubbleAnythingFlagHelper
import com.android.wm.shell.shared.desktopmode.DesktopModeCompatPolicy
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
import com.android.wm.shell.splitscreen.SplitScreenController
@@ -52,7 +53,8 @@ class AppHandleAndHeaderVisibilityHelper (
private fun allowedForTask(taskInfo: ActivityManager.RunningTaskInfo): Boolean {
// TODO (b/382023296): Remove once we no longer rely on
// Flags.enableBugFixesForSecondaryDisplay as it is taken care of in #allowedForDisplay
- if (displayController.getDisplay(taskInfo.displayId) == null) {
+ val display = displayController.getDisplay(taskInfo.displayId)
+ if (display == null) {
// If DisplayController doesn't have it tracked, it could be a private/managed display.
return false
}
@@ -68,8 +70,7 @@ class AppHandleAndHeaderVisibilityHelper (
// TODO (b/382023296): Remove once we no longer rely on
// Flags.enableBugFixesForSecondaryDisplay as it is taken care of in #allowedForDisplay
val isOnLargeScreen =
- displayController.getDisplay(taskInfo.displayId).minSizeDimensionDp >=
- WindowManager.LARGE_SCREEN_SMALLEST_SCREEN_WIDTH_DP
+ display.minSizeDimensionDp >= WindowManager.LARGE_SCREEN_SMALLEST_SCREEN_WIDTH_DP
if (!DesktopModeStatus.canEnterDesktopMode(context)
&& DesktopModeStatus.overridesShowAppHandle(context)
&& !isOnLargeScreen
@@ -78,6 +79,14 @@ class AppHandleAndHeaderVisibilityHelper (
// small screens
return false
}
+ if (BubbleAnythingFlagHelper.enableBubbleToFullscreen()
+ && !DesktopModeStatus.isDesktopModeSupportedOnDisplay(context, display)
+ ) {
+ // TODO(b/388853233): enable handles for split tasks once drag to bubble is enabled
+ if (taskInfo.windowingMode != WindowConfiguration.WINDOWING_MODE_FULLSCREEN) {
+ return false
+ }
+ }
return DesktopModeStatus.canEnterDesktopModeOrShowAppHandle(context)
&& !isWallpaperTask(taskInfo)
&& taskInfo.windowingMode != WindowConfiguration.WINDOWING_MODE_PINNED
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/ButtonBackgroundDrawableUtils.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/ButtonBackgroundDrawableUtils.kt
index f08cfa987cc7..33e743016d0d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/ButtonBackgroundDrawableUtils.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/ButtonBackgroundDrawableUtils.kt
@@ -51,10 +51,20 @@ fun replaceColorAlpha(@ColorInt color: Int, alpha: Int): Int {
*/
fun createBackgroundDrawable(
@ColorInt color: Int, cornerRadius: Int, drawableInsets: DrawableInsets
+): Drawable = createBackgroundDrawable(
+ color,
+ FloatArray(8) { cornerRadius.toFloat() },
+ drawableInsets)
+
+/**
+ * Creates a background drawable with specified color, corner radius, and insets.
+ */
+fun createBackgroundDrawable(
+ @ColorInt color: Int, cornerRadius: FloatArray, drawableInsets: DrawableInsets
): Drawable = LayerDrawable(arrayOf(
ShapeDrawable().apply {
shape = RoundRectShape(
- FloatArray(8) { cornerRadius.toFloat() },
+ cornerRadius,
/* inset= */ null,
/* innerRadii= */ null
)
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/Android.bp b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/Android.bp
index 7585c977809e..50581f7e01f3 100644
--- a/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/Android.bp
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/Android.bp
@@ -33,7 +33,6 @@ android_test {
"WMShellFlickerTestsBase",
"WMShellScenariosDesktopMode",
"WMShellTestUtils",
- "ui-trace-collector",
],
data: ["trace_config/*"],
}
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/UnminimizeAppFromTaskbar.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/UnminimizeAppFromTaskbar.kt
index 7d9f2bf8fdf6..05ddb4043b82 100644
--- a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/UnminimizeAppFromTaskbar.kt
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/UnminimizeAppFromTaskbar.kt
@@ -19,7 +19,7 @@ package com.android.wm.shell.scenarios
import android.app.Instrumentation
import android.tools.NavBar
import android.tools.Rotation
-import android.tools.device.apphelpers.GmailAppHelper
+import android.tools.device.apphelpers.CalculatorAppHelper
import android.tools.flicker.rules.ChangeDisplayOrientationRule
import android.tools.traces.parsers.WindowManagerStateHelper
import androidx.test.platform.app.InstrumentationRegistry
@@ -44,8 +44,9 @@ abstract class UnminimizeAppFromTaskbar(val rotation: Rotation = Rotation.ROTATI
private val wmHelper = WindowManagerStateHelper(instrumentation)
private val device = UiDevice.getInstance(instrumentation)
private val testApp = DesktopModeAppHelper(SimpleAppHelper(instrumentation))
- private val gmailHelper = GmailAppHelper(instrumentation)
- private val gmailApp = DesktopModeAppHelper(gmailHelper)
+ private val calculatorHelper = CalculatorAppHelper(instrumentation)
+ private val calculatorApp = DesktopModeAppHelper(calculatorHelper)
+
@Rule
@JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation)
@@ -59,20 +60,20 @@ abstract class UnminimizeAppFromTaskbar(val rotation: Rotation = Rotation.ROTATI
ChangeDisplayOrientationRule.setRotation(rotation)
testApp.enterDesktopMode(wmHelper, device)
tapl.showTaskbarIfHidden()
- gmailApp.launchViaIntent(wmHelper)
- gmailApp.minimizeDesktopApp(wmHelper, device)
+ calculatorApp.launchViaIntent(wmHelper)
+ calculatorApp.minimizeDesktopApp(wmHelper, device)
}
@Test
open fun unminimizeApp() {
tapl.launchedAppState.taskbar
- .getAppIcon(gmailHelper.appName)
- .launch(gmailHelper.packageName)
+ .getAppIcon(calculatorHelper.appName)
+ .launch(calculatorApp.packageName)
}
@After
fun teardown() {
testApp.exit(wmHelper)
- gmailApp.exit(wmHelper)
+ calculatorApp.exit(wmHelper)
}
-} \ No newline at end of file
+}
diff --git a/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/AndroidTestTemplate.xml b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/AndroidTestTemplate.xml
index 7659ec903480..8cbec687d8d8 100644
--- a/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/AndroidTestTemplate.xml
+++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/AndroidTestTemplate.xml
@@ -74,10 +74,6 @@
<option name="shell-timeout" value="6600s"/>
<option name="test-timeout" value="6000s"/>
<option name="hidden-api-checks" value="false"/>
- <option name="device-listeners" value="android.tools.collectors.DefaultUITraceListener"/>
- <!-- DefaultUITraceListener args -->
- <option name="instrumentation-arg" key="per_run" value="true"/>
- <option name="instrumentation-arg" key="perfetto_persist_pid_track" value="true"/>
</test>
<!-- Needed for pulling the collected trace config on to the host -->
<metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
diff --git a/libs/WindowManager/Shell/tests/e2e/utils/src/com/android/wm/shell/SimulatedConnectedDisplayTestRule.kt b/libs/WindowManager/Shell/tests/e2e/utils/src/com/android/wm/shell/SimulatedConnectedDisplayTestRule.kt
deleted file mode 100644
index f9b69d3f5f7e..000000000000
--- a/libs/WindowManager/Shell/tests/e2e/utils/src/com/android/wm/shell/SimulatedConnectedDisplayTestRule.kt
+++ /dev/null
@@ -1,172 +0,0 @@
-/*
- * Copyright 2025 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell
-
-import android.graphics.Point
-import android.hardware.display.DisplayManager
-import android.hardware.display.DisplayManager.DisplayListener
-import android.os.Handler
-import android.os.Looper
-import android.provider.Settings
-import android.util.Log
-import androidx.test.platform.app.InstrumentationRegistry
-import kotlin.time.Duration.Companion.seconds
-import kotlinx.coroutines.channels.awaitClose
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.callbackFlow
-import kotlinx.coroutines.flow.take
-import kotlinx.coroutines.runBlocking
-import kotlinx.coroutines.withTimeoutOrNull
-import org.junit.rules.TestRule
-import org.junit.runner.Description
-import org.junit.runners.model.Statement
-
-/**
- * A TestRule to manage multiple simulated connected overlay displays.
- */
-class SimulatedConnectedDisplayTestRule : TestRule {
-
- private val context = InstrumentationRegistry.getInstrumentation().targetContext
- private val displayManager = context.getSystemService(DisplayManager::class.java)
- private val addedDisplays = mutableListOf<Int>()
-
- override fun apply(base: Statement, description: Description): Statement =
- object : Statement() {
- override fun evaluate() {
- try {
- base.evaluate()
- } finally {
- teardown()
- }
- }
- }
-
- private fun teardown() {
- cleanupTestDisplays()
- }
-
- /**
- * Adds multiple overlay displays with specified dimensions. Any existing overlay displays
- * will be removed before adding the new ones.
- *
- * @param displays A list of [Point] objects, where each [Point] represents the
- * width and height of a simulated display.
- * @return List of displayIds of added displays.
- */
- fun setupTestDisplays(displays: List<Point>): List<Int> = runBlocking {
- // Cleanup any existing overlay displays.
- cleanupTestDisplays()
-
- if (displays.isEmpty()) {
- Log.w(TAG, "setupTestDisplays called with an empty list. No displays created.")
- return@runBlocking emptyList()
- }
-
- val displayAddedFlow: Flow<Int> = callbackFlow {
- val listener = object : DisplayListener {
- override fun onDisplayAdded(displayId: Int) {
- trySend(displayId)
- }
-
- override fun onDisplayRemoved(displayId: Int) {}
- override fun onDisplayChanged(displayId: Int) {}
- }
-
- val handler = Handler(Looper.getMainLooper())
- displayManager.registerDisplayListener(listener, handler)
-
- awaitClose {
- displayManager.unregisterDisplayListener(listener)
- }
- }
-
- val displaySettings = displays.joinToString(separator = ";") { size ->
- "${size.x}x${size.y}/$DEFAULT_DENSITY"
- }
-
- // Add the overlay displays
- Settings.Global.putString(
- InstrumentationRegistry.getInstrumentation().context.contentResolver,
- Settings.Global.OVERLAY_DISPLAY_DEVICES,
- displaySettings
- )
- withTimeoutOrNull(TIMEOUT) {
- displayAddedFlow.take(displays.size).collect { displayId ->
- addedDisplays.add(displayId)
- }
- } ?: error("Timed out waiting for displays to be added.")
- addedDisplays
- }
-
- /**
- * Adds multiple overlay displays with default dimensions. Any existing overlay displays
- * will be removed before adding the new ones.
- *
- * @param count number of displays to add.
- * @return List of displayIds of added displays.
- */
- fun setupTestDisplays(count: Int): List<Int> {
- val displays = List(count) { Point(DEFAULT_WIDTH, DEFAULT_HEIGHT) }
- return setupTestDisplays(displays)
- }
-
- private fun cleanupTestDisplays() = runBlocking {
- val displayRemovedFlow: Flow<Int> = callbackFlow {
- val listener = object : DisplayListener {
- override fun onDisplayAdded(displayId: Int) {}
- override fun onDisplayRemoved(displayId: Int) {
- trySend(displayId)
- }
-
- override fun onDisplayChanged(displayId: Int) {}
- }
- val handler = Handler(Looper.getMainLooper())
- displayManager.registerDisplayListener(listener, handler)
-
- awaitClose {
- displayManager.unregisterDisplayListener(listener)
- }
- }
-
- // Remove overlay displays. We'll execute this regardless of addedDisplays just to
- // ensure all overlay displays are removed before and after the test.
- // Note: If we want to restore the original overlay display added before this test (and its
- // topology), it will be complicated as re-adding overlay display would lead to different
- // displayId and topology could not be restored easily.
- Settings.Global.putString(
- InstrumentationRegistry.getInstrumentation().context.contentResolver,
- Settings.Global.OVERLAY_DISPLAY_DEVICES,
- null
- )
-
- if (!addedDisplays.isEmpty()) {
- withTimeoutOrNull(TIMEOUT) {
- displayRemovedFlow.take(addedDisplays.size).collect { displayId ->
- addedDisplays.remove(displayId)
- }
- } ?: error("Timed out waiting for displays to be removed: $addedDisplays")
- }
- }
-
- private companion object {
- const val DEFAULT_WIDTH = 1280
- const val DEFAULT_HEIGHT = 720
- const val DEFAULT_DENSITY = 160
- const val TAG = "SimulatedConnectedDisplayTestRule"
- val TIMEOUT = 10.seconds
- }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/Android.bp b/libs/WindowManager/Shell/tests/flicker/Android.bp
index 7b6cfe3f9f8a..98b0bd0b589d 100644
--- a/libs/WindowManager/Shell/tests/flicker/Android.bp
+++ b/libs/WindowManager/Shell/tests/flicker/Android.bp
@@ -38,15 +38,15 @@ java_library {
],
static_libs: [
"androidx.test.ext.junit",
- "flickertestapplib",
+ "com_android_wm_shell_flags_lib",
"flickerlib",
"flickerlib-helpers",
+ "flickertestapplib",
+ "launcher-aosp-tapl",
+ "launcher-helper-lib",
"platform-test-annotations",
"wm-flicker-common-app-helpers",
"wm-flicker-common-assertions",
- "launcher-helper-lib",
- "launcher-aosp-tapl",
- "com_android_wm_shell_flags_lib",
],
}
@@ -60,18 +60,18 @@ java_defaults {
test_suites: ["device-tests"],
libs: ["android.test.runner.stubs.system"],
static_libs: [
- "wm-shell-flicker-utils",
"androidx.test.ext.junit",
- "flickertestapplib",
"flickerlib",
"flickerlib-helpers",
"flickerlib-trace_processor_shell",
+ "flickertestapplib",
+ "launcher-aosp-tapl",
+ "launcher-helper-lib",
"platform-test-annotations",
"platform-test-rules",
"wm-flicker-common-app-helpers",
"wm-flicker-common-assertions",
- "launcher-helper-lib",
- "launcher-aosp-tapl",
+ "wm-shell-flicker-utils",
],
data: [
":FlickerTestApp",
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandlerTest.kt
index 85a431be8e8b..42dcaf4b4f33 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandlerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandlerTest.kt
@@ -51,6 +51,7 @@ import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.kotlin.argumentCaptor
import org.mockito.kotlin.clearInvocations
+import org.mockito.kotlin.mock
import org.mockito.kotlin.whenever
import org.mockito.quality.Strictness
@@ -213,12 +214,15 @@ class DesktopDisplayEventHandlerTest : ShellTestCase() {
@EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
fun testUserChanged_createsDeskWhenNeeded() =
testScope.runTest {
+ val userId = 11
whenever(DesktopModeStatus.canEnterDesktopMode(context)).thenReturn(true)
val userChangeListenerCaptor = argumentCaptor<UserChangeListener>()
verify(mockShellController).addUserChangeListener(userChangeListenerCaptor.capture())
- whenever(mockDesktopRepository.getNumberOfDesks(displayId = 2)).thenReturn(0)
- whenever(mockDesktopRepository.getNumberOfDesks(displayId = 3)).thenReturn(0)
- whenever(mockDesktopRepository.getNumberOfDesks(displayId = 4)).thenReturn(1)
+ val mockRepository = mock<DesktopRepository>()
+ whenever(mockDesktopUserRepositories.getProfile(userId)).thenReturn(mockRepository)
+ whenever(mockRepository.getNumberOfDesks(displayId = 2)).thenReturn(0)
+ whenever(mockRepository.getNumberOfDesks(displayId = 3)).thenReturn(0)
+ whenever(mockRepository.getNumberOfDesks(displayId = 4)).thenReturn(1)
whenever(mockRootTaskDisplayAreaOrganizer.displayIds).thenReturn(intArrayOf(2, 3, 4))
desktopRepositoryInitializer.initialize(mockDesktopUserRepositories)
handler.onDisplayAdded(displayId = 2)
@@ -227,7 +231,7 @@ class DesktopDisplayEventHandlerTest : ShellTestCase() {
runCurrent()
clearInvocations(mockDesktopTasksController)
- userChangeListenerCaptor.lastValue.onUserChanged(1, context)
+ userChangeListenerCaptor.lastValue.onUserChanged(userId, context)
runCurrent()
verify(mockDesktopTasksController).createDesk(displayId = 2)
@@ -238,20 +242,22 @@ class DesktopDisplayEventHandlerTest : ShellTestCase() {
@Test
fun testConnectExternalDisplay() {
onDisplaysChangedListenerCaptor.lastValue.onDisplayAdded(externalDisplayId)
- verify(desktopDisplayModeController).refreshDisplayWindowingMode()
+ verify(desktopDisplayModeController).updateExternalDisplayWindowingMode(externalDisplayId)
+ verify(desktopDisplayModeController).updateDefaultDisplayWindowingMode()
}
@Test
fun testDisconnectExternalDisplay() {
onDisplaysChangedListenerCaptor.lastValue.onDisplayRemoved(externalDisplayId)
- verify(desktopDisplayModeController).refreshDisplayWindowingMode()
+ verify(desktopDisplayModeController).updateDefaultDisplayWindowingMode()
}
@Test
@EnableFlags(DisplayFlags.FLAG_ENABLE_DISPLAY_CONTENT_MODE_MANAGEMENT)
fun testDesktopModeEligibleChanged() {
onDisplaysChangedListenerCaptor.lastValue.onDesktopModeEligibleChanged(externalDisplayId)
- verify(desktopDisplayModeController).refreshDisplayWindowingMode()
+ verify(desktopDisplayModeController).updateExternalDisplayWindowingMode(externalDisplayId)
+ verify(desktopDisplayModeController).updateDefaultDisplayWindowingMode()
}
private class FakeDesktopRepositoryInitializer : DesktopRepositoryInitializer {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopDisplayModeControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopDisplayModeControllerTest.kt
index 96b826f93aae..7e9ee34c8f68 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopDisplayModeControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopDisplayModeControllerTest.kt
@@ -101,10 +101,13 @@ class DesktopDisplayModeControllerTest(
private val fullscreenTask =
TestRunningTaskInfoBuilder().setWindowingMode(WINDOWING_MODE_FULLSCREEN).build()
private val defaultTDA = DisplayAreaInfo(MockToken().token(), DEFAULT_DISPLAY, 0)
+ private val externalTDA = DisplayAreaInfo(MockToken().token(), EXTERNAL_DISPLAY_ID, 0)
private val wallpaperToken = MockToken().token()
private val defaultDisplay = mock<Display>()
private val externalDisplay = mock<Display>()
- private val mouseDevice = mock<InputDevice>()
+ private val touchpadDevice = mock<InputDevice>()
+ private val keyboardDevice = mock<InputDevice>()
+ private val connectedDeviceIds = mutableListOf<Int>()
private lateinit var extendedDisplaySettingsRestoreSession:
ExtendedDisplaySettingsRestoreSession
@@ -127,6 +130,8 @@ class DesktopDisplayModeControllerTest(
whenever(transitions.startTransition(anyInt(), any(), isNull())).thenReturn(Binder())
whenever(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY))
.thenReturn(defaultTDA)
+ whenever(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(EXTERNAL_DISPLAY_ID))
+ .thenReturn(externalTDA)
controller =
DesktopDisplayModeController(
context,
@@ -145,16 +150,18 @@ class DesktopDisplayModeControllerTest(
whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(wallpaperToken)
whenever(displayController.getDisplay(DEFAULT_DISPLAY)).thenReturn(defaultDisplay)
whenever(displayController.getDisplay(EXTERNAL_DISPLAY_ID)).thenReturn(externalDisplay)
- setTabletModeStatus(SwitchState.UNKNOWN)
- whenever(
- DesktopModeStatus.isDesktopModeSupportedOnDisplay(
- context,
- defaultDisplay
- )
- ).thenReturn(true)
- whenever(mouseDevice.supportsSource(InputDevice.SOURCE_MOUSE)).thenReturn(true)
- whenever(inputManager.getInputDevice(EXTERNAL_DEVICE_ID)).thenReturn(mouseDevice)
- setMouseConnected(false)
+ whenever(DesktopModeStatus.isDesktopModeSupportedOnDisplay(context, defaultDisplay))
+ .thenReturn(true)
+ whenever(touchpadDevice.supportsSource(InputDevice.SOURCE_TOUCHPAD)).thenReturn(true)
+ whenever(touchpadDevice.isEnabled()).thenReturn(true)
+ whenever(inputManager.getInputDevice(TOUCHPAD_DEVICE_ID)).thenReturn(touchpadDevice)
+ whenever(keyboardDevice.isFullKeyboard()).thenReturn(true)
+ whenever(keyboardDevice.isVirtual()).thenReturn(false)
+ whenever(keyboardDevice.isEnabled()).thenReturn(true)
+ whenever(inputManager.getInputDevice(KEYBOARD_DEVICE_ID)).thenReturn(keyboardDevice)
+ whenever(inputManager.inputDeviceIds).thenAnswer { connectedDeviceIds.toIntArray() }
+ setTouchpadConnected(false)
+ setKeyboardConnected(false)
}
@After
@@ -211,8 +218,8 @@ class DesktopDisplayModeControllerTest(
@DisableFlags(Flags.FLAG_FORM_FACTOR_BASED_DESKTOP_FIRST_SWITCH)
fun testTargetWindowingMode_formfactorDisabled(
@TestParameter param: ExternalDisplayBasedTargetModeTestCase,
- @TestParameter tabletModeStatus: SwitchState,
- @TestParameter hasAnyMouseDevice: Boolean,
+ @TestParameter hasAnyTouchpadDevice: Boolean,
+ @TestParameter hasAnyKeyboardDevice: Boolean,
) {
whenever(mockWindowManager.getWindowingMode(anyInt()))
.thenReturn(param.defaultWindowingMode)
@@ -221,15 +228,11 @@ class DesktopDisplayModeControllerTest(
} else {
disconnectExternalDisplay()
}
- setTabletModeStatus(tabletModeStatus)
- setMouseConnected(hasAnyMouseDevice)
+ setTouchpadConnected(hasAnyTouchpadDevice)
+ setKeyboardConnected(hasAnyKeyboardDevice)
setExtendedMode(param.extendedDisplayEnabled)
- whenever(
- DesktopModeStatus.isDesktopModeSupportedOnDisplay(
- context,
- defaultDisplay
- )
- ).thenReturn(param.isDefaultDisplayDesktopEligible)
+ whenever(DesktopModeStatus.isDesktopModeSupportedOnDisplay(context, defaultDisplay))
+ .thenReturn(param.isDefaultDisplayDesktopEligible)
assertThat(controller.getTargetWindowingModeForDefaultDisplay())
.isEqualTo(param.expectedWindowingMode)
@@ -246,15 +249,11 @@ class DesktopDisplayModeControllerTest(
} else {
disconnectExternalDisplay()
}
- setTabletModeStatus(param.tabletModeStatus)
setExtendedMode(param.extendedDisplayEnabled)
- whenever(
- DesktopModeStatus.isDesktopModeSupportedOnDisplay(
- context,
- defaultDisplay
- )
- ).thenReturn(param.isDefaultDisplayDesktopEligible)
- setMouseConnected(param.hasAnyMouseDevice)
+ whenever(DesktopModeStatus.isDesktopModeSupportedOnDisplay(context, defaultDisplay))
+ .thenReturn(param.isDefaultDisplayDesktopEligible)
+ setTouchpadConnected(param.hasAnyTouchpadDevice)
+ setKeyboardConnected(param.hasAnyKeyboardDevice)
assertThat(controller.getTargetWindowingModeForDefaultDisplay())
.isEqualTo(param.expectedWindowingMode)
@@ -296,30 +295,36 @@ class DesktopDisplayModeControllerTest(
.isEqualTo(WINDOWING_MODE_UNDEFINED)
}
+ @Test
+ @EnableFlags(DisplayFlags.FLAG_ENABLE_DISPLAY_CONTENT_MODE_MANAGEMENT)
+ fun externalDisplayWindowingMode() {
+ externalTDA.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FULLSCREEN
+ setExtendedMode(true)
+
+ controller.updateExternalDisplayWindowingMode(EXTERNAL_DISPLAY_ID)
+
+ val arg = argumentCaptor<WindowContainerTransaction>()
+ verify(transitions, times(1)).startTransition(eq(TRANSIT_CHANGE), arg.capture(), isNull())
+ assertThat(arg.firstValue.changes[externalTDA.token.asBinder()]?.windowingMode)
+ .isEqualTo(WINDOWING_MODE_FREEFORM)
+ }
+
private fun connectExternalDisplay() {
whenever(rootTaskDisplayAreaOrganizer.getDisplayIds())
.thenReturn(intArrayOf(DEFAULT_DISPLAY, EXTERNAL_DISPLAY_ID))
- controller.refreshDisplayWindowingMode()
+ controller.updateDefaultDisplayWindowingMode()
}
private fun disconnectExternalDisplay() {
whenever(rootTaskDisplayAreaOrganizer.getDisplayIds())
.thenReturn(intArrayOf(DEFAULT_DISPLAY))
- controller.refreshDisplayWindowingMode()
- }
-
- private fun setTabletModeStatus(status: SwitchState) {
- whenever(inputManager.isInTabletMode()).thenReturn(status.value)
+ controller.updateDefaultDisplayWindowingMode()
}
private fun setExtendedMode(enabled: Boolean) {
if (DisplayFlags.enableDisplayContentModeManagement()) {
- whenever(
- DesktopModeStatus.isDesktopModeSupportedOnDisplay(
- context,
- externalDisplay
- )
- ).thenReturn(enabled)
+ whenever(DesktopModeStatus.isDesktopModeSupportedOnDisplay(context, externalDisplay))
+ .thenReturn(enabled)
} else {
Settings.Global.putInt(
context.contentResolver,
@@ -329,9 +334,20 @@ class DesktopDisplayModeControllerTest(
}
}
- private fun setMouseConnected(connected: Boolean) {
- whenever(inputManager.inputDeviceIds)
- .thenReturn(if (connected) intArrayOf(EXTERNAL_DEVICE_ID) else intArrayOf())
+ private fun setTouchpadConnected(connected: Boolean) {
+ if (connected) {
+ connectedDeviceIds.add(TOUCHPAD_DEVICE_ID)
+ } else {
+ connectedDeviceIds.remove(TOUCHPAD_DEVICE_ID)
+ }
+ }
+
+ private fun setKeyboardConnected(connected: Boolean) {
+ if (connected) {
+ connectedDeviceIds.add(KEYBOARD_DEVICE_ID)
+ } else {
+ connectedDeviceIds.remove(KEYBOARD_DEVICE_ID)
+ }
}
private class ExtendedDisplaySettingsRestoreSession(
@@ -358,13 +374,8 @@ class DesktopDisplayModeControllerTest(
companion object {
const val EXTERNAL_DISPLAY_ID = 100
- const val EXTERNAL_DEVICE_ID = 10
-
- enum class SwitchState(val value: Int) {
- UNKNOWN(InputManager.SWITCH_STATE_UNKNOWN),
- ON(InputManager.SWITCH_STATE_ON),
- OFF(InputManager.SWITCH_STATE_OFF),
- }
+ const val TOUCHPAD_DEVICE_ID = 10
+ const val KEYBOARD_DEVICE_ID = 11
enum class ExternalDisplayBasedTargetModeTestCase(
val defaultWindowingMode: Int,
@@ -490,393 +501,265 @@ class DesktopDisplayModeControllerTest(
enum class FormFactorBasedTargetModeTestCase(
val hasExternalDisplay: Boolean,
val extendedDisplayEnabled: Boolean,
- val tabletModeStatus: SwitchState,
val isDefaultDisplayDesktopEligible: Boolean,
- val hasAnyMouseDevice: Boolean,
+ val hasAnyTouchpadDevice: Boolean,
+ val hasAnyKeyboardDevice: Boolean,
val expectedWindowingMode: Int,
) {
- EXTERNAL_EXTENDED_TABLET_NO_PROJECTED_NO_MOUSE(
- hasExternalDisplay = true,
- extendedDisplayEnabled = true,
- tabletModeStatus = SwitchState.ON,
- isDefaultDisplayDesktopEligible = true,
- hasAnyMouseDevice = false,
- expectedWindowingMode = WINDOWING_MODE_FREEFORM,
- ),
- NO_EXTERNAL_EXTENDED_TABLET_NO_PROJECTED_NO_MOUSE(
- hasExternalDisplay = false,
- extendedDisplayEnabled = true,
- tabletModeStatus = SwitchState.ON,
- isDefaultDisplayDesktopEligible = true,
- hasAnyMouseDevice = false,
- expectedWindowingMode = WINDOWING_MODE_FULLSCREEN,
- ),
- EXTERNAL_MIRROR_TABLET_NO_PROJECTED_NO_MOUSE(
- hasExternalDisplay = true,
- extendedDisplayEnabled = false,
- tabletModeStatus = SwitchState.ON,
- isDefaultDisplayDesktopEligible = true,
- hasAnyMouseDevice = false,
- expectedWindowingMode = WINDOWING_MODE_FULLSCREEN,
- ),
- NO_EXTERNAL_MIRROR_TABLET_NO_PROJECTED_NO_MOUSE(
- hasExternalDisplay = false,
- extendedDisplayEnabled = false,
- tabletModeStatus = SwitchState.ON,
- isDefaultDisplayDesktopEligible = true,
- hasAnyMouseDevice = false,
- expectedWindowingMode = WINDOWING_MODE_FULLSCREEN,
- ),
- EXTERNAL_EXTENDED_CLAMSHELL_NO_PROJECTED_NO_MOUSE(
+ EXTERNAL_EXTENDED_NO_PROJECTED_TOUCHPAD_KEYBOARD(
hasExternalDisplay = true,
extendedDisplayEnabled = true,
- tabletModeStatus = SwitchState.OFF,
isDefaultDisplayDesktopEligible = true,
- hasAnyMouseDevice = false,
+ hasAnyTouchpadDevice = true,
+ hasAnyKeyboardDevice = true,
expectedWindowingMode = WINDOWING_MODE_FREEFORM,
),
- NO_EXTERNAL_EXTENDED_CLAMSHELL_NO_PROJECTED_NO_MOUSE(
+ NO_EXTERNAL_EXTENDED_NO_PROJECTED_TOUCHPAD_KEYBOARD(
hasExternalDisplay = false,
extendedDisplayEnabled = true,
- tabletModeStatus = SwitchState.OFF,
isDefaultDisplayDesktopEligible = true,
- hasAnyMouseDevice = false,
+ hasAnyTouchpadDevice = true,
+ hasAnyKeyboardDevice = true,
expectedWindowingMode = WINDOWING_MODE_FREEFORM,
),
- EXTERNAL_MIRROR_CLAMSHELL_NO_PROJECTED_NO_MOUSE(
+ EXTERNAL_MIRROR_NO_PROJECTED_TOUCHPAD_KEYBOARD(
hasExternalDisplay = true,
extendedDisplayEnabled = false,
- tabletModeStatus = SwitchState.OFF,
isDefaultDisplayDesktopEligible = true,
- hasAnyMouseDevice = false,
+ hasAnyTouchpadDevice = true,
+ hasAnyKeyboardDevice = true,
expectedWindowingMode = WINDOWING_MODE_FREEFORM,
),
- NO_EXTERNAL_MIRROR_CLAMSHELL_NO_PROJECTED_NO_MOUSE(
+ NO_EXTERNAL_MIRROR_NO_PROJECTED_TOUCHPAD_KEYBOARD(
hasExternalDisplay = false,
extendedDisplayEnabled = false,
- tabletModeStatus = SwitchState.OFF,
isDefaultDisplayDesktopEligible = true,
- hasAnyMouseDevice = false,
+ hasAnyTouchpadDevice = true,
+ hasAnyKeyboardDevice = true,
expectedWindowingMode = WINDOWING_MODE_FREEFORM,
),
- EXTERNAL_EXTENDED_UNKNOWN_NO_PROJECTED_NO_MOUSE(
+ EXTERNAL_EXTENDED_PROJECTED_TOUCHPAD_KEYBOARD(
hasExternalDisplay = true,
extendedDisplayEnabled = true,
- tabletModeStatus = SwitchState.UNKNOWN,
- isDefaultDisplayDesktopEligible = true,
- hasAnyMouseDevice = false,
- expectedWindowingMode = WINDOWING_MODE_FREEFORM,
- ),
- NO_EXTERNAL_EXTENDED_UNKNOWN_NO_PROJECTED_NO_MOUSE(
- hasExternalDisplay = false,
- extendedDisplayEnabled = true,
- tabletModeStatus = SwitchState.UNKNOWN,
- isDefaultDisplayDesktopEligible = true,
- hasAnyMouseDevice = false,
- expectedWindowingMode = WINDOWING_MODE_FULLSCREEN,
- ),
- EXTERNAL_MIRROR_UNKNOWN_NO_PROJECTED_NO_MOUSE(
- hasExternalDisplay = true,
- extendedDisplayEnabled = false,
- tabletModeStatus = SwitchState.UNKNOWN,
- isDefaultDisplayDesktopEligible = true,
- hasAnyMouseDevice = false,
- expectedWindowingMode = WINDOWING_MODE_FULLSCREEN,
- ),
- NO_EXTERNAL_MIRROR_UNKNOWN_NO_PROJECTED_NO_MOUSE(
- hasExternalDisplay = false,
- extendedDisplayEnabled = false,
- tabletModeStatus = SwitchState.UNKNOWN,
- isDefaultDisplayDesktopEligible = true,
- hasAnyMouseDevice = false,
- expectedWindowingMode = WINDOWING_MODE_FULLSCREEN,
- ),
- EXTERNAL_EXTENDED_TABLET_PROJECTED_NO_MOUSE(
- hasExternalDisplay = true,
- extendedDisplayEnabled = true,
- tabletModeStatus = SwitchState.ON,
isDefaultDisplayDesktopEligible = false,
- hasAnyMouseDevice = false,
+ hasAnyTouchpadDevice = true,
+ hasAnyKeyboardDevice = true,
expectedWindowingMode = WINDOWING_MODE_FULLSCREEN,
),
- NO_EXTERNAL_EXTENDED_TABLET_PROJECTED_NO_MOUSE(
+ NO_EXTERNAL_EXTENDED_PROJECTED_TOUCHPAD_KEYBOARD(
hasExternalDisplay = false,
extendedDisplayEnabled = true,
- tabletModeStatus = SwitchState.ON,
isDefaultDisplayDesktopEligible = false,
- hasAnyMouseDevice = false,
+ hasAnyTouchpadDevice = true,
+ hasAnyKeyboardDevice = true,
expectedWindowingMode = WINDOWING_MODE_FULLSCREEN,
),
- EXTERNAL_MIRROR_TABLET_PROJECTED_NO_MOUSE(
+ EXTERNAL_MIRROR_PROJECTED_TOUCHPAD_KEYBOARD(
hasExternalDisplay = true,
extendedDisplayEnabled = false,
- tabletModeStatus = SwitchState.ON,
isDefaultDisplayDesktopEligible = false,
- hasAnyMouseDevice = false,
+ hasAnyTouchpadDevice = true,
+ hasAnyKeyboardDevice = true,
expectedWindowingMode = WINDOWING_MODE_FULLSCREEN,
),
- NO_EXTERNAL_MIRROR_TABLET_PROJECTED_NO_MOUSE(
+ NO_EXTERNAL_MIRROR_PROJECTED_TOUCHPAD_KEYBOARD(
hasExternalDisplay = false,
extendedDisplayEnabled = false,
- tabletModeStatus = SwitchState.ON,
isDefaultDisplayDesktopEligible = false,
- hasAnyMouseDevice = false,
+ hasAnyTouchpadDevice = true,
+ hasAnyKeyboardDevice = true,
expectedWindowingMode = WINDOWING_MODE_FULLSCREEN,
),
- EXTERNAL_EXTENDED_CLAMSHELL_PROJECTED_NO_MOUSE(
+ EXTERNAL_EXTENDED_NO_PROJECTED_NO_TOUCHPAD_KEYBOARD(
hasExternalDisplay = true,
extendedDisplayEnabled = true,
- tabletModeStatus = SwitchState.OFF,
- isDefaultDisplayDesktopEligible = false,
- hasAnyMouseDevice = false,
- expectedWindowingMode = WINDOWING_MODE_FULLSCREEN,
+ isDefaultDisplayDesktopEligible = true,
+ hasAnyTouchpadDevice = false,
+ hasAnyKeyboardDevice = true,
+ expectedWindowingMode = WINDOWING_MODE_FREEFORM,
),
- NO_EXTERNAL_EXTENDED_CLAMSHELL_PROJECTED_NO_MOUSE(
+ NO_EXTERNAL_EXTENDED_NO_PROJECTED_NO_TOUCHPAD_KEYBOARD(
hasExternalDisplay = false,
extendedDisplayEnabled = true,
- tabletModeStatus = SwitchState.OFF,
- isDefaultDisplayDesktopEligible = false,
- hasAnyMouseDevice = false,
+ isDefaultDisplayDesktopEligible = true,
+ hasAnyTouchpadDevice = false,
+ hasAnyKeyboardDevice = true,
expectedWindowingMode = WINDOWING_MODE_FULLSCREEN,
),
- EXTERNAL_MIRROR_CLAMSHELL_PROJECTED_NO_MOUSE(
+ EXTERNAL_MIRROR_NO_PROJECTED_NO_TOUCHPAD_KEYBOARD(
hasExternalDisplay = true,
extendedDisplayEnabled = false,
- tabletModeStatus = SwitchState.OFF,
- isDefaultDisplayDesktopEligible = false,
- hasAnyMouseDevice = false,
+ isDefaultDisplayDesktopEligible = true,
+ hasAnyTouchpadDevice = false,
+ hasAnyKeyboardDevice = true,
expectedWindowingMode = WINDOWING_MODE_FULLSCREEN,
),
- NO_EXTERNAL_MIRROR_CLAMSHELL_PROJECTED_NO_MOUSE(
+ NO_EXTERNAL_MIRROR_NO_PROJECTED_NO_TOUCHPAD_KEYBOARD(
hasExternalDisplay = false,
extendedDisplayEnabled = false,
- tabletModeStatus = SwitchState.OFF,
- isDefaultDisplayDesktopEligible = false,
- hasAnyMouseDevice = false,
+ isDefaultDisplayDesktopEligible = true,
+ hasAnyTouchpadDevice = false,
+ hasAnyKeyboardDevice = true,
expectedWindowingMode = WINDOWING_MODE_FULLSCREEN,
),
- EXTERNAL_EXTENDED_UNKNOWN_PROJECTED_NO_MOUSE(
+ EXTERNAL_EXTENDED_PROJECTED_NO_TOUCHPAD_KEYBOARD(
hasExternalDisplay = true,
extendedDisplayEnabled = true,
- tabletModeStatus = SwitchState.UNKNOWN,
isDefaultDisplayDesktopEligible = false,
- hasAnyMouseDevice = false,
+ hasAnyTouchpadDevice = false,
+ hasAnyKeyboardDevice = true,
expectedWindowingMode = WINDOWING_MODE_FULLSCREEN,
),
- NO_EXTERNAL_EXTENDED_UNKNOWN_PROJECTED_NO_MOUSE(
+ NO_EXTERNAL_EXTENDED_PROJECTED_NO_TOUCHPAD_KEYBOARD(
hasExternalDisplay = false,
extendedDisplayEnabled = true,
- tabletModeStatus = SwitchState.UNKNOWN,
isDefaultDisplayDesktopEligible = false,
- hasAnyMouseDevice = false,
+ hasAnyTouchpadDevice = false,
+ hasAnyKeyboardDevice = true,
expectedWindowingMode = WINDOWING_MODE_FULLSCREEN,
),
- EXTERNAL_MIRROR_UNKNOWN_PROJECTED_NO_MOUSE(
+ EXTERNAL_MIRROR_PROJECTED_NO_TOUCHPAD_KEYBOARD(
hasExternalDisplay = true,
extendedDisplayEnabled = false,
- tabletModeStatus = SwitchState.UNKNOWN,
isDefaultDisplayDesktopEligible = false,
- hasAnyMouseDevice = false,
+ hasAnyTouchpadDevice = false,
+ hasAnyKeyboardDevice = true,
expectedWindowingMode = WINDOWING_MODE_FULLSCREEN,
),
- NO_EXTERNAL_MIRROR_UNKNOWN_PROJECTED_NO_MOUSE(
+ NO_EXTERNAL_MIRROR_PROJECTED_NO_TOUCHPAD_KEYBOARD(
hasExternalDisplay = false,
extendedDisplayEnabled = false,
- tabletModeStatus = SwitchState.UNKNOWN,
isDefaultDisplayDesktopEligible = false,
- hasAnyMouseDevice = false,
+ hasAnyTouchpadDevice = false,
+ hasAnyKeyboardDevice = true,
expectedWindowingMode = WINDOWING_MODE_FULLSCREEN,
),
- EXTERNAL_EXTENDED_TABLET_NO_PROJECTED_MOUSE(
+ EXTERNAL_EXTENDED_NO_PROJECTED_TOUCHPAD_NO_KEYBOARD(
hasExternalDisplay = true,
extendedDisplayEnabled = true,
- tabletModeStatus = SwitchState.ON,
isDefaultDisplayDesktopEligible = true,
- hasAnyMouseDevice = true,
+ hasAnyTouchpadDevice = true,
+ hasAnyKeyboardDevice = false,
expectedWindowingMode = WINDOWING_MODE_FREEFORM,
),
- NO_EXTERNAL_EXTENDED_TABLET_NO_PROJECTED_MOUSE(
+ NO_EXTERNAL_EXTENDED_NO_PROJECTED_TOUCHPAD_NO_KEYBOARD(
hasExternalDisplay = false,
extendedDisplayEnabled = true,
- tabletModeStatus = SwitchState.ON,
isDefaultDisplayDesktopEligible = true,
- hasAnyMouseDevice = true,
- expectedWindowingMode = WINDOWING_MODE_FREEFORM,
- ),
- EXTERNAL_MIRROR_TABLET_NO_PROJECTED_MOUSE(
- hasExternalDisplay = true,
- extendedDisplayEnabled = false,
- tabletModeStatus = SwitchState.ON,
- isDefaultDisplayDesktopEligible = true,
- hasAnyMouseDevice = true,
- expectedWindowingMode = WINDOWING_MODE_FREEFORM,
- ),
- NO_EXTERNAL_MIRROR_TABLET_NO_PROJECTED_MOUSE(
- hasExternalDisplay = false,
- extendedDisplayEnabled = false,
- tabletModeStatus = SwitchState.ON,
- isDefaultDisplayDesktopEligible = true,
- hasAnyMouseDevice = true,
- expectedWindowingMode = WINDOWING_MODE_FREEFORM,
- ),
- EXTERNAL_EXTENDED_CLAMSHELL_NO_PROJECTED_MOUSE(
- hasExternalDisplay = true,
- extendedDisplayEnabled = true,
- tabletModeStatus = SwitchState.OFF,
- isDefaultDisplayDesktopEligible = true,
- hasAnyMouseDevice = true,
- expectedWindowingMode = WINDOWING_MODE_FREEFORM,
- ),
- NO_EXTERNAL_EXTENDED_CLAMSHELL_NO_PROJECTED_MOUSE(
- hasExternalDisplay = false,
- extendedDisplayEnabled = true,
- tabletModeStatus = SwitchState.OFF,
- isDefaultDisplayDesktopEligible = true,
- hasAnyMouseDevice = true,
- expectedWindowingMode = WINDOWING_MODE_FREEFORM,
- ),
- EXTERNAL_MIRROR_CLAMSHELL_NO_PROJECTED_MOUSE(
- hasExternalDisplay = true,
- extendedDisplayEnabled = false,
- tabletModeStatus = SwitchState.OFF,
- isDefaultDisplayDesktopEligible = true,
- hasAnyMouseDevice = true,
- expectedWindowingMode = WINDOWING_MODE_FREEFORM,
- ),
- NO_EXTERNAL_MIRROR_CLAMSHELL_NO_PROJECTED_MOUSE(
- hasExternalDisplay = false,
- extendedDisplayEnabled = false,
- tabletModeStatus = SwitchState.OFF,
- isDefaultDisplayDesktopEligible = true,
- hasAnyMouseDevice = true,
- expectedWindowingMode = WINDOWING_MODE_FREEFORM,
- ),
- EXTERNAL_EXTENDED_UNKNOWN_NO_PROJECTED_MOUSE(
- hasExternalDisplay = true,
- extendedDisplayEnabled = true,
- tabletModeStatus = SwitchState.UNKNOWN,
- isDefaultDisplayDesktopEligible = true,
- hasAnyMouseDevice = true,
- expectedWindowingMode = WINDOWING_MODE_FREEFORM,
- ),
- NO_EXTERNAL_EXTENDED_UNKNOWN_NO_PROJECTED_MOUSE(
- hasExternalDisplay = false,
- extendedDisplayEnabled = true,
- tabletModeStatus = SwitchState.UNKNOWN,
- isDefaultDisplayDesktopEligible = true,
- hasAnyMouseDevice = true,
- expectedWindowingMode = WINDOWING_MODE_FREEFORM,
+ hasAnyTouchpadDevice = true,
+ hasAnyKeyboardDevice = false,
+ expectedWindowingMode = WINDOWING_MODE_FULLSCREEN,
),
- EXTERNAL_MIRROR_UNKNOWN_NO_PROJECTED_MOUSE(
+ EXTERNAL_MIRROR_NO_PROJECTED_TOUCHPAD_NO_KEYBOARD(
hasExternalDisplay = true,
extendedDisplayEnabled = false,
- tabletModeStatus = SwitchState.UNKNOWN,
isDefaultDisplayDesktopEligible = true,
- hasAnyMouseDevice = true,
- expectedWindowingMode = WINDOWING_MODE_FREEFORM,
+ hasAnyTouchpadDevice = true,
+ hasAnyKeyboardDevice = false,
+ expectedWindowingMode = WINDOWING_MODE_FULLSCREEN,
),
- NO_EXTERNAL_MIRROR_UNKNOWN_NO_PROJECTED_MOUSE(
+ NO_EXTERNAL_MIRROR_NO_PROJECTED_TOUCHPAD_NO_KEYBOARD(
hasExternalDisplay = false,
extendedDisplayEnabled = false,
- tabletModeStatus = SwitchState.UNKNOWN,
isDefaultDisplayDesktopEligible = true,
- hasAnyMouseDevice = true,
- expectedWindowingMode = WINDOWING_MODE_FREEFORM,
+ hasAnyTouchpadDevice = true,
+ hasAnyKeyboardDevice = false,
+ expectedWindowingMode = WINDOWING_MODE_FULLSCREEN,
),
- EXTERNAL_EXTENDED_TABLET_PROJECTED_MOUSE(
+ EXTERNAL_EXTENDED_PROJECTED_TOUCHPAD_NO_KEYBOARD(
hasExternalDisplay = true,
extendedDisplayEnabled = true,
- tabletModeStatus = SwitchState.ON,
isDefaultDisplayDesktopEligible = false,
- hasAnyMouseDevice = true,
+ hasAnyTouchpadDevice = true,
+ hasAnyKeyboardDevice = false,
expectedWindowingMode = WINDOWING_MODE_FULLSCREEN,
),
- NO_EXTERNAL_EXTENDED_TABLET_PROJECTED_MOUSE(
+ NO_EXTERNAL_EXTENDED_PROJECTED_TOUCHPAD_NO_KEYBOARD(
hasExternalDisplay = false,
extendedDisplayEnabled = true,
- tabletModeStatus = SwitchState.ON,
isDefaultDisplayDesktopEligible = false,
- hasAnyMouseDevice = true,
+ hasAnyTouchpadDevice = true,
+ hasAnyKeyboardDevice = false,
expectedWindowingMode = WINDOWING_MODE_FULLSCREEN,
),
- EXTERNAL_MIRROR_TABLET_PROJECTED_MOUSE(
+ EXTERNAL_MIRROR_PROJECTED_TOUCHPAD_NO_KEYBOARD(
hasExternalDisplay = true,
extendedDisplayEnabled = false,
- tabletModeStatus = SwitchState.ON,
isDefaultDisplayDesktopEligible = false,
- hasAnyMouseDevice = true,
+ hasAnyTouchpadDevice = true,
+ hasAnyKeyboardDevice = false,
expectedWindowingMode = WINDOWING_MODE_FULLSCREEN,
),
- NO_EXTERNAL_MIRROR_TABLET_PROJECTED_MOUSE(
+ NO_EXTERNAL_MIRROR_PROJECTED_TOUCHPAD_NO_KEYBOARD(
hasExternalDisplay = false,
extendedDisplayEnabled = false,
- tabletModeStatus = SwitchState.ON,
isDefaultDisplayDesktopEligible = false,
- hasAnyMouseDevice = true,
+ hasAnyTouchpadDevice = true,
+ hasAnyKeyboardDevice = false,
expectedWindowingMode = WINDOWING_MODE_FULLSCREEN,
),
- EXTERNAL_EXTENDED_CLAMSHELL_PROJECTED_MOUSE(
+ EXTERNAL_EXTENDED_NO_PROJECTED_NO_TOUCHPAD_NO_KEYBOARD(
hasExternalDisplay = true,
extendedDisplayEnabled = true,
- tabletModeStatus = SwitchState.OFF,
- isDefaultDisplayDesktopEligible = false,
- hasAnyMouseDevice = true,
- expectedWindowingMode = WINDOWING_MODE_FULLSCREEN,
+ isDefaultDisplayDesktopEligible = true,
+ hasAnyTouchpadDevice = false,
+ hasAnyKeyboardDevice = false,
+ expectedWindowingMode = WINDOWING_MODE_FREEFORM,
),
- NO_EXTERNAL_EXTENDED_CLAMSHELL_PROJECTED_MOUSE(
+ NO_EXTERNAL_EXTENDED_NO_PROJECTED_NO_TOUCHPAD_NO_KEYBOARD(
hasExternalDisplay = false,
extendedDisplayEnabled = true,
- tabletModeStatus = SwitchState.OFF,
- isDefaultDisplayDesktopEligible = false,
- hasAnyMouseDevice = true,
+ isDefaultDisplayDesktopEligible = true,
+ hasAnyTouchpadDevice = false,
+ hasAnyKeyboardDevice = false,
expectedWindowingMode = WINDOWING_MODE_FULLSCREEN,
),
- EXTERNAL_MIRROR_CLAMSHELL_PROJECTED_MOUSE(
+ EXTERNAL_MIRROR_NO_PROJECTED_NO_TOUCHPAD_NO_KEYBOARD(
hasExternalDisplay = true,
extendedDisplayEnabled = false,
- tabletModeStatus = SwitchState.OFF,
- isDefaultDisplayDesktopEligible = false,
- hasAnyMouseDevice = true,
+ isDefaultDisplayDesktopEligible = true,
+ hasAnyTouchpadDevice = false,
+ hasAnyKeyboardDevice = false,
expectedWindowingMode = WINDOWING_MODE_FULLSCREEN,
),
- NO_EXTERNAL_MIRROR_CLAMSHELL_PROJECTED_MOUSE(
+ NO_EXTERNAL_MIRROR_NO_PROJECTED_NO_TOUCHPAD_NO_KEYBOARD(
hasExternalDisplay = false,
extendedDisplayEnabled = false,
- tabletModeStatus = SwitchState.OFF,
- isDefaultDisplayDesktopEligible = false,
- hasAnyMouseDevice = true,
+ isDefaultDisplayDesktopEligible = true,
+ hasAnyTouchpadDevice = false,
+ hasAnyKeyboardDevice = false,
expectedWindowingMode = WINDOWING_MODE_FULLSCREEN,
),
- EXTERNAL_EXTENDED_UNKNOWN_PROJECTED_MOUSE(
+ EXTERNAL_EXTENDED_PROJECTED_NO_TOUCHPAD_NO_KEYBOARD(
hasExternalDisplay = true,
extendedDisplayEnabled = true,
- tabletModeStatus = SwitchState.UNKNOWN,
isDefaultDisplayDesktopEligible = false,
- hasAnyMouseDevice = true,
+ hasAnyTouchpadDevice = false,
+ hasAnyKeyboardDevice = false,
expectedWindowingMode = WINDOWING_MODE_FULLSCREEN,
),
- NO_EXTERNAL_EXTENDED_UNKNOWN_PROJECTED_MOUSE(
+ NO_EXTERNAL_EXTENDED_PROJECTED_NO_TOUCHPAD_NO_KEYBOARD(
hasExternalDisplay = false,
extendedDisplayEnabled = true,
- tabletModeStatus = SwitchState.UNKNOWN,
isDefaultDisplayDesktopEligible = false,
- hasAnyMouseDevice = true,
+ hasAnyTouchpadDevice = false,
+ hasAnyKeyboardDevice = false,
expectedWindowingMode = WINDOWING_MODE_FULLSCREEN,
),
- EXTERNAL_MIRROR_UNKNOWN_PROJECTED_MOUSE(
+ EXTERNAL_MIRROR_PROJECTED_NO_TOUCHPAD_NO_KEYBOARD(
hasExternalDisplay = true,
extendedDisplayEnabled = false,
- tabletModeStatus = SwitchState.UNKNOWN,
isDefaultDisplayDesktopEligible = false,
- hasAnyMouseDevice = true,
+ hasAnyTouchpadDevice = false,
+ hasAnyKeyboardDevice = false,
expectedWindowingMode = WINDOWING_MODE_FULLSCREEN,
),
- NO_EXTERNAL_MIRROR_UNKNOWN_PROJECTED_MOUSE(
+ NO_EXTERNAL_MIRROR_PROJECTED_NO_TOUCHPAD_NO_KEYBOARD(
hasExternalDisplay = false,
extendedDisplayEnabled = false,
- tabletModeStatus = SwitchState.UNKNOWN,
isDefaultDisplayDesktopEligible = false,
- hasAnyMouseDevice = true,
+ hasAnyTouchpadDevice = false,
+ hasAnyKeyboardDevice = false,
expectedWindowingMode = WINDOWING_MODE_FULLSCREEN,
),
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicatorTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicatorTest.kt
index 652fae01c1b2..a4052890f08a 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicatorTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicatorTest.kt
@@ -283,14 +283,32 @@ class DesktopModeVisualIndicatorTest : ShellTestCase() {
com.android.wm.shell.Flags.FLAG_ENABLE_BUBBLE_TO_FULLSCREEN,
com.android.wm.shell.Flags.FLAG_ENABLE_CREATE_ANY_BUBBLE,
)
- fun testDefaultIndicators_bubblesEnabled() {
+ fun testDefaultIndicators_enableBubbleToFullscreen() {
createVisualIndicator(DesktopModeVisualIndicator.DragStartState.FROM_FULLSCREEN)
var result = visualIndicator.updateIndicatorType(PointF(10f, 1500f))
assertThat(result)
.isEqualTo(DesktopModeVisualIndicator.IndicatorType.TO_BUBBLE_LEFT_INDICATOR)
- result = visualIndicator.updateIndicatorType(PointF(2300f, 1500f))
+ result = visualIndicator.updateIndicatorType(PointF(2390f, 1500f))
assertThat(result)
.isEqualTo(DesktopModeVisualIndicator.IndicatorType.TO_BUBBLE_RIGHT_INDICATOR)
+
+ // Check that bubble zones are not available from split
+ createVisualIndicator(DesktopModeVisualIndicator.DragStartState.FROM_SPLIT)
+ result = visualIndicator.updateIndicatorType(PointF(10f, 1500f))
+ assertThat(result)
+ .isEqualTo(DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_LEFT_INDICATOR)
+ result = visualIndicator.updateIndicatorType(PointF(2390f, 1500f))
+ assertThat(result)
+ .isEqualTo(DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_RIGHT_INDICATOR)
+
+ // Check that bubble zones are not available from desktop
+ createVisualIndicator(DesktopModeVisualIndicator.DragStartState.FROM_FREEFORM)
+ result = visualIndicator.updateIndicatorType(PointF(10f, 1500f))
+ assertThat(result)
+ .isEqualTo(DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_LEFT_INDICATOR)
+ result = visualIndicator.updateIndicatorType(PointF(2390f, 1500f))
+ assertThat(result)
+ .isEqualTo(DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_RIGHT_INDICATOR)
}
@Test
@@ -298,7 +316,7 @@ class DesktopModeVisualIndicatorTest : ShellTestCase() {
com.android.wm.shell.Flags.FLAG_ENABLE_BUBBLE_TO_FULLSCREEN,
com.android.wm.shell.Flags.FLAG_ENABLE_CREATE_ANY_BUBBLE,
)
- fun testDefaultIndicators_foldable_leftRightSplit() {
+ fun testDefaultIndicators_foldable_enableBubbleToFullscreen_dragFromFullscreen() {
setUpFoldable()
createVisualIndicator(
@@ -325,13 +343,47 @@ class DesktopModeVisualIndicatorTest : ShellTestCase() {
result = visualIndicator.updateIndicatorType(foldRightBottom())
assertThat(result)
.isEqualTo(DesktopModeVisualIndicator.IndicatorType.TO_BUBBLE_RIGHT_INDICATOR)
+ }
+
+ @Test
+ @EnableFlags(
+ com.android.wm.shell.Flags.FLAG_ENABLE_BUBBLE_TO_FULLSCREEN,
+ com.android.wm.shell.Flags.FLAG_ENABLE_CREATE_ANY_BUBBLE,
+ )
+ @DisableFlags(com.android.wm.shell.Flags.FLAG_ENABLE_BUBBLE_ANYTHING)
+ fun testDefaultIndicators_foldable_enableBubbleToFullscreen_dragFromSplit() {
+ setUpFoldable()
createVisualIndicator(
DesktopModeVisualIndicator.DragStartState.FROM_SPLIT,
isSmallTablet = true,
isLeftRightSplit = true,
)
- result = visualIndicator.updateIndicatorType(foldCenter())
+ var result = visualIndicator.updateIndicatorType(foldCenter())
+ assertThat(result)
+ .isEqualTo(DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR)
+
+ // Check that bubbles are not available from split
+ result = visualIndicator.updateIndicatorType(foldLeftBottom())
+ assertThat(result)
+ .isEqualTo(DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_LEFT_INDICATOR)
+
+ result = visualIndicator.updateIndicatorType(foldRightBottom())
+ assertThat(result)
+ .isEqualTo(DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_RIGHT_INDICATOR)
+ }
+
+ @Test
+ @EnableFlags(com.android.wm.shell.Flags.FLAG_ENABLE_BUBBLE_ANYTHING)
+ fun testDefaultIndicators_foldable_enableBubbleAnything_dragFromSplit() {
+ setUpFoldable()
+
+ createVisualIndicator(
+ DesktopModeVisualIndicator.DragStartState.FROM_SPLIT,
+ isSmallTablet = true,
+ isLeftRightSplit = true,
+ )
+ var result = visualIndicator.updateIndicatorType(foldCenter())
assertThat(result)
.isEqualTo(DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
index bcdff11363ab..d81786b5e6a5 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
@@ -50,6 +50,7 @@ import android.os.Binder
import android.os.Bundle
import android.os.Handler
import android.os.IBinder
+import android.os.UserHandle
import android.os.UserManager
import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
@@ -2380,9 +2381,10 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER)
@DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
fun moveToFullscreen_tdaFreeform_windowingModeFullscreen_removesWallpaperActivity() {
+ whenever(DesktopModeStatus.enterDesktopByDefaultOnFreeformDisplay(context))
+ .thenReturn(false)
val homeTask = setUpHomeTask()
val task = setUpFreeformTask()
-
assertNotNull(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY))
.configuration
.windowConfiguration
@@ -2434,9 +2436,10 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
)
fun moveToFullscreen_tdaFreeform_windowingModeFullscreen_homeBehindFullscreen_multiDesksEnabled() {
+ whenever(DesktopModeStatus.enterDesktopByDefaultOnFreeformDisplay(context))
+ .thenReturn(false)
val homeTask = setUpHomeTask()
val task = setUpFreeformTask()
-
assertNotNull(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY))
.configuration
.windowConfiguration
@@ -5535,7 +5538,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
controller.removeDesk(deskId = 2)
- verify(desksOrganizer).removeDesk(any(), eq(2))
+ verify(desksOrganizer).removeDesk(any(), eq(2), any())
verify(desksTransitionsObserver)
.addPendingTransition(
argThat {
@@ -5551,6 +5554,49 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION,
Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
)
+ fun removeDesk_multipleDesks_removesRunningTasks() {
+ val transition = Binder()
+ whenever(transitions.startTransition(eq(TRANSIT_CLOSE), any(), anyOrNull()))
+ .thenReturn(transition)
+ taskRepository.addDesk(DEFAULT_DISPLAY, deskId = 2)
+ val task1 = setUpFreeformTask(deskId = 2)
+ val task2 = setUpFreeformTask(deskId = 2)
+ val task3 = setUpFreeformTask(deskId = 2)
+
+ controller.removeDesk(deskId = 2)
+
+ val wct = getLatestWct(TRANSIT_CLOSE)
+ wct.assertRemove(task1.token)
+ wct.assertRemove(task2.token)
+ wct.assertRemove(task3.token)
+ }
+
+ @Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION,
+ Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+ )
+ fun removeDesk_multipleDesks_removesRecentTasks() {
+ val transition = Binder()
+ whenever(transitions.startTransition(eq(TRANSIT_CLOSE), any(), anyOrNull()))
+ .thenReturn(transition)
+ taskRepository.addDesk(DEFAULT_DISPLAY, deskId = 2)
+ val task1 = setUpFreeformTask(deskId = 2, background = true)
+ val task2 = setUpFreeformTask(deskId = 2, background = true)
+ val task3 = setUpFreeformTask(deskId = 2, background = true)
+
+ controller.removeDesk(deskId = 2)
+
+ verify(recentTasksController).removeBackgroundTask(task1.taskId)
+ verify(recentTasksController).removeBackgroundTask(task2.taskId)
+ verify(recentTasksController).removeBackgroundTask(task3.taskId)
+ }
+
+ @Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION,
+ Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+ )
fun activateDesk_multipleDesks_addsPendingTransition() {
val deskId = 0
val transition = Binder()
@@ -7507,11 +7553,12 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
@EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
fun testCreateDesk() {
val currentDeskCount = taskRepository.getNumberOfDesks(DEFAULT_DISPLAY)
- whenever(desksOrganizer.createDesk(eq(DEFAULT_DISPLAY), any())).thenAnswer { invocation ->
- (invocation.arguments[1] as DesksOrganizer.OnCreateCallback).onCreated(deskId = 5)
+ whenever(desksOrganizer.createDesk(eq(DEFAULT_DISPLAY), any(), any())).thenAnswer {
+ invocation ->
+ (invocation.arguments[2] as DesksOrganizer.OnCreateCallback).onCreated(deskId = 5)
}
- controller.createDesk(DEFAULT_DISPLAY)
+ controller.createDesk(DEFAULT_DISPLAY, taskRepository.userId)
assertThat(taskRepository.getNumberOfDesks(DEFAULT_DISPLAY)).isEqualTo(currentDeskCount + 1)
}
@@ -7521,7 +7568,17 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
fun testCreateDesk_invalidDisplay_dropsRequest() {
controller.createDesk(INVALID_DISPLAY)
- verify(desksOrganizer, never()).createDesk(any(), any())
+ verify(desksOrganizer, never()).createDesk(any(), any(), any())
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun testCreateDesk_systemUser_dropsRequest() {
+ assumeTrue(UserManager.isHeadlessSystemUserMode())
+
+ controller.createDesk(DEFAULT_DISPLAY, UserHandle.USER_SYSTEM)
+
+ verify(desksOrganizer, never()).createDesk(any(), any(), any())
}
@Test
@@ -8254,6 +8311,15 @@ private fun WindowContainerTransaction.assertReorderSequenceInRange(
.inOrder()
}
+private fun WindowContainerTransaction.assertRemove(token: WindowContainerToken) {
+ assertThat(
+ hierarchyOps.any { hop ->
+ hop.container == token.asBinder() && hop.type == HIERARCHY_OP_TYPE_REMOVE_TASK
+ }
+ )
+ .isTrue()
+}
+
private fun WindowContainerTransaction.assertRemoveAt(index: Int, token: WindowContainerToken) {
assertIndexInBounds(index)
val op = hierarchyOps[index]
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/multidesks/RootTaskDesksOrganizerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/multidesks/RootTaskDesksOrganizerTest.kt
index e57fc38e3607..34f832b4fba4 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/multidesks/RootTaskDesksOrganizerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/multidesks/RootTaskDesksOrganizerTest.kt
@@ -19,7 +19,7 @@ import android.app.ActivityManager
import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
import android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED
import android.testing.AndroidTestingRunner
-import android.view.Display
+import android.view.Display.DEFAULT_DISPLAY
import android.view.SurfaceControl
import android.view.WindowManager.TRANSIT_TO_FRONT
import android.window.TransitionInfo
@@ -41,9 +41,9 @@ import com.android.wm.shell.desktopmode.multidesks.RootTaskDesksOrganizer.DeskRo
import com.android.wm.shell.sysui.ShellCommandHandler
import com.android.wm.shell.sysui.ShellInit
import com.google.common.truth.Truth.assertThat
+import kotlin.coroutines.suspendCoroutine
import kotlin.test.assertNotNull
import kotlinx.coroutines.test.runTest
-import org.junit.Assert.assertEquals
import org.junit.Assert.assertThrows
import org.junit.Before
import org.junit.Test
@@ -86,11 +86,21 @@ class RootTaskDesksOrganizerTest : ShellTestCase() {
organizer.setOnDesktopTaskInfoChangedListener(taskInfoChangedListener)
}
- @Test fun testCreateDesk_createsDeskAndMinimizationRoots() = runTest { createDesk() }
+ @Test fun testCreateDesk_createsDeskAndMinimizationRoots() = runTest { createDeskSuspending() }
+
+ @Test
+ fun testCreateDesk_rootExistsForOtherUser_reusesRoot() = runTest {
+ val desk = createDeskSuspending(userId = PRIMARY_USER_ID)
+
+ val deskId =
+ organizer.createDeskSuspending(displayId = DEFAULT_DISPLAY, userId = SECONDARY_USER_ID)
+
+ assertThat(deskId).isEqualTo(desk.deskRoot.deskId)
+ }
@Test
fun testCreateMinimizationRoot_marksHidden() = runTest {
- val desk = createDesk()
+ val desk = createDeskSuspending()
verify(mockShellTaskOrganizer)
.applyTransaction(
@@ -115,7 +125,7 @@ class RootTaskDesksOrganizerTest : ShellTestCase() {
@Test
fun testOnTaskAppeared_duplicateRoot_throws() = runTest {
- val desk = createDesk()
+ val desk = createDeskSuspending()
assertThrows(Exception::class.java) {
organizer.onTaskAppeared(desk.deskRoot.taskInfo, SurfaceControl())
@@ -124,7 +134,7 @@ class RootTaskDesksOrganizerTest : ShellTestCase() {
@Test
fun testOnTaskAppeared_duplicateMinimizedRoot_throws() = runTest {
- val desk = createDesk()
+ val desk = createDeskSuspending()
assertThrows(Exception::class.java) {
organizer.onTaskAppeared(desk.minimizationRoot.taskInfo, SurfaceControl())
@@ -133,7 +143,7 @@ class RootTaskDesksOrganizerTest : ShellTestCase() {
@Test
fun testOnTaskVanished_removesRoot() = runTest {
- val desk = createDesk()
+ val desk = createDeskSuspending()
organizer.onTaskVanished(desk.deskRoot.taskInfo)
@@ -142,7 +152,7 @@ class RootTaskDesksOrganizerTest : ShellTestCase() {
@Test
fun testOnTaskVanished_removesMinimizedRoot() = runTest {
- val desk = createDesk()
+ val desk = createDeskSuspending()
organizer.onTaskVanished(desk.deskRoot.taskInfo)
organizer.onTaskVanished(desk.minimizationRoot.taskInfo)
@@ -152,7 +162,7 @@ class RootTaskDesksOrganizerTest : ShellTestCase() {
@Test
fun testDesktopWindowAppearsInDesk() = runTest {
- val desk = createDesk()
+ val desk = createDeskSuspending()
val child = createFreeformTask().apply { parentTaskId = desk.deskRoot.deskId }
organizer.onTaskAppeared(child, SurfaceControl())
@@ -162,7 +172,7 @@ class RootTaskDesksOrganizerTest : ShellTestCase() {
@Test
fun testDesktopWindowAppearsInDeskMinimizationRoot() = runTest {
- val desk = createDesk()
+ val desk = createDeskSuspending()
val child = createFreeformTask().apply { parentTaskId = desk.minimizationRoot.rootId }
organizer.onTaskAppeared(child, SurfaceControl())
@@ -172,7 +182,7 @@ class RootTaskDesksOrganizerTest : ShellTestCase() {
@Test
fun testDesktopWindowMovesToMinimizationRoot() = runTest {
- val desk = createDesk()
+ val desk = createDeskSuspending()
val child = createFreeformTask().apply { parentTaskId = desk.deskRoot.deskId }
organizer.onTaskAppeared(child, SurfaceControl())
@@ -185,7 +195,7 @@ class RootTaskDesksOrganizerTest : ShellTestCase() {
@Test
fun testDesktopWindowDisappearsFromDesk() = runTest {
- val desk = createDesk()
+ val desk = createDeskSuspending()
val child = createFreeformTask().apply { parentTaskId = desk.deskRoot.deskId }
organizer.onTaskAppeared(child, SurfaceControl())
@@ -196,7 +206,7 @@ class RootTaskDesksOrganizerTest : ShellTestCase() {
@Test
fun testDesktopWindowDisappearsFromDeskMinimizationRoot() = runTest {
- val desk = createDesk()
+ val desk = createDeskSuspending()
val child = createFreeformTask().apply { parentTaskId = desk.minimizationRoot.rootId }
organizer.onTaskAppeared(child, SurfaceControl())
@@ -206,11 +216,23 @@ class RootTaskDesksOrganizerTest : ShellTestCase() {
}
@Test
+ fun testRemoveDesk_disablesAsLaunchRoot() = runTest {
+ val desk = createDeskSuspending(userId = PRIMARY_USER_ID)
+ val wct = WindowContainerTransaction()
+ organizer.activateDesk(wct, desk.deskRoot.deskId)
+ assertThat(desk.deskRoot.isLaunchRootRequested).isTrue()
+
+ organizer.removeDesk(wct, desk.deskRoot.deskId, userId = PRIMARY_USER_ID)
+
+ assertThat(desk.deskRoot.isLaunchRootRequested).isFalse()
+ }
+
+ @Test
fun testRemoveDesk_removesDeskRoot() = runTest {
- val desk = createDesk()
+ val desk = createDeskSuspending(userId = PRIMARY_USER_ID)
val wct = WindowContainerTransaction()
- organizer.removeDesk(wct, desk.deskRoot.deskId)
+ organizer.removeDesk(wct, desk.deskRoot.deskId, userId = PRIMARY_USER_ID)
assertThat(
wct.hierarchyOps.any { hop ->
@@ -223,10 +245,10 @@ class RootTaskDesksOrganizerTest : ShellTestCase() {
@Test
fun testRemoveDesk_removesMinimizationRoot() = runTest {
- val desk = createDesk()
+ val desk = createDeskSuspending(userId = PRIMARY_USER_ID)
val wct = WindowContainerTransaction()
- organizer.removeDesk(wct, desk.deskRoot.deskId)
+ organizer.removeDesk(wct, desk.deskRoot.deskId, userId = PRIMARY_USER_ID)
assertThat(
wct.hierarchyOps.any { hop ->
@@ -238,8 +260,27 @@ class RootTaskDesksOrganizerTest : ShellTestCase() {
}
@Test
+ fun testRemoveDesk_rootUsedByOtherUser_keepsDeskRoot() = runTest {
+ val primaryUserDesk = createDeskSuspending(userId = PRIMARY_USER_ID)
+ val secondaryUserDesk = createDeskSuspending(userId = SECONDARY_USER_ID)
+ assertThat(primaryUserDesk).isEqualTo(secondaryUserDesk)
+
+ val wct = WindowContainerTransaction()
+ organizer.removeDesk(wct, primaryUserDesk.deskRoot.deskId, userId = PRIMARY_USER_ID)
+
+ assertThat(
+ wct.hierarchyOps.any { hop ->
+ hop.type == HierarchyOp.HIERARCHY_OP_TYPE_REMOVE_ROOT_TASK &&
+ hop.container == primaryUserDesk.deskRoot.token.asBinder()
+ }
+ )
+ .isFalse()
+ assertThat(primaryUserDesk.deskRoot.users).containsExactly(SECONDARY_USER_ID)
+ }
+
+ @Test
fun testActivateDesk() = runTest {
- val desk = createDesk()
+ val desk = createDeskSuspending()
val wct = WindowContainerTransaction()
organizer.activateDesk(wct, desk.deskRoot.deskId)
@@ -271,7 +312,7 @@ class RootTaskDesksOrganizerTest : ShellTestCase() {
@Test
fun testMoveTaskToDesk() = runTest {
- val desk = createDesk()
+ val desk = createDeskSuspending()
val desktopTask = createFreeformTask().apply { parentTaskId = -1 }
val wct = WindowContainerTransaction()
@@ -308,7 +349,7 @@ class RootTaskDesksOrganizerTest : ShellTestCase() {
@Test
fun testGetDeskAtEnd() = runTest {
- val desk = createDesk()
+ val desk = createDeskSuspending()
val task = createFreeformTask().apply { parentTaskId = desk.deskRoot.deskId }
val endDesk =
@@ -321,7 +362,7 @@ class RootTaskDesksOrganizerTest : ShellTestCase() {
@Test
fun testGetDeskAtEnd_inMinimizationRoot() = runTest {
- val desk = createDesk()
+ val desk = createDeskSuspending()
val task = createFreeformTask().apply { parentTaskId = desk.minimizationRoot.rootId }
val endDesk =
@@ -334,7 +375,7 @@ class RootTaskDesksOrganizerTest : ShellTestCase() {
@Test
fun testIsDeskActiveAtEnd() = runTest {
- val desk = createDesk()
+ val desk = createDeskSuspending()
val isActive =
organizer.isDeskActiveAtEnd(
@@ -352,7 +393,7 @@ class RootTaskDesksOrganizerTest : ShellTestCase() {
@Test
fun deactivateDesk_clearsLaunchRoot() = runTest {
val wct = WindowContainerTransaction()
- val desk = createDesk()
+ val desk = createDeskSuspending()
organizer.activateDesk(wct, desk.deskRoot.deskId)
organizer.deactivateDesk(wct, desk.deskRoot.deskId)
@@ -370,7 +411,7 @@ class RootTaskDesksOrganizerTest : ShellTestCase() {
@Test
fun isDeskChange_forDeskId() = runTest {
- val desk = createDesk()
+ val desk = createDeskSuspending()
assertThat(
organizer.isDeskChange(
@@ -385,7 +426,7 @@ class RootTaskDesksOrganizerTest : ShellTestCase() {
@Test
fun isDeskChange_forDeskId_inMinimizationRoot() = runTest {
- val desk = createDesk()
+ val desk = createDeskSuspending()
assertThat(
organizer.isDeskChange(
@@ -403,7 +444,7 @@ class RootTaskDesksOrganizerTest : ShellTestCase() {
@Test
fun isDeskChange_anyDesk() = runTest {
- val desk = createDesk()
+ val desk = createDeskSuspending()
assertThat(
organizer.isDeskChange(
@@ -417,7 +458,7 @@ class RootTaskDesksOrganizerTest : ShellTestCase() {
@Test
fun isDeskChange_anyDesk_inMinimizationRoot() = runTest {
- val desk = createDesk()
+ val desk = createDeskSuspending()
assertThat(
organizer.isDeskChange(
@@ -434,7 +475,7 @@ class RootTaskDesksOrganizerTest : ShellTestCase() {
@Test
fun minimizeTask() = runTest {
- val desk = createDesk()
+ val desk = createDeskSuspending()
val task = createFreeformTask().apply { parentTaskId = desk.deskRoot.deskId }
val wct = WindowContainerTransaction()
organizer.moveTaskToDesk(wct, desk.deskRoot.deskId, task)
@@ -447,7 +488,7 @@ class RootTaskDesksOrganizerTest : ShellTestCase() {
@Test
fun minimizeTask_alreadyMinimized_noOp() = runTest {
- val desk = createDesk()
+ val desk = createDeskSuspending()
val task = createFreeformTask().apply { parentTaskId = desk.minimizationRoot.rootId }
val wct = WindowContainerTransaction()
organizer.onTaskAppeared(task, SurfaceControl())
@@ -459,8 +500,8 @@ class RootTaskDesksOrganizerTest : ShellTestCase() {
@Test
fun minimizeTask_inDifferentDesk_noOp() = runTest {
- val desk = createDesk()
- val otherDesk = createDesk()
+ val desk = createDeskSuspending()
+ val otherDesk = createDeskSuspending()
val task = createFreeformTask().apply { parentTaskId = otherDesk.deskRoot.deskId }
val wct = WindowContainerTransaction()
organizer.onTaskAppeared(task, SurfaceControl())
@@ -472,7 +513,7 @@ class RootTaskDesksOrganizerTest : ShellTestCase() {
@Test
fun unminimizeTask() = runTest {
- val desk = createDesk()
+ val desk = createDeskSuspending()
val task = createFreeformTask().apply { parentTaskId = desk.deskRoot.deskId }
val wct = WindowContainerTransaction()
organizer.moveTaskToDesk(wct, desk.deskRoot.deskId, task)
@@ -489,7 +530,7 @@ class RootTaskDesksOrganizerTest : ShellTestCase() {
@Test
fun unminimizeTask_alreadyUnminimized_noOp() = runTest {
- val desk = createDesk()
+ val desk = createDeskSuspending()
val task = createFreeformTask().apply { parentTaskId = desk.deskRoot.deskId }
val wct = WindowContainerTransaction()
organizer.moveTaskToDesk(wct, desk.deskRoot.deskId, task)
@@ -503,7 +544,7 @@ class RootTaskDesksOrganizerTest : ShellTestCase() {
@Test
fun unminimizeTask_notInDesk_noOp() = runTest {
- val desk = createDesk()
+ val desk = createDeskSuspending()
val task = createFreeformTask()
val wct = WindowContainerTransaction()
@@ -514,7 +555,7 @@ class RootTaskDesksOrganizerTest : ShellTestCase() {
@Test
fun reorderTaskToFront() = runTest {
- val desk = createDesk()
+ val desk = createDeskSuspending()
val task = createFreeformTask().apply { parentTaskId = desk.deskRoot.deskId }
val wct = WindowContainerTransaction()
organizer.onTaskAppeared(task, SurfaceControl())
@@ -534,7 +575,7 @@ class RootTaskDesksOrganizerTest : ShellTestCase() {
@Test
fun reorderTaskToFront_notInDesk_noOp() = runTest {
- val desk = createDesk()
+ val desk = createDeskSuspending()
val task = createFreeformTask()
val wct = WindowContainerTransaction()
@@ -553,7 +594,7 @@ class RootTaskDesksOrganizerTest : ShellTestCase() {
@Test
fun reorderTaskToFront_minimized_unminimizesAndReorders() = runTest {
- val desk = createDesk()
+ val desk = createDeskSuspending()
val task = createFreeformTask().apply { parentTaskId = desk.deskRoot.deskId }
val wct = WindowContainerTransaction()
organizer.onTaskAppeared(task, SurfaceControl())
@@ -578,7 +619,7 @@ class RootTaskDesksOrganizerTest : ShellTestCase() {
fun onTaskAppeared_visibleDesk_onlyDesk_disablesLaunchAdjacent() = runTest {
launchAdjacentController.launchAdjacentEnabled = true
- createDesk(visible = true)
+ createDeskSuspending(visible = true)
assertThat(launchAdjacentController.launchAdjacentEnabled).isFalse()
}
@@ -587,7 +628,7 @@ class RootTaskDesksOrganizerTest : ShellTestCase() {
fun onTaskAppeared_invisibleDesk_onlyDesk_enablesLaunchAdjacent() = runTest {
launchAdjacentController.launchAdjacentEnabled = false
- createDesk(visible = false)
+ createDeskSuspending(visible = false)
assertThat(launchAdjacentController.launchAdjacentEnabled).isTrue()
}
@@ -596,8 +637,8 @@ class RootTaskDesksOrganizerTest : ShellTestCase() {
fun onTaskAppeared_invisibleDesk_otherVisibleDesk_disablesLaunchAdjacent() = runTest {
launchAdjacentController.launchAdjacentEnabled = true
- createDesk(visible = true)
- createDesk(visible = false)
+ createDeskSuspending(visible = true)
+ createDeskSuspending(visible = false)
assertThat(launchAdjacentController.launchAdjacentEnabled).isFalse()
}
@@ -606,7 +647,7 @@ class RootTaskDesksOrganizerTest : ShellTestCase() {
fun onTaskInfoChanged_deskBecomesVisible_onlyDesk_disablesLaunchAdjacent() = runTest {
launchAdjacentController.launchAdjacentEnabled = true
- val desk = createDesk(visible = false)
+ val desk = createDeskSuspending(visible = false)
desk.deskRoot.taskInfo.isVisible = true
organizer.onTaskInfoChanged(desk.deskRoot.taskInfo)
@@ -617,7 +658,7 @@ class RootTaskDesksOrganizerTest : ShellTestCase() {
fun onTaskInfoChanged_deskBecomesInvisible_onlyDesk_enablesLaunchAdjacent() = runTest {
launchAdjacentController.launchAdjacentEnabled = false
- val desk = createDesk(visible = true)
+ val desk = createDeskSuspending(visible = true)
desk.deskRoot.taskInfo.isVisible = false
organizer.onTaskInfoChanged(desk.deskRoot.taskInfo)
@@ -628,8 +669,8 @@ class RootTaskDesksOrganizerTest : ShellTestCase() {
fun onTaskInfoChanged_deskBecomesInvisible_otherVisibleDesk_disablesLaunchAdjacent() = runTest {
launchAdjacentController.launchAdjacentEnabled = true
- createDesk(visible = true)
- val desk = createDesk(visible = true)
+ createDeskSuspending(visible = true)
+ val desk = createDeskSuspending(visible = true)
desk.deskRoot.taskInfo.isVisible = false
organizer.onTaskInfoChanged(desk.deskRoot.taskInfo)
@@ -640,7 +681,7 @@ class RootTaskDesksOrganizerTest : ShellTestCase() {
fun onTaskVanished_visibleDeskDisappears_onlyDesk_enablesLaunchAdjacent() = runTest {
launchAdjacentController.launchAdjacentEnabled = false
- val desk = createDesk(visible = true)
+ val desk = createDeskSuspending(visible = true)
organizer.onTaskVanished(desk.deskRoot.taskInfo)
assertThat(launchAdjacentController.launchAdjacentEnabled).isTrue()
@@ -650,8 +691,8 @@ class RootTaskDesksOrganizerTest : ShellTestCase() {
fun onTaskVanished_visibleDeskDisappears_otherDeskVisible_disablesLaunchAdjacent() = runTest {
launchAdjacentController.launchAdjacentEnabled = true
- createDesk(visible = true)
- val desk = createDesk(visible = true)
+ createDeskSuspending(visible = true)
+ val desk = createDeskSuspending(visible = true)
organizer.onTaskVanished(desk.deskRoot.taskInfo)
assertThat(launchAdjacentController.launchAdjacentEnabled).isFalse()
@@ -659,7 +700,7 @@ class RootTaskDesksOrganizerTest : ShellTestCase() {
@Test
fun onTaskInfoChanged_taskNotRoot_invokesListener() = runTest {
- createDesk()
+ createDeskSuspending()
val task = createFreeformTask().apply { taskId = TEST_CHILD_TASK_ID }
organizer.onTaskInfoChanged(task)
@@ -669,7 +710,7 @@ class RootTaskDesksOrganizerTest : ShellTestCase() {
@Test
fun onTaskInfoChanged_isDeskRoot_doesNotInvokeListener() = runTest {
- val deskRoot = createDesk().deskRoot
+ val deskRoot = createDeskSuspending().deskRoot
organizer.onTaskInfoChanged(deskRoot.taskInfo)
@@ -678,7 +719,7 @@ class RootTaskDesksOrganizerTest : ShellTestCase() {
@Test
fun onTaskInfoChanged_isMinimizationRoot_doesNotInvokeListener() = runTest {
- val minimizationRoot = createDesk().minimizationRoot
+ val minimizationRoot = createDeskSuspending().minimizationRoot
organizer.onTaskInfoChanged(minimizationRoot.taskInfo)
@@ -690,7 +731,10 @@ class RootTaskDesksOrganizerTest : ShellTestCase() {
val minimizationRoot: DeskMinimizationRoot,
)
- private suspend fun createDesk(visible: Boolean = true): DeskRoots {
+ private suspend fun createDeskSuspending(
+ visible: Boolean = true,
+ userId: Int = PRIMARY_USER_ID,
+ ): DeskRoots {
val freeformRootTask =
createFreeformTask().apply {
parentTaskId = -1
@@ -701,7 +745,7 @@ class RootTaskDesksOrganizerTest : ShellTestCase() {
Mockito.reset(mockShellTaskOrganizer)
whenever(
mockShellTaskOrganizer.createRootTask(
- Display.DEFAULT_DISPLAY,
+ DEFAULT_DISPLAY,
WINDOWING_MODE_FREEFORM,
organizer,
true,
@@ -715,13 +759,9 @@ class RootTaskDesksOrganizerTest : ShellTestCase() {
val listener = (invocation.arguments[2] as TaskListener)
listener.onTaskAppeared(minimizationRootTask, SurfaceControl())
}
- val deskId = organizer.createDesk(Display.DEFAULT_DISPLAY)
- assertEquals(freeformRootTask.taskId, deskId)
- val deskRoot = assertNotNull(organizer.deskRootsByDeskId.get(freeformRootTask.taskId))
- val minimizationRoot =
- assertNotNull(organizer.deskMinimizationRootsByDeskId[freeformRootTask.taskId])
- assertThat(minimizationRoot.deskId).isEqualTo(freeformRootTask.taskId)
- assertThat(minimizationRoot.rootId).isEqualTo(minimizationRootTask.taskId)
+ val deskId = organizer.createDeskSuspending(DEFAULT_DISPLAY, userId)
+ val deskRoot = assertNotNull(organizer.deskRootsByDeskId.get(deskId))
+ val minimizationRoot = assertNotNull(organizer.deskMinimizationRootsByDeskId[deskId])
return DeskRoots(deskRoot, minimizationRoot)
}
@@ -746,7 +786,14 @@ class RootTaskDesksOrganizerTest : ShellTestCase() {
hop.toTop
}
+ private suspend fun DesksOrganizer.createDeskSuspending(displayId: Int, userId: Int): Int =
+ suspendCoroutine { cont ->
+ createDesk(displayId, userId) { deskId -> cont.resumeWith(Result.success(deskId)) }
+ }
+
companion object {
+ private const val PRIMARY_USER_ID = 10
+ private const val SECONDARY_USER_ID = 11
private const val TEST_CHILD_TASK_ID = 100
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
index 82373ff1bc41..64bd86134d92 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
@@ -167,6 +167,7 @@ public class StageCoordinatorTests extends ShellTestCase {
private final TestShellExecutor mMainExecutor = new TestShellExecutor();
private final ShellExecutor mAnimExecutor = new TestShellExecutor();
private final Handler mMainHandler = new Handler(Looper.getMainLooper());
+ private final Handler mAnimHandler = mock(Handler.class);
private final DisplayAreaInfo mDisplayAreaInfo = new DisplayAreaInfo(new MockToken().token(),
DEFAULT_DISPLAY, 0);
private final ActivityManager.RunningTaskInfo mMainChildTaskInfo =
@@ -629,7 +630,7 @@ public class StageCoordinatorTests extends ShellTestCase {
ShellInit shellInit = new ShellInit(mMainExecutor);
final Transitions t = new Transitions(mContext, shellInit, mock(ShellController.class),
mTaskOrganizer, mTransactionPool, mock(DisplayController.class),
- mDisplayInsetsController, mMainExecutor, mMainHandler, mAnimExecutor,
+ mDisplayInsetsController, mMainExecutor, mMainHandler, mAnimExecutor, mAnimHandler,
mock(HomeTransitionObserver.class), mock(FocusTransitionObserver.class));
shellInit.init();
return t;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/DefaultTransitionHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/DefaultTransitionHandlerTest.java
index 6996d44af034..2dab39184247 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/DefaultTransitionHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/DefaultTransitionHandlerTest.java
@@ -100,7 +100,8 @@ public class DefaultTransitionHandlerTest extends ShellTestCase {
mTransitionHandler = new DefaultTransitionHandler(
mContext, mShellInit, mDisplayController, mDisplayInsetsController,
mTransactionPool, mMainExecutor, mMainHandler, mAnimExecutor,
- mRootTaskDisplayAreaOrganizer, mock(InteractionJankMonitor.class));
+ mock(Handler.class), mRootTaskDisplayAreaOrganizer,
+ mock(InteractionJankMonitor.class));
mShellInit.init();
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/HomeTransitionObserverTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/HomeTransitionObserverTest.java
index 52634c08dafd..5d77766dc0db 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/HomeTransitionObserverTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/HomeTransitionObserverTest.java
@@ -88,6 +88,7 @@ public class HomeTransitionObserverTest extends ShellTestCase {
private final ShellExecutor mAnimExecutor = new TestShellExecutor();
private final TestShellExecutor mMainExecutor = new TestShellExecutor();
private final Handler mMainHandler = new Handler(Looper.getMainLooper());
+ private final Handler mAnimHandler = mock(Handler.class);
private final DisplayController mDisplayController = mock(DisplayController.class);
private final DisplayInsetsController mDisplayInsetsController =
mock(DisplayInsetsController.class);
@@ -105,7 +106,7 @@ public class HomeTransitionObserverTest extends ShellTestCase {
mDisplayInsetsController, mock(ShellInit.class));
mTransition = new Transitions(mContext, mock(ShellInit.class), mock(ShellController.class),
mOrganizer, mTransactionPool, mDisplayController, mDisplayInsetsController,
- mMainExecutor, mMainHandler, mAnimExecutor, mHomeTransitionObserver,
+ mMainExecutor, mMainHandler, mAnimExecutor, mAnimHandler, mHomeTransitionObserver,
mock(FocusTransitionObserver.class));
mHomeTransitionObserver.setHomeTransitionListener(mTransition, mListener);
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
index 44bb2154f170..4dd9cab1d340 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
@@ -146,6 +146,7 @@ public class ShellTransitionTests extends ShellTestCase {
private final ShellExecutor mAnimExecutor = new TestShellExecutor();
private final TestTransitionHandler mDefaultHandler = new TestTransitionHandler();
private final Handler mMainHandler = new Handler(Looper.getMainLooper());
+ private final Handler mAnimHandler = mock(Handler.class);
private final DisplayInsetsController mDisplayInsets =
mock(DisplayInsetsController.class);
@@ -160,7 +161,7 @@ public class ShellTransitionTests extends ShellTestCase {
ShellInit shellInit = mock(ShellInit.class);
final Transitions t = new Transitions(mContext, shellInit, mock(ShellController.class),
mOrganizer, mTransactionPool, createTestDisplayController(), mDisplayInsets,
- mMainExecutor, mMainHandler, mAnimExecutor,
+ mMainExecutor, mMainHandler, mAnimExecutor, mAnimHandler,
mock(HomeTransitionObserver.class), mock(FocusTransitionObserver.class));
// One from Transitions, one from RootTaskDisplayAreaOrganizer
verify(shellInit).addInitCallback(any(), eq(t));
@@ -173,7 +174,7 @@ public class ShellTransitionTests extends ShellTestCase {
ShellController shellController = mock(ShellController.class);
final Transitions t = new Transitions(mContext, shellInit, shellController,
mOrganizer, mTransactionPool, createTestDisplayController(), mDisplayInsets,
- mMainExecutor, mMainHandler, mAnimExecutor,
+ mMainExecutor, mMainHandler, mAnimExecutor, mAnimHandler,
mock(HomeTransitionObserver.class), mock(FocusTransitionObserver.class));
shellInit.init();
verify(shellController, times(1)).addExternalInterface(
@@ -1318,7 +1319,7 @@ public class ShellTransitionTests extends ShellTestCase {
final Transitions transitions =
new Transitions(mContext, shellInit, mock(ShellController.class), mOrganizer,
mTransactionPool, createTestDisplayController(), mDisplayInsets,
- mMainExecutor, mMainHandler, mAnimExecutor,
+ mMainExecutor, mMainHandler, mAnimExecutor, mAnimHandler,
mock(HomeTransitionObserver.class),
mock(FocusTransitionObserver.class));
final RecentTasksController mockRecentsTaskController = mock(RecentTasksController.class);
@@ -1914,7 +1915,8 @@ public class ShellTransitionTests extends ShellTestCase {
ShellInit shellInit = new ShellInit(mMainExecutor);
final Transitions t = new Transitions(mContext, shellInit, mock(ShellController.class),
mOrganizer, mTransactionPool, createTestDisplayController(), mDisplayInsets,
- mMainExecutor, mMainHandler, mAnimExecutor, mock(HomeTransitionObserver.class),
+ mMainExecutor, mMainHandler, mAnimExecutor, mAnimHandler,
+ mock(HomeTransitionObserver.class),
mock(FocusTransitionObserver.class));
shellInit.init();
return t;
diff --git a/libs/androidfw/AssetManager2.cpp b/libs/androidfw/AssetManager2.cpp
index e09ab5fd1643..508750720690 100644
--- a/libs/androidfw/AssetManager2.cpp
+++ b/libs/androidfw/AssetManager2.cpp
@@ -82,6 +82,9 @@ struct FindEntryResult {
// The bitmask of configuration axis with which the resource value varies.
uint32_t type_flags;
+ // The bitmask of ResTable_entry flags
+ uint16_t entry_flags;
+
// The dynamic package ID map for the package from which this resource came from.
const DynamicRefTable* dynamic_ref_table;
@@ -600,12 +603,12 @@ base::expected<std::set<ResTable_config>, IOError> AssetManager2::GetResourceCon
return configurations;
}
-std::set<std::string> AssetManager2::GetResourceLocales(bool exclude_system,
- bool merge_equivalent_languages) const {
+LoadedPackage::Locales AssetManager2::GetResourceLocales(
+ bool exclude_system, bool merge_equivalent_languages) const {
ATRACE_NAME("AssetManager::GetResourceLocales");
auto op = StartOperation();
- std::set<std::string> locales;
+ LoadedPackage::Locales locales;
const auto non_system_overlays =
exclude_system ? GetNonSystemOverlays() : std::set<ApkAssetsPtr>();
@@ -619,8 +622,7 @@ std::set<std::string> AssetManager2::GetResourceLocales(bool exclude_system,
if (!non_system_overlays.empty()) {
// Exclude overlays that target only system resources.
const auto& apk_assets = GetApkAssets(package_group.cookies_[i]);
- if (apk_assets && apk_assets->IsOverlay() &&
- non_system_overlays.find(apk_assets) == non_system_overlays.end()) {
+ if (apk_assets && apk_assets->IsOverlay() && !non_system_overlays.contains(apk_assets)) {
continue;
}
}
@@ -1031,6 +1033,7 @@ base::expected<FindEntryResult, NullOrIOError> AssetManager2::FindEntryInternal(
.entry = *entry,
.config = *best_config,
.type_flags = type_flags,
+ .entry_flags = (*best_entry_verified)->flags(),
.dynamic_ref_table = package_group.dynamic_ref_table.get(),
.package_name = &best_package->GetPackageName(),
.type_string_ref = StringPoolRef(best_package->GetTypeStringPool(), best_type->id - 1),
@@ -1185,16 +1188,16 @@ base::expected<AssetManager2::SelectedValue, NullOrIOError> AssetManager2::GetRe
}
// Create a reference since we can't represent this complex type as a Res_value.
- return SelectedValue(Res_value::TYPE_REFERENCE, resid, result->cookie, result->type_flags,
- resid, result->config);
+ return SelectedValue(Res_value::TYPE_REFERENCE, resid, result->cookie, result->entry_flags,
+ result->type_flags, resid, result->config);
}
// Convert the package ID to the runtime assigned package ID.
Res_value value = std::get<Res_value>(result->entry);
result->dynamic_ref_table->lookupResourceValue(&value);
- return SelectedValue(value.dataType, value.data, result->cookie, result->type_flags,
- resid, result->config);
+ return SelectedValue(value.dataType, value.data, result->cookie, result->entry_flags,
+ result->type_flags, resid, result->config);
}
base::expected<std::monostate, NullOrIOError> AssetManager2::ResolveReference(
@@ -1847,8 +1850,8 @@ std::optional<AssetManager2::SelectedValue> Theme::GetAttribute(uint32_t resid)
}
return AssetManager2::SelectedValue(entry_it->value.dataType, entry_it->value.data,
- entry_it->cookie, type_spec_flags, 0U /* resid */,
- {} /* config */);
+ entry_it->cookie, 0U /* entry flags*/, type_spec_flags,
+ 0U /* resid */, {} /* config */);
}
return std::nullopt;
}
diff --git a/libs/androidfw/LoadedArsc.cpp b/libs/androidfw/LoadedArsc.cpp
index d9166a16cdea..7cebb6d79502 100644
--- a/libs/androidfw/LoadedArsc.cpp
+++ b/libs/androidfw/LoadedArsc.cpp
@@ -361,14 +361,18 @@ base::expected<std::monostate, IOError> LoadedPackage::CollectConfigurations(
return {};
}
-void LoadedPackage::CollectLocales(bool canonicalize, std::set<std::string>* out_locales) const {
- char temp_locale[RESTABLE_MAX_LOCALE_LEN];
+void LoadedPackage::CollectLocales(bool canonicalize,
+ Locales* out_locales) const {
for (const auto& type_spec : type_specs_) {
for (const auto& type_entry : type_spec.second.type_entries) {
if (type_entry.config.locale != 0) {
+ char temp_locale[RESTABLE_MAX_LOCALE_LEN];
type_entry.config.getBcp47Locale(temp_locale, canonicalize);
- std::string locale(temp_locale);
- out_locales->insert(std::move(locale));
+ auto locale_sv = std::string_view(temp_locale);
+ if (auto it = out_locales->lower_bound(locale_sv);
+ it == out_locales->end() || *it != locale_sv) {
+ out_locales->emplace_hint(it, locale_sv);
+ }
}
}
}
diff --git a/libs/androidfw/include/androidfw/AssetManager2.h b/libs/androidfw/include/androidfw/AssetManager2.h
index b0179524f6cd..e312042cf7da 100644
--- a/libs/androidfw/include/androidfw/AssetManager2.h
+++ b/libs/androidfw/include/androidfw/AssetManager2.h
@@ -189,8 +189,8 @@ class AssetManager2 {
// ('android' package, other libraries) will be excluded from the list.
// If `merge_equivalent_languages` is set to true, resource locales will be canonicalized
// and de-duped in the resulting list.
- std::set<std::string> GetResourceLocales(bool exclude_system = false,
- bool merge_equivalent_languages = false) const;
+ LoadedPackage::Locales GetResourceLocales(
+ bool exclude_system = false, bool merge_equivalent_languages = false) const;
// Searches the set of APKs loaded by this AssetManager and opens the first one found located
// in the assets/ directory.
@@ -257,6 +257,7 @@ class AssetManager2 {
: cookie(entry.cookie),
data(entry.value.data),
type(entry.value.dataType),
+ entry_flags(0U),
flags(bag->type_spec_flags),
resid(0U),
config() {
@@ -271,6 +272,9 @@ class AssetManager2 {
// Type of the data value.
uint8_t type;
+ // The bitmask of ResTable_entry flags
+ uint16_t entry_flags;
+
// The bitmask of configuration axis that this resource varies with.
// See ResTable_config::CONFIG_*.
uint32_t flags;
@@ -283,9 +287,10 @@ class AssetManager2 {
private:
SelectedValue(uint8_t value_type, Res_value::data_type value_data, ApkAssetsCookie cookie,
- uint32_t type_flags, uint32_t resid, ResTable_config config) :
- cookie(cookie), data(value_data), type(value_type), flags(type_flags),
- resid(resid), config(std::move(config)) {}
+ uint16_t entry_flags, uint32_t type_flags, uint32_t resid, ResTable_config config)
+ :
+ cookie(cookie), data(value_data), type(value_type), entry_flags(entry_flags),
+ flags(type_flags), resid(resid), config(std::move(config)) {}
};
// Retrieves the best matching resource value with ID `resid`.
diff --git a/libs/androidfw/include/androidfw/LoadedArsc.h b/libs/androidfw/include/androidfw/LoadedArsc.h
index 413b27829474..eb99dc7b8eff 100644
--- a/libs/androidfw/include/androidfw/LoadedArsc.h
+++ b/libs/androidfw/include/androidfw/LoadedArsc.h
@@ -238,7 +238,8 @@ class LoadedPackage {
// Populates a set of strings representing locales.
// If `canonicalize` is set to true, each locale is transformed into its canonical format
// before being inserted into the set. This may cause some equivalent locales to de-dupe.
- void CollectLocales(bool canonicalize, std::set<std::string>* out_locales) const;
+ using Locales = std::set<std::string, std::less<>>;
+ void CollectLocales(bool canonicalize, Locales* out_locales) const;
// type_idx is TT - 1 from 0xPPTTEEEE.
inline const TypeSpec* GetTypeSpecByTypeIndex(uint8_t type_index) const {
diff --git a/libs/androidfw/tests/AssetManager2_bench.cpp b/libs/androidfw/tests/AssetManager2_bench.cpp
index 136f5ea639a1..c469817d3595 100644
--- a/libs/androidfw/tests/AssetManager2_bench.cpp
+++ b/libs/androidfw/tests/AssetManager2_bench.cpp
@@ -191,7 +191,7 @@ static void BM_AssetManagerGetResourceLocales(benchmark::State& state) {
assets.SetApkAssets({apk});
for (auto&& _ : state) {
- std::set<std::string> locales =
+ auto locales =
assets.GetResourceLocales(false /*exclude_system*/, true /*merge_equivalent_languages*/);
benchmark::DoNotOptimize(locales);
}
diff --git a/libs/androidfw/tests/AssetManager2_test.cpp b/libs/androidfw/tests/AssetManager2_test.cpp
index 3f228841f6ba..a691c5dabfcb 100644
--- a/libs/androidfw/tests/AssetManager2_test.cpp
+++ b/libs/androidfw/tests/AssetManager2_test.cpp
@@ -23,6 +23,7 @@
#include "androidfw/ResourceUtils.h"
#include "data/appaslib/R.h"
#include "data/basic/R.h"
+#include "data/flagged/R.h"
#include "data/lib_one/R.h"
#include "data/lib_two/R.h"
#include "data/libclient/R.h"
@@ -32,6 +33,7 @@
namespace app = com::android::app;
namespace appaslib = com::android::appaslib::app;
namespace basic = com::android::basic;
+namespace flagged = com::android::flagged;
namespace lib_one = com::android::lib_one;
namespace lib_two = com::android::lib_two;
namespace libclient = com::android::libclient;
@@ -87,6 +89,10 @@ class AssetManager2Test : public ::testing::Test {
overlayable_assets_ = ApkAssets::Load("overlayable/overlayable.apk");
ASSERT_THAT(overlayable_assets_, NotNull());
+
+ flagged_assets_ = ApkAssets::Load("flagged/flagged.apk");
+ ASSERT_THAT(app_assets_, NotNull());
+
chdir(original_path.c_str());
}
@@ -104,6 +110,7 @@ class AssetManager2Test : public ::testing::Test {
AssetManager2::ApkAssetsPtr app_assets_;
AssetManager2::ApkAssetsPtr overlay_assets_;
AssetManager2::ApkAssetsPtr overlayable_assets_;
+ AssetManager2::ApkAssetsPtr flagged_assets_;
};
TEST_F(AssetManager2Test, FindsResourceFromSingleApkAssets) {
@@ -621,7 +628,7 @@ TEST_F(AssetManager2Test, GetResourceLocales) {
AssetManager2 assetmanager;
assetmanager.SetApkAssets({system_assets_, basic_de_fr_assets_});
- std::set<std::string> locales = assetmanager.GetResourceLocales();
+ auto locales = assetmanager.GetResourceLocales();
// We expect the locale sv from the system assets, and de and fr from basic_de_fr assets.
EXPECT_EQ(3u, locales.size());
@@ -856,4 +863,12 @@ TEST_F(AssetManager2Test, GetApkAssets) {
EXPECT_EQ(1, lib_one_assets_->getStrongCount());
}
+TEST_F(AssetManager2Test, GetFlaggedAssets) {
+ AssetManager2 assetmanager;
+ assetmanager.SetApkAssets({flagged_assets_});
+ auto value = assetmanager.GetResource(flagged::R::xml::flagged, false, 0);
+ ASSERT_TRUE(value.has_value());
+ EXPECT_TRUE(value->entry_flags & ResTable_entry::FLAG_USES_FEATURE_FLAGS);
+}
+
} // namespace android
diff --git a/libs/androidfw/tests/data/flagged/AndroidManifest.xml b/libs/androidfw/tests/data/flagged/AndroidManifest.xml
new file mode 100644
index 000000000000..cc1394328797
--- /dev/null
+++ b/libs/androidfw/tests/data/flagged/AndroidManifest.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2025 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.basic">
+ <application />
+</manifest>
diff --git a/libs/androidfw/tests/data/flagged/R.h b/libs/androidfw/tests/data/flagged/R.h
new file mode 100644
index 000000000000..33ccab28cdd3
--- /dev/null
+++ b/libs/androidfw/tests/data/flagged/R.h
@@ -0,0 +1,35 @@
+/*
+* Copyright (C) 2025 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.
+*/
+
+#pragma once
+
+#include <cstdint>
+
+namespace com {
+namespace android {
+namespace flagged {
+
+struct R {
+ struct xml {
+ enum : uint32_t {
+ flagged = 0x7f010000,
+ };
+ };
+};
+
+} // namespace flagged
+} // namespace android
+} // namespace com \ No newline at end of file
diff --git a/libs/androidfw/tests/data/flagged/build b/libs/androidfw/tests/data/flagged/build
new file mode 100755
index 000000000000..9e5d21ba1833
--- /dev/null
+++ b/libs/androidfw/tests/data/flagged/build
@@ -0,0 +1,28 @@
+#!/bin/bash
+#
+# Copyright (C) 2025 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.
+#
+
+set -e
+
+PATH_TO_FRAMEWORK_RES=${ANDROID_BUILD_TOP}/prebuilts/sdk/current/public/android.jar
+
+aapt2 compile --dir res -o compiled.flata
+aapt2 link -o flagged.apk \
+ --manifest AndroidManifest.xml \
+ -I $PATH_TO_FRAMEWORK_RES \
+ -I ../basic/basic.apk \
+ compiled.flata
+rm compiled.flata
diff --git a/libs/androidfw/tests/data/flagged/flagged.apk b/libs/androidfw/tests/data/flagged/flagged.apk
new file mode 100644
index 000000000000..94b8f4d9fcf0
--- /dev/null
+++ b/libs/androidfw/tests/data/flagged/flagged.apk
Binary files differ
diff --git a/libs/androidfw/tests/data/flagged/res/xml/flagged.xml b/libs/androidfw/tests/data/flagged/res/xml/flagged.xml
new file mode 100644
index 000000000000..5fe8d1b3ac27
--- /dev/null
+++ b/libs/androidfw/tests/data/flagged/res/xml/flagged.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2025 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.
+-->
+<first xmlns:android="http://schemas.android.com/apk/res/android">
+ <second android:featureFlag="android.content.res.always_false"/>
+</first> \ No newline at end of file
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index ab1be7e6128d..1bde5ff43aa8 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -168,6 +168,14 @@ cc_defaults {
"libutils",
],
},
+ host_linux: {
+ shared_libs: [
+ "libaconfig_storage_read_api_cc",
+ ],
+ whole_static_libs: [
+ "hwui_flags_cc_lib",
+ ],
+ },
},
}
diff --git a/libs/hwui/jni/Bitmap.cpp b/libs/hwui/jni/Bitmap.cpp
index b6a2ad7064a9..1a258e022dd0 100644
--- a/libs/hwui/jni/Bitmap.cpp
+++ b/libs/hwui/jni/Bitmap.cpp
@@ -2,6 +2,9 @@
#include "Bitmap.h"
#include <android-base/unique_fd.h>
+#ifdef __linux__
+#include <com_android_graphics_hwui_flags.h>
+#endif
#include <hwui/Bitmap.h>
#include <hwui/Paint.h>
#include <inttypes.h>
@@ -33,15 +36,6 @@
#endif
#include "android_nio_utils.h"
-#ifdef __ANDROID__
-#include <com_android_graphics_hwui_flags.h>
-namespace hwui_flags = com::android::graphics::hwui::flags;
-#else
-namespace hwui_flags {
-constexpr bool bitmap_parcel_ashmem_as_immutable() { return false; }
-}
-#endif
-
#define DEBUG_PARCEL 0
static jclass gBitmap_class;
@@ -861,7 +855,7 @@ static bool shouldParcelAsMutable(SkBitmap& bitmap, AParcel* parcel) {
return false;
}
- if (!hwui_flags::bitmap_parcel_ashmem_as_immutable()) {
+ if (!com::android::graphics::hwui::flags::bitmap_parcel_ashmem_as_immutable()) {
return true;
}