diff options
Diffstat (limited to 'libs')
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 Binary files differnew file mode 100644 index 000000000000..94b8f4d9fcf0 --- /dev/null +++ b/libs/androidfw/tests/data/flagged/flagged.apk 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; } |