summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--PREUPLOAD.cfg2
-rw-r--r--core/java/android/hardware/contexthub/HubServiceInfo.java15
-rw-r--r--core/java/android/view/Display.java16
-rw-r--r--core/java/android/view/DisplayInfo.java17
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java28
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvStageCoordinator.java2
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/AppCompatUtilsTest.kt3
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java20
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUILayoutTest.java10
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIShellTestCase.java39
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIStatusManagerTest.java4
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java14
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/LetterboxEduDialogLayoutTest.java10
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/LetterboxEduWindowManagerTest.java14
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/ReachabilityEduLayoutTest.java10
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/ReachabilityEduWindowManagerTest.java10
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/RestartDialogLayoutTest.java10
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/RestartDialogWindowManagerTest.java10
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsLayoutTest.java10
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManagerTest.java10
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/CloseDesktopTaskTransitionHandlerTest.kt16
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopActivityOrientationChangeHandlerTest.kt77
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopBackNavigationTransitionHandlerTest.kt46
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandlerTest.kt192
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopImmersiveControllerTest.kt286
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandlerTest.kt357
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeEventLoggerTest.kt182
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandlerTest.kt182
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt1409
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTransitionTypesTest.kt13
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeUiEventLoggerTest.kt19
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicatorTest.kt136
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt54
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTaskChangeListenerTest.kt286
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt8984
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt353
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt32
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopUserRepositoriesTest.kt24
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt97
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/WindowDecorCaptionHandleRepositoryTest.kt106
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/compatui/SystemModalsTransitionHandlerTest.kt6
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/education/AppHandleEducationControllerTest.kt843
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/education/AppHandleEducationDatastoreRepositoryTest.kt164
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/education/AppHandleEducationFilterTest.kt402
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/minimize/DesktopWindowLimitRemoteHandlerTest.kt44
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/persistence/DesktopPersistentRepositoryTest.kt18
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/persistence/DesktopRepositoryInitializerTest.kt223
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTestUtils.java7
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java2
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java5
-rw-r--r--media/java/android/media/AudioDevicePort.java21
-rw-r--r--media/java/android/media/AudioManager.java13
-rw-r--r--media/java/android/media/AudioPortEventHandler.java20
-rw-r--r--packages/SettingsLib/SettingsTheme/src/com/android/settingslib/widget/SettingsBasePreferenceFragment.kt14
-rw-r--r--packages/SettingsLib/res/values/arrays.xml6
-rw-r--r--packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java28
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt2
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt9
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/common/ui/data/repository/ConfigurationRepositoryImplTest.kt15
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/AppLaunchDataRepositoryTest.kt125
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/CustomShortcutCategoriesRepositoryTest.kt63
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/InputGestureDataAdapterTest.kt59
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperInputDeviceRepositoryTest.kt67
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/source/TestShortcuts.kt22
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt10
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/shade/data/repository/ShadeDisplaysRepositoryTest.kt81
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/shade/data/repository/ShadePrimaryDisplayCommandTest.kt38
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ConfigurationControllerImplTest.kt39
-rw-r--r--packages/SystemUI/src/com/android/systemui/common/ui/data/repository/ConfigurationRepository.kt18
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/model/InternalShortcutModels.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/AppLaunchDataRepository.kt109
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/CustomShortcutCategoriesRepository.kt77
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/DefaultShortcutCategoriesRepository.kt19
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/InputGestureDataAdapter.kt145
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutCategoriesUtils.kt20
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperInputDeviceRepository.kt48
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutCommand.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutCustomizationRequestInfo.kt30
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt20
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutCustomizationViewModel.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/scrim/ScrimDrawable.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/ShadePrimaryDisplayCommand.kt52
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/ShadeTraceLogger.kt59
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeDisplaysRepository.kt33
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/display/DefaultDisplayShadePolicy.kt (renamed from packages/SystemUI/src/com/android/systemui/shade/display/SpecificDisplayIdPolicy.kt)11
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/display/ShadeDisplayPolicy.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractor.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt47
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationForwarder.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/ConfigurationController.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/window/flag/WindowBlurFlag.kt32
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/common/ui/data/repository/FakeConfigurationRepository.kt10
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt34
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/ShadeDisplaysRepositoryKosmos.kt43
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/FakeConfigurationController.kt4
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeConfigurationController.java8
-rw-r--r--services/core/java/com/android/server/display/DisplayDevice.java8
-rw-r--r--services/core/java/com/android/server/display/DisplayManagerService.java29
-rw-r--r--services/core/java/com/android/server/display/DisplayPowerController.java1
-rw-r--r--services/core/java/com/android/server/display/ExternalDisplayPolicy.java6
-rw-r--r--services/core/java/com/android/server/display/LogicalDisplay.java60
-rw-r--r--services/core/java/com/android/server/display/VirtualDisplayAdapter.java20
-rw-r--r--services/core/java/com/android/server/wm/DeferredDisplayUpdater.java3
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java92
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayTest.java65
108 files changed, 9117 insertions, 7494 deletions
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
index d83109a1a986..5e0428bab467 100644
--- a/PREUPLOAD.cfg
+++ b/PREUPLOAD.cfg
@@ -18,7 +18,7 @@ clang_format = --commit ${PREUPLOAD_COMMIT} --style file --extensions c,h,cc,cpp
tests/
tools/
bpfmt = -d
-ktfmt = --kotlinlang-style --include-dirs=services/permission,packages/SystemUI,libs/WindowManager/Shell/src/com/android/wm/shell/freeform,libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode
+ktfmt = --kotlinlang-style --include-dirs=services/permission,packages/SystemUI,libs/WindowManager/Shell/src/com/android/wm/shell/freeform,libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode,libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode
[Hook Scripts]
checkstyle_hook = ${REPO_ROOT}/prebuilts/checkstyle/checkstyle.py --sha ${PREUPLOAD_COMMIT}
diff --git a/core/java/android/hardware/contexthub/HubServiceInfo.java b/core/java/android/hardware/contexthub/HubServiceInfo.java
index a1c52fb5864f..2f33e8f8872b 100644
--- a/core/java/android/hardware/contexthub/HubServiceInfo.java
+++ b/core/java/android/hardware/contexthub/HubServiceInfo.java
@@ -132,6 +132,21 @@ public final class HubServiceInfo implements Parcelable {
return 0;
}
+ @Override
+ public String toString() {
+ StringBuilder out = new StringBuilder();
+ out.append("Service: ");
+ out.append("descriptor=");
+ out.append(mServiceDescriptor);
+ out.append(", format=");
+ out.append(mFormat);
+ out.append(", version=");
+ out.append(Integer.toHexString(mMajorVersion));
+ out.append(".");
+ out.append(Integer.toHexString(mMinorVersion));
+ return out.toString();
+ }
+
/** Parcel implementation details */
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java
index 1ea226b013d6..0c8a0d60a96a 100644
--- a/core/java/android/view/Display.java
+++ b/core/java/android/view/Display.java
@@ -2056,6 +2056,22 @@ public final class Display {
}
/**
+ * Returns whether the display is eligible for hosting tasks.
+ *
+ * For example, if the display is used for mirroring, this will return {@code false}.
+ *
+ * TODO (b/383666349): Rename this later once there is a better option.
+ *
+ * @hide
+ */
+ public boolean canHostTasks() {
+ synchronized (mLock) {
+ updateDisplayInfoLocked();
+ return mIsValid && mDisplayInfo.canHostTasks;
+ }
+ }
+
+ /**
* Returns true if the specified UID has access to this display.
* @hide
*/
diff --git a/core/java/android/view/DisplayInfo.java b/core/java/android/view/DisplayInfo.java
index 43078847326c..ba098eb53246 100644
--- a/core/java/android/view/DisplayInfo.java
+++ b/core/java/android/view/DisplayInfo.java
@@ -408,6 +408,15 @@ public final class DisplayInfo implements Parcelable {
@Nullable
public String thermalBrightnessThrottlingDataId;
+ /**
+ * Indicates whether the display is eligible for hosting tasks.
+ *
+ * For example, if the display is used for mirroring, this will be {@code false}.
+ *
+ * @hide
+ */
+ public boolean canHostTasks;
+
public static final @android.annotation.NonNull Creator<DisplayInfo> CREATOR = new Creator<DisplayInfo>() {
@Override
public DisplayInfo createFromParcel(Parcel source) {
@@ -493,7 +502,8 @@ public final class DisplayInfo implements Parcelable {
&& BrightnessSynchronizer.floatEquals(hdrSdrRatio, other.hdrSdrRatio)
&& thermalRefreshRateThrottling.contentEquals(other.thermalRefreshRateThrottling)
&& Objects.equals(
- thermalBrightnessThrottlingDataId, other.thermalBrightnessThrottlingDataId);
+ thermalBrightnessThrottlingDataId, other.thermalBrightnessThrottlingDataId)
+ && canHostTasks == other.canHostTasks;
}
@Override
@@ -561,6 +571,7 @@ public final class DisplayInfo implements Parcelable {
hdrSdrRatio = other.hdrSdrRatio;
thermalRefreshRateThrottling = other.thermalRefreshRateThrottling;
thermalBrightnessThrottlingDataId = other.thermalBrightnessThrottlingDataId;
+ canHostTasks = other.canHostTasks;
}
public void readFromParcel(Parcel source) {
@@ -642,6 +653,7 @@ public final class DisplayInfo implements Parcelable {
thermalRefreshRateThrottling = source.readSparseArray(null,
SurfaceControl.RefreshRateRange.class);
thermalBrightnessThrottlingDataId = source.readString8();
+ canHostTasks = source.readBoolean();
}
@Override
@@ -717,6 +729,7 @@ public final class DisplayInfo implements Parcelable {
dest.writeFloat(hdrSdrRatio);
dest.writeSparseArray(thermalRefreshRateThrottling);
dest.writeString8(thermalBrightnessThrottlingDataId);
+ dest.writeBoolean(canHostTasks);
}
@Override
@@ -1020,6 +1033,8 @@ public final class DisplayInfo implements Parcelable {
sb.append(thermalRefreshRateThrottling);
sb.append(", thermalBrightnessThrottlingDataId ");
sb.append(thermalBrightnessThrottlingDataId);
+ sb.append(", canHostTasks ");
+ sb.append(canHostTasks);
sb.append("}");
return sb.toString();
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
index 5dd49f0b5c0f..39ed2061c675 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
@@ -302,7 +302,8 @@ public class SplitScreenController implements SplitDragPolicy.Starter,
mTaskOrganizer, mDisplayController, mDisplayImeController,
mDisplayInsetsController, mTransitions, mTransactionPool, mIconProvider,
mMainExecutor, mMainHandler, mBgExecutor, mRecentTasksOptional,
- mLaunchAdjacentController, mWindowDecorViewModel, mSplitState);
+ mLaunchAdjacentController, mWindowDecorViewModel, mSplitState,
+ mDesktopTasksController);
}
@Override
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 d0c21c9ec7c0..164dbe97c1f5 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
@@ -142,6 +142,7 @@ import com.android.wm.shell.common.split.SplitLayout;
import com.android.wm.shell.common.split.SplitScreenUtils;
import com.android.wm.shell.common.split.SplitState;
import com.android.wm.shell.common.split.SplitWindowManager;
+import com.android.wm.shell.desktopmode.DesktopTasksController;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.recents.RecentTasksController;
import com.android.wm.shell.shared.TransactionPool;
@@ -222,6 +223,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
private final Optional<RecentTasksController> mRecentTasks;
private final LaunchAdjacentController mLaunchAdjacentController;
private final Optional<WindowDecorViewModel> mWindowDecorViewModel;
+ private final Optional<DesktopTasksController> mDesktopTasksController;
/** Singleton source of truth for the current state of split screen on this device. */
private final SplitState mSplitState;
@@ -358,7 +360,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
ShellExecutor bgExecutor,
Optional<RecentTasksController> recentTasks,
LaunchAdjacentController launchAdjacentController,
- Optional<WindowDecorViewModel> windowDecorViewModel, SplitState splitState) {
+ Optional<WindowDecorViewModel> windowDecorViewModel, SplitState splitState,
+ Optional<DesktopTasksController> desktopTasksController) {
mContext = context;
mDisplayId = displayId;
mSyncQueue = syncQueue;
@@ -371,6 +374,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mLaunchAdjacentController = launchAdjacentController;
mWindowDecorViewModel = windowDecorViewModel;
mSplitState = splitState;
+ mDesktopTasksController = desktopTasksController;
taskOrganizer.createRootTask(displayId, WINDOWING_MODE_FULLSCREEN, this /* listener */);
@@ -443,7 +447,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
ShellExecutor bgExecutor,
Optional<RecentTasksController> recentTasks,
LaunchAdjacentController launchAdjacentController,
- Optional<WindowDecorViewModel> windowDecorViewModel, SplitState splitState) {
+ Optional<WindowDecorViewModel> windowDecorViewModel, SplitState splitState,
+ Optional<DesktopTasksController> desktopTasksController) {
mContext = context;
mDisplayId = displayId;
mSyncQueue = syncQueue;
@@ -465,6 +470,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mLaunchAdjacentController = launchAdjacentController;
mWindowDecorViewModel = windowDecorViewModel;
mSplitState = splitState;
+ mDesktopTasksController = desktopTasksController;
mDisplayController.addDisplayWindowListener(this);
transitions.addHandler(this);
@@ -2768,11 +2774,17 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
final @WindowManager.TransitionType int type = request.getType();
final boolean isOpening = isOpeningType(type);
final boolean inFullscreen = triggerTask.getWindowingMode() == WINDOWING_MODE_FULLSCREEN;
- final boolean inDesktopMode = DesktopModeStatus.canEnterDesktopMode(mContext)
- && triggerTask.getWindowingMode() == WINDOWING_MODE_FREEFORM;
+ final boolean inDesktopMode = mDesktopTasksController.isPresent()
+ && mDesktopTasksController.get().isDesktopModeShowing(mDisplayId);
+ final boolean isLaunchingDesktopTask = isOpening && DesktopModeStatus.canEnterDesktopMode(
+ mContext) && triggerTask.getWindowingMode() == WINDOWING_MODE_FREEFORM;
final StageTaskListener stage = getStageOfTask(triggerTask);
- if (isOpening && inFullscreen) {
+ if (inDesktopMode || isLaunchingDesktopTask) {
+ // Don't handle request when desktop mode is showing (since they don't coexist), or
+ // when launching a desktop task (defer to DesktopTasksController)
+ return null;
+ } else if (isOpening && inFullscreen) {
// One task is opening into fullscreen mode, remove the corresponding split record.
mRecentTasks.ifPresent(recentTasks -> recentTasks.removeSplitPair(triggerTask.taskId));
logExit(EXIT_REASON_FULLSCREEN_REQUEST);
@@ -2824,12 +2836,6 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mSplitTransitions.setDismissTransition(transition, stageType,
EXIT_REASON_FULLSCREEN_REQUEST);
}
- } else if (isOpening && inDesktopMode) {
- // If the app being opened is in Desktop mode, set it to full screen and dismiss
- // split screen stage.
- prepareExitSplitScreen(STAGE_TYPE_UNDEFINED, out);
- out.setWindowingMode(triggerTask.token, WINDOWING_MODE_UNDEFINED)
- .setBounds(triggerTask.token, null);
} else if (isOpening && inFullscreen) {
final int activityType = triggerTask.getActivityType();
if (activityType == ACTIVITY_TYPE_HOME || activityType == ACTIVITY_TYPE_RECENTS) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvStageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvStageCoordinator.java
index a318bcf97e6e..9d85bea421ae 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvStageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvStageCoordinator.java
@@ -59,7 +59,7 @@ public class TvStageCoordinator extends StageCoordinator
super(context, displayId, syncQueue, taskOrganizer, displayController, displayImeController,
displayInsetsController, transitions, transactionPool, iconProvider,
mainExecutor, mainHandler, bgExecutor, recentTasks, launchAdjacentController,
- Optional.empty(), splitState);
+ Optional.empty(), splitState, Optional.empty());
mTvSplitMenuController = new TvSplitMenuController(context, this,
systemWindows, mainHandler);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/AppCompatUtilsTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/AppCompatUtilsTest.kt
index d52fd4fdf6c7..3ab7b3418e02 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/AppCompatUtilsTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/AppCompatUtilsTest.kt
@@ -20,7 +20,6 @@ import android.content.ComponentName
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.internal.R
-import com.android.wm.shell.ShellTestCase
import com.android.wm.shell.desktopmode.DesktopTestHelpers.createFreeformTask
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
@@ -35,7 +34,7 @@ import org.junit.runner.RunWith
*/
@RunWith(AndroidTestingRunner::class)
@SmallTest
-class AppCompatUtilsTest : ShellTestCase() {
+class AppCompatUtilsTest : CompatUIShellTestCase() {
@Test
fun testIsTopActivityExemptFromDesktopWindowing_onlyTransparentActivitiesInStack() {
assertTrue(isTopActivityExemptFromDesktopWindowing(mContext,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java
index 67573dabf2ec..ecf766db0be3 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java
@@ -39,9 +39,6 @@ import android.content.res.Configuration;
import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.RequiresFlagsDisabled;
-import android.platform.test.flag.junit.CheckFlagsRule;
-import android.platform.test.flag.junit.DeviceFlagsValueProvider;
-import android.platform.test.flag.junit.SetFlagsRule;
import android.testing.AndroidTestingRunner;
import android.view.InsetsSource;
import android.view.InsetsState;
@@ -52,7 +49,6 @@ import androidx.test.filters.SmallTest;
import com.android.window.flags.Flags;
import com.android.wm.shell.ShellTaskOrganizer;
-import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayImeController;
import com.android.wm.shell.common.DisplayInsetsController;
@@ -70,11 +66,8 @@ import com.android.wm.shell.transition.Transitions;
import dagger.Lazy;
-import java.util.Optional;
-
import org.junit.Assert;
import org.junit.Before;
-import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
@@ -82,25 +75,20 @@ import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.util.Optional;
+
/**
* Tests for {@link CompatUIController}.
*
* Build/Install/Run:
- * atest WMShellUnitTests:CompatUIControllerTest
+ * atest WMShellUnitTests:CompatUIControllerTest
*/
@RunWith(AndroidTestingRunner.class)
@SmallTest
-public class CompatUIControllerTest extends ShellTestCase {
+public class CompatUIControllerTest extends CompatUIShellTestCase {
private static final int DISPLAY_ID = 0;
private static final int TASK_ID = 12;
- @Rule
- public final CheckFlagsRule mCheckFlagsRule =
- DeviceFlagsValueProvider.createCheckFlagsRule();
-
- @Rule
- public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
-
private CompatUIController mController;
private ShellInit mShellInit;
@Mock
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUILayoutTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUILayoutTest.java
index e5d1919decf4..2117b062bf57 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUILayoutTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUILayoutTest.java
@@ -26,8 +26,6 @@ import android.app.ActivityManager;
import android.app.TaskInfo;
import android.graphics.Rect;
import android.platform.test.annotations.RequiresFlagsDisabled;
-import android.platform.test.flag.junit.CheckFlagsRule;
-import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.testing.AndroidTestingRunner;
import android.util.Pair;
import android.view.LayoutInflater;
@@ -40,7 +38,6 @@ import androidx.test.filters.SmallTest;
import com.android.window.flags.Flags;
import com.android.wm.shell.R;
import com.android.wm.shell.ShellTaskOrganizer;
-import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.compatui.CompatUIController.CompatUIHintsState;
@@ -49,7 +46,6 @@ import com.android.wm.shell.compatui.api.CompatUIEvent;
import junit.framework.Assert;
import org.junit.Before;
-import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
@@ -66,14 +62,10 @@ import java.util.function.Consumer;
*/
@RunWith(AndroidTestingRunner.class)
@SmallTest
-public class CompatUILayoutTest extends ShellTestCase {
+public class CompatUILayoutTest extends CompatUIShellTestCase {
private static final int TASK_ID = 1;
- @Rule
- public final CheckFlagsRule mCheckFlagsRule =
- DeviceFlagsValueProvider.createCheckFlagsRule();
-
@Mock private SyncTransactionQueue mSyncTransactionQueue;
@Mock private Consumer<CompatUIEvent> mCallback;
@Mock private Consumer<Pair<TaskInfo, ShellTaskOrganizer.TaskListener>> mOnRestartButtonClicked;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIShellTestCase.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIShellTestCase.java
new file mode 100644
index 000000000000..5a49d01f2991
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIShellTestCase.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2024 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.compatui;
+
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+import android.platform.test.flag.junit.SetFlagsRule;
+
+import com.android.wm.shell.ShellTestCase;
+
+import org.junit.Rule;
+
+/**
+ * Base class for CompatUI tests.
+ */
+public class CompatUIShellTestCase extends ShellTestCase {
+
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule =
+ DeviceFlagsValueProvider.createCheckFlagsRule();
+
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIStatusManagerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIStatusManagerTest.java
index 8fd7c0ec3099..0b37648faeec 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIStatusManagerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIStatusManagerTest.java
@@ -27,8 +27,6 @@ import android.testing.AndroidTestingRunner;
import androidx.test.filters.SmallTest;
-import com.android.wm.shell.ShellTestCase;
-
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -44,7 +42,7 @@ import java.util.function.IntSupplier;
*/
@RunWith(AndroidTestingRunner.class)
@SmallTest
-public class CompatUIStatusManagerTest extends ShellTestCase {
+public class CompatUIStatusManagerTest extends CompatUIShellTestCase {
private FakeCompatUIStatusManagerTest mTestState;
private CompatUIStatusManager mStatusManager;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java
index 1c0175603df9..61b6d803c8be 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java
@@ -16,7 +16,6 @@
package com.android.wm.shell.compatui;
-import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT;
import static android.view.WindowInsets.Type.navigationBars;
import static android.view.WindowManager.LARGE_SCREEN_SMALLEST_SCREEN_WIDTH_DP;
@@ -40,9 +39,6 @@ import android.app.TaskInfo;
import android.content.res.Configuration;
import android.graphics.Rect;
import android.platform.test.annotations.RequiresFlagsDisabled;
-import android.platform.test.flag.junit.CheckFlagsRule;
-import android.platform.test.flag.junit.DeviceFlagsValueProvider;
-import android.platform.test.flag.junit.SetFlagsRule;
import android.testing.AndroidTestingRunner;
import android.util.Pair;
import android.view.DisplayInfo;
@@ -56,7 +52,6 @@ import androidx.test.filters.SmallTest;
import com.android.window.flags.Flags;
import com.android.wm.shell.ShellTaskOrganizer;
-import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.compatui.CompatUIController.CompatUIHintsState;
@@ -65,7 +60,6 @@ import com.android.wm.shell.compatui.api.CompatUIEvent;
import junit.framework.Assert;
import org.junit.Before;
-import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
@@ -82,13 +76,7 @@ import java.util.function.Consumer;
*/
@RunWith(AndroidTestingRunner.class)
@SmallTest
-public class CompatUIWindowManagerTest extends ShellTestCase {
- @Rule
- public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT);
-
- @Rule
- public final CheckFlagsRule mCheckFlagsRule =
- DeviceFlagsValueProvider.createCheckFlagsRule();
+public class CompatUIWindowManagerTest extends CompatUIShellTestCase {
private static final int TASK_ID = 1;
private static final int TASK_WIDTH = 2000;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/LetterboxEduDialogLayoutTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/LetterboxEduDialogLayoutTest.java
index e8191db13084..e786fef1855c 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/LetterboxEduDialogLayoutTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/LetterboxEduDialogLayoutTest.java
@@ -25,8 +25,6 @@ import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import android.platform.test.annotations.RequiresFlagsDisabled;
-import android.platform.test.flag.junit.CheckFlagsRule;
-import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.testing.AndroidTestingRunner;
import android.view.LayoutInflater;
import android.view.View;
@@ -34,10 +32,8 @@ import android.view.View;
import androidx.test.filters.SmallTest;
import com.android.wm.shell.R;
-import com.android.wm.shell.ShellTestCase;
import org.junit.Before;
-import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -51,7 +47,7 @@ import org.mockito.MockitoAnnotations;
*/
@RunWith(AndroidTestingRunner.class)
@SmallTest
-public class LetterboxEduDialogLayoutTest extends ShellTestCase {
+public class LetterboxEduDialogLayoutTest extends CompatUIShellTestCase {
@Mock
private Runnable mDismissCallback;
@@ -60,10 +56,6 @@ public class LetterboxEduDialogLayoutTest extends ShellTestCase {
private View mDismissButton;
private View mDialogContainer;
- @Rule
- public final CheckFlagsRule mCheckFlagsRule =
- DeviceFlagsValueProvider.createCheckFlagsRule();
-
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/LetterboxEduWindowManagerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/LetterboxEduWindowManagerTest.java
index 4c97c76ae122..09fc082a63e3 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/LetterboxEduWindowManagerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/LetterboxEduWindowManagerTest.java
@@ -46,9 +46,6 @@ import android.graphics.Rect;
import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.RequiresFlagsDisabled;
-import android.platform.test.flag.junit.CheckFlagsRule;
-import android.platform.test.flag.junit.DeviceFlagsValueProvider;
-import android.platform.test.flag.junit.SetFlagsRule;
import android.testing.AndroidTestingRunner;
import android.util.Pair;
import android.view.DisplayCutout;
@@ -65,7 +62,6 @@ import androidx.test.filters.SmallTest;
import com.android.window.flags.Flags;
import com.android.wm.shell.R;
import com.android.wm.shell.ShellTaskOrganizer;
-import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.TestShellExecutor;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.DockStateReader;
@@ -75,7 +71,6 @@ import com.android.wm.shell.transition.Transitions;
import org.junit.After;
import org.junit.Before;
-import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
@@ -95,7 +90,7 @@ import java.util.function.Consumer;
*/
@RunWith(AndroidTestingRunner.class)
@SmallTest
-public class LetterboxEduWindowManagerTest extends ShellTestCase {
+public class LetterboxEduWindowManagerTest extends CompatUIShellTestCase {
private static final int USER_ID_1 = 1;
private static final int USER_ID_2 = 2;
@@ -128,18 +123,11 @@ public class LetterboxEduWindowManagerTest extends ShellTestCase {
@Mock private Consumer<Pair<TaskInfo, ShellTaskOrganizer.TaskListener>> mOnDismissCallback;
@Mock private DockStateReader mDockStateReader;
- @Rule
- public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
-
private CompatUIConfiguration mCompatUIConfiguration;
private TestShellExecutor mExecutor;
private FakeCompatUIStatusManagerTest mCompatUIStatus;
private CompatUIStatusManager mCompatUIStatusManager;
- @Rule
- public final CheckFlagsRule mCheckFlagsRule =
- DeviceFlagsValueProvider.createCheckFlagsRule();
-
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/ReachabilityEduLayoutTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/ReachabilityEduLayoutTest.java
index 0da14d673732..02c099b3cfb2 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/ReachabilityEduLayoutTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/ReachabilityEduLayoutTest.java
@@ -26,8 +26,6 @@ import static org.mockito.Mockito.verify;
import android.app.TaskInfo;
import android.platform.test.annotations.RequiresFlagsDisabled;
-import android.platform.test.flag.junit.CheckFlagsRule;
-import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.LayoutInflater;
@@ -36,10 +34,8 @@ import android.view.View;
import androidx.test.filters.SmallTest;
import com.android.wm.shell.R;
-import com.android.wm.shell.ShellTestCase;
import org.junit.Before;
-import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -54,7 +50,7 @@ import org.mockito.MockitoAnnotations;
@RunWith(AndroidTestingRunner.class)
@SmallTest
@TestableLooper.RunWithLooper(setAsMainLooper = true)
-public class ReachabilityEduLayoutTest extends ShellTestCase {
+public class ReachabilityEduLayoutTest extends CompatUIShellTestCase {
private ReachabilityEduLayout mLayout;
private View mMoveUpButton;
@@ -68,10 +64,6 @@ public class ReachabilityEduLayoutTest extends ShellTestCase {
@Mock
private TaskInfo mTaskInfo;
- @Rule
- public final CheckFlagsRule mCheckFlagsRule =
- DeviceFlagsValueProvider.createCheckFlagsRule();
-
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/ReachabilityEduWindowManagerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/ReachabilityEduWindowManagerTest.java
index eafb41470cda..fa04e070250e 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/ReachabilityEduWindowManagerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/ReachabilityEduWindowManagerTest.java
@@ -25,14 +25,11 @@ import android.app.ActivityManager;
import android.app.TaskInfo;
import android.content.res.Configuration;
import android.platform.test.annotations.RequiresFlagsDisabled;
-import android.platform.test.flag.junit.CheckFlagsRule;
-import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.testing.AndroidTestingRunner;
import androidx.test.filters.SmallTest;
import com.android.wm.shell.ShellTaskOrganizer;
-import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.TestShellExecutor;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.SyncTransactionQueue;
@@ -40,7 +37,6 @@ import com.android.wm.shell.common.SyncTransactionQueue;
import junit.framework.Assert;
import org.junit.Before;
-import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -56,7 +52,7 @@ import java.util.function.BiConsumer;
*/
@RunWith(AndroidTestingRunner.class)
@SmallTest
-public class ReachabilityEduWindowManagerTest extends ShellTestCase {
+public class ReachabilityEduWindowManagerTest extends CompatUIShellTestCase {
@Mock
private SyncTransactionQueue mSyncTransactionQueue;
@Mock
@@ -71,10 +67,6 @@ public class ReachabilityEduWindowManagerTest extends ShellTestCase {
private TaskInfo mTaskInfo;
private ReachabilityEduWindowManager mWindowManager;
- @Rule
- public final CheckFlagsRule mCheckFlagsRule =
- DeviceFlagsValueProvider.createCheckFlagsRule();
-
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/RestartDialogLayoutTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/RestartDialogLayoutTest.java
index 6b0c5dd2e1c7..2cded9d9776c 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/RestartDialogLayoutTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/RestartDialogLayoutTest.java
@@ -26,8 +26,6 @@ import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import android.platform.test.annotations.RequiresFlagsDisabled;
-import android.platform.test.flag.junit.CheckFlagsRule;
-import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.testing.AndroidTestingRunner;
import android.view.LayoutInflater;
import android.view.View;
@@ -36,10 +34,8 @@ import android.widget.CheckBox;
import androidx.test.filters.SmallTest;
import com.android.wm.shell.R;
-import com.android.wm.shell.ShellTestCase;
import org.junit.Before;
-import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -55,7 +51,7 @@ import java.util.function.Consumer;
*/
@RunWith(AndroidTestingRunner.class)
@SmallTest
-public class RestartDialogLayoutTest extends ShellTestCase {
+public class RestartDialogLayoutTest extends CompatUIShellTestCase {
@Mock private Runnable mDismissCallback;
@Mock private Consumer<Boolean> mRestartCallback;
@@ -66,10 +62,6 @@ public class RestartDialogLayoutTest extends ShellTestCase {
private View mDialogContainer;
private CheckBox mDontRepeatCheckBox;
- @Rule
- public final CheckFlagsRule mCheckFlagsRule =
- DeviceFlagsValueProvider.createCheckFlagsRule();
-
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/RestartDialogWindowManagerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/RestartDialogWindowManagerTest.java
index cfeef90cb4b6..ebd0f412a0a1 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/RestartDialogWindowManagerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/RestartDialogWindowManagerTest.java
@@ -22,15 +22,12 @@ import android.app.ActivityManager;
import android.app.TaskInfo;
import android.content.res.Configuration;
import android.platform.test.annotations.RequiresFlagsDisabled;
-import android.platform.test.flag.junit.CheckFlagsRule;
-import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.testing.AndroidTestingRunner;
import android.util.Pair;
import androidx.test.filters.SmallTest;
import com.android.wm.shell.ShellTaskOrganizer;
-import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.transition.Transitions;
@@ -38,7 +35,6 @@ import com.android.wm.shell.transition.Transitions;
import junit.framework.Assert;
import org.junit.Before;
-import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -54,7 +50,7 @@ import java.util.function.Consumer;
*/
@RunWith(AndroidTestingRunner.class)
@SmallTest
-public class RestartDialogWindowManagerTest extends ShellTestCase {
+public class RestartDialogWindowManagerTest extends CompatUIShellTestCase {
@Mock
private SyncTransactionQueue mSyncTransactionQueue;
@@ -66,10 +62,6 @@ public class RestartDialogWindowManagerTest extends ShellTestCase {
private RestartDialogWindowManager mWindowManager;
private TaskInfo mTaskInfo;
- @Rule
- public final CheckFlagsRule mCheckFlagsRule =
- DeviceFlagsValueProvider.createCheckFlagsRule();
-
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsLayoutTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsLayoutTest.java
index e8e68bdbd940..c6532e13f3cc 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsLayoutTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsLayoutTest.java
@@ -27,8 +27,6 @@ import android.app.ActivityManager;
import android.app.TaskInfo;
import android.content.ComponentName;
import android.platform.test.annotations.RequiresFlagsDisabled;
-import android.platform.test.flag.junit.CheckFlagsRule;
-import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.testing.AndroidTestingRunner;
import android.util.Pair;
import android.view.LayoutInflater;
@@ -40,7 +38,6 @@ import androidx.test.filters.SmallTest;
import com.android.wm.shell.R;
import com.android.wm.shell.ShellTaskOrganizer;
-import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.TestShellExecutor;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.SyncTransactionQueue;
@@ -48,7 +45,6 @@ import com.android.wm.shell.common.SyncTransactionQueue;
import junit.framework.Assert;
import org.junit.Before;
-import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
@@ -66,7 +62,7 @@ import java.util.function.BiConsumer;
*/
@RunWith(AndroidTestingRunner.class)
@SmallTest
-public class UserAspectRatioSettingsLayoutTest extends ShellTestCase {
+public class UserAspectRatioSettingsLayoutTest extends CompatUIShellTestCase {
private static final int TASK_ID = 1;
@@ -88,10 +84,6 @@ public class UserAspectRatioSettingsLayoutTest extends ShellTestCase {
private UserAspectRatioSettingsLayout mLayout;
private TaskInfo mTaskInfo;
- @Rule
- public final CheckFlagsRule mCheckFlagsRule =
- DeviceFlagsValueProvider.createCheckFlagsRule();
-
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManagerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManagerTest.java
index 9f86d49b52c4..096e900199ba 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManagerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManagerTest.java
@@ -41,8 +41,6 @@ import android.content.Intent;
import android.content.res.Configuration;
import android.graphics.Rect;
import android.platform.test.annotations.RequiresFlagsDisabled;
-import android.platform.test.flag.junit.CheckFlagsRule;
-import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper.RunWithLooper;
import android.util.Pair;
@@ -56,7 +54,6 @@ import android.view.View;
import androidx.test.filters.SmallTest;
import com.android.wm.shell.ShellTaskOrganizer;
-import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.TestShellExecutor;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.SyncTransactionQueue;
@@ -65,7 +62,6 @@ import com.android.wm.shell.compatui.CompatUIController.CompatUIHintsState;
import junit.framework.Assert;
import org.junit.Before;
-import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
@@ -87,7 +83,7 @@ import java.util.function.Supplier;
@RunWith(AndroidTestingRunner.class)
@RunWithLooper
@SmallTest
-public class UserAspectRatioSettingsWindowManagerTest extends ShellTestCase {
+public class UserAspectRatioSettingsWindowManagerTest extends CompatUIShellTestCase {
private static final int TASK_ID = 1;
@@ -112,10 +108,6 @@ public class UserAspectRatioSettingsWindowManagerTest extends ShellTestCase {
private TestShellExecutor mExecutor;
- @Rule
- public final CheckFlagsRule mCheckFlagsRule =
- DeviceFlagsValueProvider.createCheckFlagsRule();
-
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/CloseDesktopTaskTransitionHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/CloseDesktopTaskTransitionHandlerTest.kt
index 9b4cc17e19d9..db00f41f723b 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/CloseDesktopTaskTransitionHandlerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/CloseDesktopTaskTransitionHandlerTest.kt
@@ -64,7 +64,7 @@ class CloseDesktopTaskTransitionHandlerTest : ShellTestCase() {
context,
testExecutor,
testExecutor,
- transactionSupplier
+ transactionSupplier,
)
}
@@ -81,11 +81,11 @@ class CloseDesktopTaskTransitionHandlerTest : ShellTestCase() {
info =
createTransitionInfo(
type = WindowManager.TRANSIT_OPEN,
- task = createTask(WINDOWING_MODE_FREEFORM)
+ task = createTask(WINDOWING_MODE_FREEFORM),
),
startTransaction = mock(),
finishTransaction = mock(),
- finishCallback = {}
+ finishCallback = {},
)
assertFalse("Should not animate open transition", animates)
@@ -99,7 +99,7 @@ class CloseDesktopTaskTransitionHandlerTest : ShellTestCase() {
info = createTransitionInfo(task = createTask(WINDOWING_MODE_FULLSCREEN)),
startTransaction = mock(),
finishTransaction = mock(),
- finishCallback = {}
+ finishCallback = {},
)
assertFalse("Should not animate fullscreen task close transition", animates)
@@ -113,11 +113,11 @@ class CloseDesktopTaskTransitionHandlerTest : ShellTestCase() {
info =
createTransitionInfo(
changeMode = WindowManager.TRANSIT_OPEN,
- task = createTask(WINDOWING_MODE_FREEFORM)
+ task = createTask(WINDOWING_MODE_FREEFORM),
),
startTransaction = mock(),
finishTransaction = mock(),
- finishCallback = {}
+ finishCallback = {},
)
assertFalse("Should not animate opening freeform task close transition", animates)
@@ -131,7 +131,7 @@ class CloseDesktopTaskTransitionHandlerTest : ShellTestCase() {
info = createTransitionInfo(task = createTask(WINDOWING_MODE_FREEFORM)),
startTransaction = mock(),
finishTransaction = mock(),
- finishCallback = {}
+ finishCallback = {},
)
assertTrue("Should animate closing freeform task close transition", animates)
@@ -140,7 +140,7 @@ class CloseDesktopTaskTransitionHandlerTest : ShellTestCase() {
private fun createTransitionInfo(
type: Int = WindowManager.TRANSIT_CLOSE,
changeMode: Int = WindowManager.TRANSIT_CLOSE,
- task: RunningTaskInfo
+ task: RunningTaskInfo,
): TransitionInfo =
TransitionInfo(type, 0 /* flags */).apply {
addChange(
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopActivityOrientationChangeHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopActivityOrientationChangeHandlerTest.kt
index 4cc641cd1d81..ecad5217b87f 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopActivityOrientationChangeHandlerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopActivityOrientationChangeHandlerTest.kt
@@ -36,7 +36,6 @@ import com.android.dx.mockito.inline.extended.ExtendedMockito.never
import com.android.dx.mockito.inline.extended.StaticMockitoSession
import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE
import com.android.window.flags.Flags.FLAG_RESPECT_ORIENTATION_CHANGE_FOR_UNRESIZEABLE
-import com.android.wm.shell.sysui.ShellController
import com.android.wm.shell.ShellTaskOrganizer
import com.android.wm.shell.ShellTestCase
import com.android.wm.shell.common.ShellExecutor
@@ -47,6 +46,7 @@ import com.android.wm.shell.desktopmode.persistence.Desktop
import com.android.wm.shell.desktopmode.persistence.DesktopPersistentRepository
import com.android.wm.shell.desktopmode.persistence.DesktopRepositoryInitializer
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
+import com.android.wm.shell.sysui.ShellController
import com.android.wm.shell.sysui.ShellInit
import com.android.wm.shell.transition.Transitions
import junit.framework.Assert.assertEquals
@@ -129,16 +129,22 @@ class DesktopActivityOrientationChangeHandlerTest : ShellTestCase() {
persistentRepository,
repositoryInitializer,
testScope,
- userManager
+ userManager,
)
whenever(shellTaskOrganizer.getRunningTasks(anyInt())).thenAnswer { runningTasks }
whenever(transitions.startTransition(anyInt(), any(), isNull())).thenAnswer { Binder() }
- whenever(runBlocking { persistentRepository.readDesktop(any(), any()) }).thenReturn(
- Desktop.getDefaultInstance()
- )
+ whenever(runBlocking { persistentRepository.readDesktop(any(), any()) })
+ .thenReturn(Desktop.getDefaultInstance())
- handler = DesktopActivityOrientationChangeHandler(context, shellInit, shellTaskOrganizer,
- taskStackListener, resizeTransitionHandler, userRepositories)
+ handler =
+ DesktopActivityOrientationChangeHandler(
+ context,
+ shellInit,
+ shellTaskOrganizer,
+ taskStackListener,
+ resizeTransitionHandler,
+ userRepositories,
+ )
shellInit.init()
}
@@ -161,19 +167,28 @@ class DesktopActivityOrientationChangeHandlerTest : ShellTestCase() {
whenever(DesktopModeStatus.canEnterDesktopMode(context)).thenReturn(false)
clearInvocations(shellInit)
- handler = DesktopActivityOrientationChangeHandler(context, shellInit, shellTaskOrganizer,
- taskStackListener, resizeTransitionHandler, userRepositories)
+ handler =
+ DesktopActivityOrientationChangeHandler(
+ context,
+ shellInit,
+ shellTaskOrganizer,
+ taskStackListener,
+ resizeTransitionHandler,
+ userRepositories,
+ )
- verify(shellInit, never()).addInitCallback(any(),
- any<DesktopActivityOrientationChangeHandler>())
+ verify(shellInit, never())
+ .addInitCallback(any(), any<DesktopActivityOrientationChangeHandler>())
}
@Test
fun handleActivityOrientationChange_resizeable_doNothing() {
val task = setUpFreeformTask()
- taskStackListener.onActivityRequestedOrientationChanged(task.taskId,
- SCREEN_ORIENTATION_LANDSCAPE)
+ taskStackListener.onActivityRequestedOrientationChanged(
+ task.taskId,
+ SCREEN_ORIENTATION_LANDSCAPE,
+ )
verify(resizeTransitionHandler, never()).startTransition(any(), any())
}
@@ -189,8 +204,10 @@ class DesktopActivityOrientationChangeHandlerTest : ShellTestCase() {
userRepositories.current.addTask(DEFAULT_DISPLAY, task.taskId, isVisible = true)
runningTasks.add(task)
- taskStackListener.onActivityRequestedOrientationChanged(task.taskId,
- SCREEN_ORIENTATION_LANDSCAPE)
+ taskStackListener.onActivityRequestedOrientationChanged(
+ task.taskId,
+ SCREEN_ORIENTATION_LANDSCAPE,
+ )
verify(resizeTransitionHandler, never()).startTransition(any(), any())
}
@@ -198,8 +215,11 @@ class DesktopActivityOrientationChangeHandlerTest : ShellTestCase() {
@Test
fun handleActivityOrientationChange_nonResizeablePortrait_requestSameOrientation_doNothing() {
val task = setUpFreeformTask(isResizeable = false)
- val newTask = setUpFreeformTask(isResizeable = false,
- orientation = SCREEN_ORIENTATION_SENSOR_PORTRAIT)
+ val newTask =
+ setUpFreeformTask(
+ isResizeable = false,
+ orientation = SCREEN_ORIENTATION_SENSOR_PORTRAIT,
+ )
handler.handleActivityOrientationChange(task, newTask)
@@ -211,8 +231,10 @@ class DesktopActivityOrientationChangeHandlerTest : ShellTestCase() {
val task = setUpFreeformTask(isResizeable = false)
userRepositories.current.updateTask(task.displayId, task.taskId, isVisible = false)
- taskStackListener.onActivityRequestedOrientationChanged(task.taskId,
- SCREEN_ORIENTATION_LANDSCAPE)
+ taskStackListener.onActivityRequestedOrientationChanged(
+ task.taskId,
+ SCREEN_ORIENTATION_LANDSCAPE,
+ )
verify(resizeTransitionHandler, never()).startTransition(any(), any())
}
@@ -221,8 +243,8 @@ class DesktopActivityOrientationChangeHandlerTest : ShellTestCase() {
fun handleActivityOrientationChange_nonResizeablePortrait_respectLandscapeRequest() {
val task = setUpFreeformTask(isResizeable = false)
val oldBounds = task.configuration.windowConfiguration.bounds
- val newTask = setUpFreeformTask(isResizeable = false,
- orientation = SCREEN_ORIENTATION_LANDSCAPE)
+ val newTask =
+ setUpFreeformTask(isResizeable = false, orientation = SCREEN_ORIENTATION_LANDSCAPE)
handler.handleActivityOrientationChange(task, newTask)
@@ -242,9 +264,12 @@ class DesktopActivityOrientationChangeHandlerTest : ShellTestCase() {
@Test
fun handleActivityOrientationChange_nonResizeableLandscape_respectPortraitRequest() {
val oldBounds = Rect(0, 0, 500, 200)
- val task = setUpFreeformTask(
- isResizeable = false, orientation = SCREEN_ORIENTATION_LANDSCAPE, bounds = oldBounds
- )
+ val task =
+ setUpFreeformTask(
+ isResizeable = false,
+ orientation = SCREEN_ORIENTATION_LANDSCAPE,
+ bounds = oldBounds,
+ )
val newTask = setUpFreeformTask(isResizeable = false, bounds = oldBounds)
handler.handleActivityOrientationChange(task, newTask)
@@ -266,7 +291,7 @@ class DesktopActivityOrientationChangeHandlerTest : ShellTestCase() {
displayId: Int = DEFAULT_DISPLAY,
isResizeable: Boolean = true,
orientation: Int = SCREEN_ORIENTATION_PORTRAIT,
- bounds: Rect? = Rect(0, 0, 200, 500)
+ bounds: Rect? = Rect(0, 0, 200, 500),
): RunningTaskInfo {
val task = createFreeformTask(displayId, bounds)
val activityInfo = ActivityInfo()
@@ -291,4 +316,4 @@ class DesktopActivityOrientationChangeHandlerTest : ShellTestCase() {
private fun findBoundsChange(wct: WindowContainerTransaction, task: RunningTaskInfo): Rect? =
wct.changes[task.token.asBinder()]?.configuration?.windowConfiguration?.bounds
-} \ No newline at end of file
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopBackNavigationTransitionHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopBackNavigationTransitionHandlerTest.kt
index 6df8d6fd7717..d14c6402982d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopBackNavigationTransitionHandlerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopBackNavigationTransitionHandlerTest.kt
@@ -56,11 +56,7 @@ class DesktopBackNavigationTransitionHandlerTest : ShellTestCase() {
@Before
fun setUp() {
handler =
- DesktopBackNavigationTransitionHandler(
- testExecutor,
- testExecutor,
- displayController
- )
+ DesktopBackNavigationTransitionHandler(testExecutor, testExecutor, displayController)
whenever(displayController.getDisplayContext(any())).thenReturn(mContext)
}
@@ -75,13 +71,13 @@ class DesktopBackNavigationTransitionHandlerTest : ShellTestCase() {
handler.startAnimation(
transition = mock(),
info =
- createTransitionInfo(
- type = WindowManager.TRANSIT_OPEN,
- task = createTask(WINDOWING_MODE_FREEFORM)
- ),
+ createTransitionInfo(
+ type = WindowManager.TRANSIT_OPEN,
+ task = createTask(WINDOWING_MODE_FREEFORM),
+ ),
startTransaction = mock(),
finishTransaction = mock(),
- finishCallback = {}
+ finishCallback = {},
)
assertFalse("Should not animate open transition", animates)
@@ -95,7 +91,7 @@ class DesktopBackNavigationTransitionHandlerTest : ShellTestCase() {
info = createTransitionInfo(task = createTask(WINDOWING_MODE_FULLSCREEN)),
startTransaction = mock(),
finishTransaction = mock(),
- finishCallback = {}
+ finishCallback = {},
)
assertFalse("Should not animate fullscreen task to back transition", animates)
@@ -107,13 +103,13 @@ class DesktopBackNavigationTransitionHandlerTest : ShellTestCase() {
handler.startAnimation(
transition = mock(),
info =
- createTransitionInfo(
- changeMode = WindowManager.TRANSIT_OPEN,
- task = createTask(WINDOWING_MODE_FREEFORM)
- ),
+ createTransitionInfo(
+ changeMode = WindowManager.TRANSIT_OPEN,
+ task = createTask(WINDOWING_MODE_FREEFORM),
+ ),
startTransaction = mock(),
finishTransaction = mock(),
- finishCallback = {}
+ finishCallback = {},
)
assertFalse("Should not animate opening freeform task to back transition", animates)
@@ -127,7 +123,7 @@ class DesktopBackNavigationTransitionHandlerTest : ShellTestCase() {
info = createTransitionInfo(task = createTask(WINDOWING_MODE_FREEFORM)),
startTransaction = mock(),
finishTransaction = mock(),
- finishCallback = {}
+ finishCallback = {},
)
assertTrue("Should animate going to back freeform task close transition", animates)
@@ -138,22 +134,24 @@ class DesktopBackNavigationTransitionHandlerTest : ShellTestCase() {
val animates =
handler.startAnimation(
transition = mock(),
- info = createTransitionInfo(
- type = TRANSIT_CLOSE,
- changeMode = TRANSIT_CLOSE,
- task = createTask(WINDOWING_MODE_FREEFORM)
- ),
+ info =
+ createTransitionInfo(
+ type = TRANSIT_CLOSE,
+ changeMode = TRANSIT_CLOSE,
+ task = createTask(WINDOWING_MODE_FREEFORM),
+ ),
startTransaction = mock(),
finishTransaction = mock(),
- finishCallback = {}
+ finishCallback = {},
)
assertTrue("Should animate going to back freeform task close transition", animates)
}
+
private fun createTransitionInfo(
type: Int = WindowManager.TRANSIT_TO_BACK,
changeMode: Int = WindowManager.TRANSIT_TO_BACK,
- task: RunningTaskInfo
+ task: RunningTaskInfo,
): TransitionInfo =
TransitionInfo(type, 0 /* flags */).apply {
addChange(
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 fea82365c1a0..6a3717427e93 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
@@ -63,109 +63,115 @@ import org.mockito.kotlin.whenever
@RunWith(AndroidTestingRunner::class)
class DesktopDisplayEventHandlerTest : ShellTestCase() {
- @Mock lateinit var testExecutor: ShellExecutor
- @Mock lateinit var transitions: Transitions
- @Mock lateinit var displayController: DisplayController
- @Mock lateinit var rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer
- @Mock private lateinit var mockWindowManager: IWindowManager
+ @Mock lateinit var testExecutor: ShellExecutor
+ @Mock lateinit var transitions: Transitions
+ @Mock lateinit var displayController: DisplayController
+ @Mock lateinit var rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer
+ @Mock private lateinit var mockWindowManager: IWindowManager
- private lateinit var shellInit: ShellInit
- private lateinit var handler: DesktopDisplayEventHandler
+ private lateinit var shellInit: ShellInit
+ private lateinit var handler: DesktopDisplayEventHandler
- @Before
- fun setUp() {
- shellInit = spy(ShellInit(testExecutor))
- whenever(transitions.startTransition(anyInt(), any(), isNull())).thenAnswer { Binder() }
- val tda = DisplayAreaInfo(MockToken().token(), DEFAULT_DISPLAY, 0)
- whenever(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)).thenReturn(tda)
- handler =
- DesktopDisplayEventHandler(
- context,
- shellInit,
- transitions,
- displayController,
- rootTaskDisplayAreaOrganizer,
- mockWindowManager,
- )
- shellInit.init()
- }
+ @Before
+ fun setUp() {
+ shellInit = spy(ShellInit(testExecutor))
+ whenever(transitions.startTransition(anyInt(), any(), isNull())).thenAnswer { Binder() }
+ val tda = DisplayAreaInfo(MockToken().token(), DEFAULT_DISPLAY, 0)
+ whenever(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)).thenReturn(tda)
+ handler =
+ DesktopDisplayEventHandler(
+ context,
+ shellInit,
+ transitions,
+ displayController,
+ rootTaskDisplayAreaOrganizer,
+ mockWindowManager,
+ )
+ shellInit.init()
+ }
- private fun testDisplayWindowingModeSwitch(
- defaultWindowingMode: Int,
- extendedDisplayEnabled: Boolean,
- expectTransition: Boolean
- ) {
- val externalDisplayId = 100
- val captor = ArgumentCaptor.forClass(OnDisplaysChangedListener::class.java)
- verify(displayController).addDisplayWindowListener(captor.capture())
- val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!!
- tda.configuration.windowConfiguration.windowingMode = defaultWindowingMode
- whenever(mockWindowManager.getWindowingMode(anyInt())).thenAnswer { defaultWindowingMode }
- val settingsSession = ExtendedDisplaySettingsSession(
- context.contentResolver, if (extendedDisplayEnabled) 1 else 0)
+ private fun testDisplayWindowingModeSwitch(
+ defaultWindowingMode: Int,
+ extendedDisplayEnabled: Boolean,
+ expectTransition: Boolean,
+ ) {
+ val externalDisplayId = 100
+ val captor = ArgumentCaptor.forClass(OnDisplaysChangedListener::class.java)
+ verify(displayController).addDisplayWindowListener(captor.capture())
+ val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!!
+ tda.configuration.windowConfiguration.windowingMode = defaultWindowingMode
+ whenever(mockWindowManager.getWindowingMode(anyInt())).thenAnswer { defaultWindowingMode }
+ val settingsSession =
+ ExtendedDisplaySettingsSession(
+ context.contentResolver,
+ if (extendedDisplayEnabled) 1 else 0,
+ )
- settingsSession.use {
- // The external display connected.
- whenever(rootTaskDisplayAreaOrganizer.getDisplayIds())
- .thenReturn(intArrayOf(DEFAULT_DISPLAY, externalDisplayId))
- captor.value.onDisplayAdded(externalDisplayId)
- tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FREEFORM
- // The external display disconnected.
- whenever(rootTaskDisplayAreaOrganizer.getDisplayIds())
- .thenReturn(intArrayOf(DEFAULT_DISPLAY))
- captor.value.onDisplayRemoved(externalDisplayId)
+ settingsSession.use {
+ // The external display connected.
+ whenever(rootTaskDisplayAreaOrganizer.getDisplayIds())
+ .thenReturn(intArrayOf(DEFAULT_DISPLAY, externalDisplayId))
+ captor.value.onDisplayAdded(externalDisplayId)
+ tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FREEFORM
+ // The external display disconnected.
+ whenever(rootTaskDisplayAreaOrganizer.getDisplayIds())
+ .thenReturn(intArrayOf(DEFAULT_DISPLAY))
+ captor.value.onDisplayRemoved(externalDisplayId)
- if (expectTransition) {
- val arg = argumentCaptor<WindowContainerTransaction>()
- verify(transitions, times(2)).startTransition(eq(TRANSIT_CHANGE), arg.capture(), isNull())
- assertThat(arg.firstValue.changes[tda.token.asBinder()]?.windowingMode)
- .isEqualTo(WINDOWING_MODE_FREEFORM)
- assertThat(arg.secondValue.changes[tda.token.asBinder()]?.windowingMode)
- .isEqualTo(defaultWindowingMode)
- } else {
- verify(transitions, never()).startTransition(eq(TRANSIT_CHANGE), any(), isNull())
- }
+ if (expectTransition) {
+ val arg = argumentCaptor<WindowContainerTransaction>()
+ verify(transitions, times(2))
+ .startTransition(eq(TRANSIT_CHANGE), arg.capture(), isNull())
+ assertThat(arg.firstValue.changes[tda.token.asBinder()]?.windowingMode)
+ .isEqualTo(WINDOWING_MODE_FREEFORM)
+ assertThat(arg.secondValue.changes[tda.token.asBinder()]?.windowingMode)
+ .isEqualTo(defaultWindowingMode)
+ } else {
+ verify(transitions, never()).startTransition(eq(TRANSIT_CHANGE), any(), isNull())
+ }
+ }
}
- }
- @Test
- fun displayWindowingModeSwitchOnDisplayConnected_extendedDisplayDisabled() {
- testDisplayWindowingModeSwitch(
- defaultWindowingMode = WINDOWING_MODE_FULLSCREEN,
- extendedDisplayEnabled = false,
- expectTransition = false
- )
- }
+ @Test
+ fun displayWindowingModeSwitchOnDisplayConnected_extendedDisplayDisabled() {
+ testDisplayWindowingModeSwitch(
+ defaultWindowingMode = WINDOWING_MODE_FULLSCREEN,
+ extendedDisplayEnabled = false,
+ expectTransition = false,
+ )
+ }
- @Test
- fun displayWindowingModeSwitchOnDisplayConnected_fullscreenDisplay() {
- testDisplayWindowingModeSwitch(
- defaultWindowingMode = WINDOWING_MODE_FULLSCREEN,
- extendedDisplayEnabled = true,
- expectTransition = true
- )
- }
+ @Test
+ fun displayWindowingModeSwitchOnDisplayConnected_fullscreenDisplay() {
+ testDisplayWindowingModeSwitch(
+ defaultWindowingMode = WINDOWING_MODE_FULLSCREEN,
+ extendedDisplayEnabled = true,
+ expectTransition = true,
+ )
+ }
- @Test
- fun displayWindowingModeSwitchOnDisplayConnected_freeformDisplay() {
- testDisplayWindowingModeSwitch(
- defaultWindowingMode = WINDOWING_MODE_FREEFORM,
- extendedDisplayEnabled = true,
- expectTransition = false
- )
- }
+ @Test
+ fun displayWindowingModeSwitchOnDisplayConnected_freeformDisplay() {
+ testDisplayWindowingModeSwitch(
+ defaultWindowingMode = WINDOWING_MODE_FREEFORM,
+ extendedDisplayEnabled = true,
+ expectTransition = false,
+ )
+ }
- private class ExtendedDisplaySettingsSession(
- private val contentResolver: ContentResolver,
- private val overrideValue: Int
- ) : AutoCloseable {
- private val settingName = DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS
- private val initialValue = Settings.Global.getInt(contentResolver, settingName, 0)
+ private class ExtendedDisplaySettingsSession(
+ private val contentResolver: ContentResolver,
+ private val overrideValue: Int,
+ ) : AutoCloseable {
+ private val settingName = DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS
+ private val initialValue = Settings.Global.getInt(contentResolver, settingName, 0)
- init { Settings.Global.putInt(contentResolver, settingName, overrideValue) }
+ init {
+ Settings.Global.putInt(contentResolver, settingName, overrideValue)
+ }
- override fun close() {
- Settings.Global.putInt(contentResolver, settingName, initialValue)
+ override fun close() {
+ Settings.Global.putInt(contentResolver, settingName, initialValue)
+ }
}
- }
-} \ No newline at end of file
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopImmersiveControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopImmersiveControllerTest.kt
index b87f20023796..47d133b974e6 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopImmersiveControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopImmersiveControllerTest.kt
@@ -88,9 +88,16 @@ class DesktopImmersiveControllerTest : ShellTestCase() {
@Before
fun setUp() {
- userRepositories = DesktopUserRepositories(
- context, ShellInit(TestShellExecutor()), mock(), mock(), mock(), mock(), mock()
- )
+ userRepositories =
+ DesktopUserRepositories(
+ context,
+ ShellInit(TestShellExecutor()),
+ mock(),
+ mock(),
+ mock(),
+ mock(),
+ mock(),
+ )
whenever(mockDisplayController.getDisplayLayout(DEFAULT_DISPLAY))
.thenReturn(mockDisplayLayout)
whenever(mockDisplayLayout.getStableBounds(any())).thenAnswer { invocation ->
@@ -98,15 +105,16 @@ class DesktopImmersiveControllerTest : ShellTestCase() {
}
whenever(mockDisplayLayout.width()).thenReturn(DISPLAY_BOUNDS.width())
whenever(mockDisplayLayout.height()).thenReturn(DISPLAY_BOUNDS.height())
- controller = DesktopImmersiveController(
- shellInit = mock(),
- transitions = mockTransitions,
- desktopUserRepositories = userRepositories,
- displayController = mockDisplayController,
- shellTaskOrganizer = mockShellTaskOrganizer,
- shellCommandHandler = mock(),
- transactionSupplier = transactionSupplier,
- )
+ controller =
+ DesktopImmersiveController(
+ shellInit = mock(),
+ transitions = mockTransitions,
+ desktopUserRepositories = userRepositories,
+ displayController = mockDisplayController,
+ shellTaskOrganizer = mockShellTaskOrganizer,
+ shellCommandHandler = mock(),
+ transactionSupplier = transactionSupplier,
+ )
desktopRepository = userRepositories.current
}
@@ -119,15 +127,13 @@ class DesktopImmersiveControllerTest : ShellTestCase() {
desktopRepository.setTaskInFullImmersiveState(
displayId = task.displayId,
taskId = task.taskId,
- immersive = false
+ immersive = false,
)
controller.moveTaskToImmersive(task)
controller.onTransitionReady(
transition = mockBinder,
- info = createTransitionInfo(
- changes = listOf(createChange(task))
- ),
+ info = createTransitionInfo(changes = listOf(createChange(task))),
startTransaction = SurfaceControl.Transaction(),
finishTransaction = SurfaceControl.Transaction(),
)
@@ -145,16 +151,14 @@ class DesktopImmersiveControllerTest : ShellTestCase() {
desktopRepository.setTaskInFullImmersiveState(
displayId = task.displayId,
taskId = task.taskId,
- immersive = false
+ immersive = false,
)
assertThat(desktopRepository.removeBoundsBeforeFullImmersive(task.taskId)).isNull()
controller.moveTaskToImmersive(task)
controller.onTransitionReady(
transition = mockBinder,
- info = createTransitionInfo(
- changes = listOf(createChange(task))
- ),
+ info = createTransitionInfo(changes = listOf(createChange(task))),
startTransaction = SurfaceControl.Transaction(),
finishTransaction = SurfaceControl.Transaction(),
)
@@ -171,15 +175,13 @@ class DesktopImmersiveControllerTest : ShellTestCase() {
desktopRepository.setTaskInFullImmersiveState(
displayId = task.displayId,
taskId = task.taskId,
- immersive = true
+ immersive = true,
)
controller.moveTaskToNonImmersive(task, USER_INTERACTION)
controller.onTransitionReady(
transition = mockBinder,
- info = createTransitionInfo(
- changes = listOf(createChange(task))
- ),
+ info = createTransitionInfo(changes = listOf(createChange(task))),
startTransaction = SurfaceControl.Transaction(),
finishTransaction = SurfaceControl.Transaction(),
)
@@ -197,16 +199,14 @@ class DesktopImmersiveControllerTest : ShellTestCase() {
desktopRepository.setTaskInFullImmersiveState(
displayId = task.displayId,
taskId = task.taskId,
- immersive = true
+ immersive = true,
)
desktopRepository.saveBoundsBeforeFullImmersive(task.taskId, Rect(100, 100, 600, 600))
controller.moveTaskToNonImmersive(task, USER_INTERACTION)
controller.onTransitionReady(
transition = mockBinder,
- info = createTransitionInfo(
- changes = listOf(createChange(task))
- ),
+ info = createTransitionInfo(changes = listOf(createChange(task))),
startTransaction = SurfaceControl.Transaction(),
finishTransaction = SurfaceControl.Transaction(),
)
@@ -220,16 +220,23 @@ class DesktopImmersiveControllerTest : ShellTestCase() {
desktopRepository.setTaskInFullImmersiveState(
displayId = task.displayId,
taskId = task.taskId,
- immersive = true
+ immersive = true,
)
controller.onTransitionReady(
transition = mock(IBinder::class.java),
- info = createTransitionInfo(
- changes = listOf(createChange(task).apply {
- setRotation(/* start= */ Surface.ROTATION_0, /* end= */ Surface.ROTATION_90)
- })
- ),
+ info =
+ createTransitionInfo(
+ changes =
+ listOf(
+ createChange(task).apply {
+ setRotation(
+ /* start= */ Surface.ROTATION_0,
+ /* end= */ Surface.ROTATION_90,
+ )
+ }
+ )
+ ),
startTransaction = SurfaceControl.Transaction(),
finishTransaction = SurfaceControl.Transaction(),
)
@@ -247,8 +254,7 @@ class DesktopImmersiveControllerTest : ShellTestCase() {
controller.moveTaskToImmersive(task)
controller.moveTaskToImmersive(task)
- verify(mockTransitions, times(1))
- .startTransition(eq(TRANSIT_CHANGE), any(), eq(controller))
+ verify(mockTransitions, times(1)).startTransition(eq(TRANSIT_CHANGE), any(), eq(controller))
}
@Test
@@ -261,8 +267,7 @@ class DesktopImmersiveControllerTest : ShellTestCase() {
controller.moveTaskToNonImmersive(task, USER_INTERACTION)
controller.moveTaskToNonImmersive(task, USER_INTERACTION)
- verify(mockTransitions, times(1))
- .startTransition(eq(TRANSIT_CHANGE), any(), eq(controller))
+ verify(mockTransitions, times(1)).startTransition(eq(TRANSIT_CHANGE), any(), eq(controller))
}
@Test
@@ -275,7 +280,7 @@ class DesktopImmersiveControllerTest : ShellTestCase() {
desktopRepository.setTaskInFullImmersiveState(
displayId = DEFAULT_DISPLAY,
taskId = task.taskId,
- immersive = true
+ immersive = true,
)
controller.exitImmersiveIfApplicable(transition, wct, DEFAULT_DISPLAY, USER_INTERACTION)
@@ -284,7 +289,7 @@ class DesktopImmersiveControllerTest : ShellTestCase() {
transition = transition,
taskId = task.taskId,
direction = Direction.EXIT,
- animate = false
+ animate = false,
)
}
@@ -298,7 +303,7 @@ class DesktopImmersiveControllerTest : ShellTestCase() {
desktopRepository.setTaskInFullImmersiveState(
displayId = DEFAULT_DISPLAY,
taskId = task.taskId,
- immersive = false
+ immersive = false,
)
controller.exitImmersiveIfApplicable(transition, wct, DEFAULT_DISPLAY, USER_INTERACTION)
@@ -307,7 +312,7 @@ class DesktopImmersiveControllerTest : ShellTestCase() {
transition = transition,
taskId = task.taskId,
direction = Direction.EXIT,
- animate = false
+ animate = false,
)
}
@@ -321,7 +326,7 @@ class DesktopImmersiveControllerTest : ShellTestCase() {
desktopRepository.setTaskInFullImmersiveState(
displayId = DEFAULT_DISPLAY,
taskId = task.taskId,
- immersive = true
+ immersive = true,
)
controller.exitImmersiveIfApplicable(transition, wct, DEFAULT_DISPLAY, USER_INTERACTION)
@@ -339,7 +344,7 @@ class DesktopImmersiveControllerTest : ShellTestCase() {
desktopRepository.setTaskInFullImmersiveState(
displayId = DEFAULT_DISPLAY,
taskId = task.taskId,
- immersive = false
+ immersive = false,
)
controller.exitImmersiveIfApplicable(transition, wct, DEFAULT_DISPLAY, USER_INTERACTION)
@@ -357,21 +362,25 @@ class DesktopImmersiveControllerTest : ShellTestCase() {
desktopRepository.setTaskInFullImmersiveState(
displayId = DEFAULT_DISPLAY,
taskId = task.taskId,
- immersive = true
+ immersive = true,
)
- controller.exitImmersiveIfApplicable(
- wct = wct,
- displayId = DEFAULT_DISPLAY,
- excludeTaskId = task.taskId,
- reason = USER_INTERACTION,
- ).asExit()?.runOnTransitionStart?.invoke(transition)
+ controller
+ .exitImmersiveIfApplicable(
+ wct = wct,
+ displayId = DEFAULT_DISPLAY,
+ excludeTaskId = task.taskId,
+ reason = USER_INTERACTION,
+ )
+ .asExit()
+ ?.runOnTransitionStart
+ ?.invoke(transition)
assertTransitionNotPending(
transition = transition,
taskId = task.taskId,
animate = false,
- direction = Direction.EXIT
+ direction = Direction.EXIT,
)
}
@@ -384,7 +393,7 @@ class DesktopImmersiveControllerTest : ShellTestCase() {
desktopRepository.setTaskInFullImmersiveState(
displayId = DEFAULT_DISPLAY,
taskId = task.taskId,
- immersive = true
+ immersive = true,
)
controller.exitImmersiveIfApplicable(wct = wct, taskInfo = task, reason = USER_INTERACTION)
@@ -401,7 +410,7 @@ class DesktopImmersiveControllerTest : ShellTestCase() {
desktopRepository.setTaskInFullImmersiveState(
displayId = DEFAULT_DISPLAY,
taskId = task.taskId,
- immersive = false
+ immersive = false,
)
controller.exitImmersiveIfApplicable(wct, task, USER_INTERACTION)
@@ -419,17 +428,20 @@ class DesktopImmersiveControllerTest : ShellTestCase() {
desktopRepository.setTaskInFullImmersiveState(
displayId = DEFAULT_DISPLAY,
taskId = task.taskId,
- immersive = true
+ immersive = true,
)
- controller.exitImmersiveIfApplicable(wct, task, USER_INTERACTION)
- .asExit()?.runOnTransitionStart?.invoke(transition)
+ controller
+ .exitImmersiveIfApplicable(wct, task, USER_INTERACTION)
+ .asExit()
+ ?.runOnTransitionStart
+ ?.invoke(transition)
assertTransitionPending(
transition = transition,
taskId = task.taskId,
direction = Direction.EXIT,
- animate = false
+ animate = false,
)
}
@@ -442,7 +454,7 @@ class DesktopImmersiveControllerTest : ShellTestCase() {
desktopRepository.setTaskInFullImmersiveState(
displayId = task.displayId,
taskId = task.taskId,
- immersive = false
+ immersive = false,
)
val result = controller.exitImmersiveIfApplicable(wct, task, USER_INTERACTION)
@@ -459,11 +471,16 @@ class DesktopImmersiveControllerTest : ShellTestCase() {
desktopRepository.setTaskInFullImmersiveState(
displayId = task.displayId,
taskId = task.taskId,
- immersive = false
+ immersive = false,
)
- val result = controller.exitImmersiveIfApplicable(
- wct, task.displayId, excludeTaskId = null, USER_INTERACTION)
+ val result =
+ controller.exitImmersiveIfApplicable(
+ wct,
+ task.displayId,
+ excludeTaskId = null,
+ USER_INTERACTION,
+ )
assertThat(result).isEqualTo(DesktopImmersiveController.ExitResult.NoExit)
}
@@ -478,15 +495,13 @@ class DesktopImmersiveControllerTest : ShellTestCase() {
desktopRepository.setTaskInFullImmersiveState(
displayId = DEFAULT_DISPLAY,
taskId = task.taskId,
- immersive = true
+ immersive = true,
)
controller.exitImmersiveIfApplicable(transition, wct, DEFAULT_DISPLAY, USER_INTERACTION)
controller.onTransitionReady(
transition = transition,
- info = createTransitionInfo(
- changes = listOf(createChange(task))
- ),
+ info = createTransitionInfo(changes = listOf(createChange(task))),
startTransaction = SurfaceControl.Transaction(),
finishTransaction = SurfaceControl.Transaction(),
)
@@ -496,7 +511,7 @@ class DesktopImmersiveControllerTest : ShellTestCase() {
transition = transition,
taskId = task.taskId,
direction = Direction.EXIT,
- animate = false
+ animate = false,
)
}
@@ -511,15 +526,13 @@ class DesktopImmersiveControllerTest : ShellTestCase() {
desktopRepository.setTaskInFullImmersiveState(
displayId = DEFAULT_DISPLAY,
taskId = task.taskId,
- immersive = true
+ immersive = true,
)
controller.exitImmersiveIfApplicable(transition, wct, DEFAULT_DISPLAY, USER_INTERACTION)
controller.onTransitionReady(
transition = transition,
- info = createTransitionInfo(
- changes = listOf(createChange(task))
- ),
+ info = createTransitionInfo(changes = listOf(createChange(task))),
startTransaction = SurfaceControl.Transaction(),
finishTransaction = SurfaceControl.Transaction(),
)
@@ -530,13 +543,13 @@ class DesktopImmersiveControllerTest : ShellTestCase() {
transition = transition,
taskId = task.taskId,
animate = false,
- direction = Direction.EXIT
+ direction = Direction.EXIT,
)
assertTransitionNotPending(
transition = mergedToTransition,
taskId = task.taskId,
animate = false,
- direction = Direction.EXIT
+ direction = Direction.EXIT,
)
}
@@ -550,15 +563,13 @@ class DesktopImmersiveControllerTest : ShellTestCase() {
desktopRepository.setTaskInFullImmersiveState(
displayId = DEFAULT_DISPLAY,
taskId = task.taskId,
- immersive = true
+ immersive = true,
)
controller.exitImmersiveIfApplicable(transition, wct, DEFAULT_DISPLAY, USER_INTERACTION)
controller.onTransitionReady(
transition = transition,
- info = createTransitionInfo(
- changes = listOf(createChange(task))
- ),
+ info = createTransitionInfo(changes = listOf(createChange(task))),
startTransaction = SurfaceControl.Transaction(),
finishTransaction = SurfaceControl.Transaction(),
)
@@ -569,7 +580,7 @@ class DesktopImmersiveControllerTest : ShellTestCase() {
@Test
@EnableFlags(
Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP,
- Flags.FLAG_ENABLE_RESTORE_TO_PREVIOUS_SIZE_FROM_DESKTOP_IMMERSIVE
+ Flags.FLAG_ENABLE_RESTORE_TO_PREVIOUS_SIZE_FROM_DESKTOP_IMMERSIVE,
)
fun onTransitionReady_pendingExit_removesBoundsBeforeImmersive() {
val task = createFreeformTask()
@@ -579,16 +590,14 @@ class DesktopImmersiveControllerTest : ShellTestCase() {
desktopRepository.setTaskInFullImmersiveState(
displayId = DEFAULT_DISPLAY,
taskId = task.taskId,
- immersive = true
+ immersive = true,
)
desktopRepository.saveBoundsBeforeFullImmersive(task.taskId, Rect(100, 100, 600, 600))
controller.exitImmersiveIfApplicable(transition, wct, DEFAULT_DISPLAY, USER_INTERACTION)
controller.onTransitionReady(
transition = transition,
- info = createTransitionInfo(
- changes = listOf(createChange(task))
- ),
+ info = createTransitionInfo(changes = listOf(createChange(task))),
startTransaction = SurfaceControl.Transaction(),
finishTransaction = SurfaceControl.Transaction(),
)
@@ -606,20 +615,21 @@ class DesktopImmersiveControllerTest : ShellTestCase() {
desktopRepository.setTaskInFullImmersiveState(
displayId = DEFAULT_DISPLAY,
taskId = task.taskId,
- immersive = true
+ immersive = true,
)
controller.exitImmersiveIfApplicable(wct = wct, taskInfo = task, reason = USER_INTERACTION)
assertThat(
- wct.hasBoundsChange(task.token, calculateMaximizeBounds(mockDisplayLayout, task))
- ).isTrue()
+ wct.hasBoundsChange(task.token, calculateMaximizeBounds(mockDisplayLayout, task))
+ )
+ .isTrue()
}
@Test
@EnableFlags(
Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP,
- Flags.FLAG_ENABLE_RESTORE_TO_PREVIOUS_SIZE_FROM_DESKTOP_IMMERSIVE
+ Flags.FLAG_ENABLE_RESTORE_TO_PREVIOUS_SIZE_FROM_DESKTOP_IMMERSIVE,
)
fun exitImmersiveIfApplicable_preImmersiveBoundsSaved_changesBoundsToPreImmersiveBounds() {
val task = createFreeformTask()
@@ -628,23 +638,21 @@ class DesktopImmersiveControllerTest : ShellTestCase() {
desktopRepository.setTaskInFullImmersiveState(
displayId = DEFAULT_DISPLAY,
taskId = task.taskId,
- immersive = true
+ immersive = true,
)
val preImmersiveBounds = Rect(100, 100, 500, 500)
desktopRepository.saveBoundsBeforeFullImmersive(task.taskId, preImmersiveBounds)
controller.exitImmersiveIfApplicable(wct = wct, taskInfo = task, reason = USER_INTERACTION)
- assertThat(
- wct.hasBoundsChange(task.token, preImmersiveBounds)
- ).isTrue()
+ assertThat(wct.hasBoundsChange(task.token, preImmersiveBounds)).isTrue()
}
@Test
@EnableFlags(
Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP,
Flags.FLAG_ENABLE_RESTORE_TO_PREVIOUS_SIZE_FROM_DESKTOP_IMMERSIVE,
- Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS
+ Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS,
)
fun exitImmersiveIfApplicable_preImmersiveBoundsNotSaved_changesBoundsToInitialBounds() {
val task = createFreeformTask()
@@ -653,14 +661,13 @@ class DesktopImmersiveControllerTest : ShellTestCase() {
desktopRepository.setTaskInFullImmersiveState(
displayId = DEFAULT_DISPLAY,
taskId = task.taskId,
- immersive = true
+ immersive = true,
)
controller.exitImmersiveIfApplicable(wct = wct, taskInfo = task, reason = USER_INTERACTION)
- assertThat(
- wct.hasBoundsChange(task.token, calculateInitialBounds(mockDisplayLayout, task))
- ).isTrue()
+ assertThat(wct.hasBoundsChange(task.token, calculateInitialBounds(mockDisplayLayout, task)))
+ .isTrue()
}
@Test
@@ -672,10 +679,13 @@ class DesktopImmersiveControllerTest : ShellTestCase() {
desktopRepository.setTaskInFullImmersiveState(
displayId = task.displayId,
taskId = task.taskId,
- immersive = true
+ immersive = true,
)
- controller.exitImmersiveIfApplicable(wct, task, USER_INTERACTION)
- .asExit()?.runOnTransitionStart?.invoke(Binder())
+ controller
+ .exitImmersiveIfApplicable(wct, task, USER_INTERACTION)
+ .asExit()
+ ?.runOnTransitionStart
+ ?.invoke(Binder())
controller.moveTaskToNonImmersive(task, USER_INTERACTION)
@@ -693,7 +703,7 @@ class DesktopImmersiveControllerTest : ShellTestCase() {
desktopRepository.setTaskInFullImmersiveState(
displayId = DEFAULT_DISPLAY,
taskId = task.taskId,
- immersive = true
+ immersive = true,
)
controller.exitImmersiveIfApplicable(transition, wct, DEFAULT_DISPLAY, USER_INTERACTION)
@@ -711,25 +721,23 @@ class DesktopImmersiveControllerTest : ShellTestCase() {
desktopRepository.setTaskInFullImmersiveState(
displayId = task.displayId,
taskId = task.taskId,
- immersive = true
+ immersive = true,
)
controller.moveTaskToNonImmersive(task, USER_INTERACTION)
controller.animateResizeChange(
- change = TransitionInfo.Change(task.token, SurfaceControl()).apply {
- taskInfo = task
- },
+ change = TransitionInfo.Change(task.token, SurfaceControl()).apply { taskInfo = task },
startTransaction = StubTransaction(),
finishTransaction = StubTransaction(),
- finishCallback = { }
+ finishCallback = {},
)
animatorTestRule.advanceTimeBy(DesktopImmersiveController.FULL_IMMERSIVE_ANIM_DURATION_MS)
assertTransitionPending(
transition = mockBinder,
taskId = task.taskId,
- direction = Direction.EXIT
+ direction = Direction.EXIT,
)
}
@@ -743,7 +751,7 @@ class DesktopImmersiveControllerTest : ShellTestCase() {
desktopRepository.setTaskInFullImmersiveState(
displayId = task.displayId,
taskId = task.taskId,
- immersive = false
+ immersive = false,
)
controller.moveTaskToImmersive(task)
@@ -753,13 +761,13 @@ class DesktopImmersiveControllerTest : ShellTestCase() {
info = createTransitionInfo(changes = emptyList()),
startTransaction = StubTransaction(),
finishTransaction = StubTransaction(),
- finishCallback = {}
+ finishCallback = {},
)
assertTransitionNotPending(
transition = mockBinder,
taskId = task.taskId,
- direction = Direction.ENTER
+ direction = Direction.ENTER,
)
}
@@ -768,15 +776,18 @@ class DesktopImmersiveControllerTest : ShellTestCase() {
taskId: Int,
direction: Direction,
animate: Boolean = true,
- displayId: Int = DEFAULT_DISPLAY
+ displayId: Int = DEFAULT_DISPLAY,
) {
- assertThat(controller.pendingImmersiveTransitions.any { pendingTransition ->
- pendingTransition.transition == transition
- && pendingTransition.displayId == displayId
- && pendingTransition.taskId == taskId
- && pendingTransition.animate == animate
- && pendingTransition.direction == direction
- }).isTrue()
+ assertThat(
+ controller.pendingImmersiveTransitions.any { pendingTransition ->
+ pendingTransition.transition == transition &&
+ pendingTransition.displayId == displayId &&
+ pendingTransition.taskId == taskId &&
+ pendingTransition.animate == animate &&
+ pendingTransition.direction == direction
+ }
+ )
+ .isTrue()
}
private fun assertTransitionNotPending(
@@ -784,43 +795,44 @@ class DesktopImmersiveControllerTest : ShellTestCase() {
taskId: Int,
direction: Direction,
animate: Boolean = true,
- displayId: Int = DEFAULT_DISPLAY
+ displayId: Int = DEFAULT_DISPLAY,
) {
- assertThat(controller.pendingImmersiveTransitions.any { pendingTransition ->
- pendingTransition.transition == transition
- && pendingTransition.displayId == displayId
- && pendingTransition.taskId == taskId
- && pendingTransition.direction == direction
- }).isFalse()
+ assertThat(
+ controller.pendingImmersiveTransitions.any { pendingTransition ->
+ pendingTransition.transition == transition &&
+ pendingTransition.displayId == displayId &&
+ pendingTransition.taskId == taskId &&
+ pendingTransition.direction == direction
+ }
+ )
+ .isFalse()
}
private fun createTransitionInfo(
@TransitionType type: Int = TRANSIT_CHANGE,
@TransitionFlags flags: Int = 0,
- changes: List<TransitionInfo.Change> = emptyList()
- ): TransitionInfo = TransitionInfo(type, flags).apply {
- changes.forEach { change -> addChange(change) }
- }
+ changes: List<TransitionInfo.Change> = emptyList(),
+ ): TransitionInfo =
+ TransitionInfo(type, flags).apply { changes.forEach { change -> addChange(change) } }
private fun createChange(task: RunningTaskInfo): TransitionInfo.Change =
- TransitionInfo.Change(task.token, SurfaceControl()).apply {
- taskInfo = task
- }
+ TransitionInfo.Change(task.token, SurfaceControl()).apply { taskInfo = task }
private fun WindowContainerTransaction.hasBoundsChange(token: WindowContainerToken): Boolean =
this.changes.any { change ->
- change.key == token.asBinder()
- && (change.value.windowSetMask and WINDOW_CONFIG_BOUNDS) != 0
+ change.key == token.asBinder() &&
+ (change.value.windowSetMask and WINDOW_CONFIG_BOUNDS) != 0
}
private fun WindowContainerTransaction.hasBoundsChange(
token: WindowContainerToken,
bounds: Rect,
- ): Boolean = this.changes.any { change ->
- change.key == token.asBinder()
- && (change.value.windowSetMask and WINDOW_CONFIG_BOUNDS) != 0
- && change.value.configuration.windowConfiguration.bounds == bounds
- }
+ ): Boolean =
+ this.changes.any { change ->
+ change.key == token.asBinder() &&
+ (change.value.windowSetMask and WINDOW_CONFIG_BOUNDS) != 0 &&
+ change.value.configuration.windowConfiguration.bounds == bounds
+ }
companion object {
private val STABLE_BOUNDS = Rect(0, 100, 2000, 1900)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandlerTest.kt
index 49a7e2951a7e..3cf84d92a625 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandlerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandlerTest.kt
@@ -85,34 +85,22 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() {
@JvmField @Rule val setFlagsRule = SetFlagsRule()
- @Mock
- lateinit var transitions: Transitions
- @Mock
- lateinit var userRepositories: DesktopUserRepositories
- @Mock
- lateinit var freeformTaskTransitionHandler: FreeformTaskTransitionHandler
- @Mock
- lateinit var closeDesktopTaskTransitionHandler: CloseDesktopTaskTransitionHandler
+ @Mock lateinit var transitions: Transitions
+ @Mock lateinit var userRepositories: DesktopUserRepositories
+ @Mock lateinit var freeformTaskTransitionHandler: FreeformTaskTransitionHandler
+ @Mock lateinit var closeDesktopTaskTransitionHandler: CloseDesktopTaskTransitionHandler
@Mock
lateinit var desktopBackNavigationTransitionHandler: DesktopBackNavigationTransitionHandler
- @Mock
- lateinit var desktopImmersiveController: DesktopImmersiveController
- @Mock
- lateinit var interactionJankMonitor: InteractionJankMonitor
- @Mock
- lateinit var mockHandler: Handler
- @Mock
- lateinit var closingTaskLeash: SurfaceControl
- @Mock
- lateinit var shellInit: ShellInit
- @Mock
- lateinit var rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer
- @Mock
- private lateinit var desktopRepository: DesktopRepository
+ @Mock lateinit var desktopImmersiveController: DesktopImmersiveController
+ @Mock lateinit var interactionJankMonitor: InteractionJankMonitor
+ @Mock lateinit var mockHandler: Handler
+ @Mock lateinit var closingTaskLeash: SurfaceControl
+ @Mock lateinit var shellInit: ShellInit
+ @Mock lateinit var rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer
+ @Mock private lateinit var desktopRepository: DesktopRepository
private lateinit var mixedHandler: DesktopMixedTransitionHandler
-
@Before
fun setUp() {
whenever(userRepositories.current).thenReturn(desktopRepository)
@@ -157,11 +145,11 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() {
@Test
@DisableFlags(
Flags.FLAG_ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS,
- Flags.FLAG_ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS_BUGFIX)
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS_BUGFIX,
+ )
fun startRemoveTransition_callsFreeformTaskTransitionHandler() {
val wct = WindowContainerTransaction()
- whenever(freeformTaskTransitionHandler.startRemoveTransition(wct))
- .thenReturn(mock())
+ whenever(freeformTaskTransitionHandler.startRemoveTransition(wct)).thenReturn(mock())
mixedHandler.startRemoveTransition(wct)
@@ -171,7 +159,8 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() {
@Test
@EnableFlags(
Flags.FLAG_ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS,
- Flags.FLAG_ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS_BUGFIX)
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS_BUGFIX,
+ )
fun startRemoveTransition_startsCloseTransition() {
val wct = WindowContainerTransaction()
whenever(transitions.startTransition(WindowManager.TRANSIT_CLOSE, wct, mixedHandler))
@@ -193,18 +182,19 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() {
val transitionInfo =
createCloseTransitionInfo(
changeMode = TRANSIT_OPEN,
- task = createTask(WINDOWING_MODE_FREEFORM)
+ task = createTask(WINDOWING_MODE_FREEFORM),
)
whenever(freeformTaskTransitionHandler.startAnimation(any(), any(), any(), any(), any()))
.thenReturn(true)
- val started = mixedHandler.startAnimation(
- transition = transition,
- info = transitionInfo,
- startTransaction = mock(),
- finishTransaction = mock(),
- finishCallback = {}
- )
+ val started =
+ mixedHandler.startAnimation(
+ transition = transition,
+ info = transitionInfo,
+ startTransaction = mock(),
+ finishTransaction = mock(),
+ finishCallback = {},
+ )
assertFalse("Should not start animation without closing desktop task", started)
}
@@ -212,7 +202,8 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() {
@Test
@EnableFlags(
Flags.FLAG_ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS,
- Flags.FLAG_ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS_BUGFIX)
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS_BUGFIX,
+ )
fun startAnimation_withClosingDesktopTask_callsCloseTaskHandler() {
val wct = WindowContainerTransaction()
val transition = mock<IBinder>()
@@ -225,13 +216,14 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() {
.thenReturn(transition)
mixedHandler.startRemoveTransition(wct)
- val started = mixedHandler.startAnimation(
- transition = transition,
- info = transitionInfo,
- startTransaction = mock(),
- finishTransaction = mock(),
- finishCallback = {}
- )
+ val started =
+ mixedHandler.startAnimation(
+ transition = transition,
+ info = transitionInfo,
+ startTransaction = mock(),
+ finishTransaction = mock(),
+ finishCallback = {},
+ )
assertTrue("Should delegate animation to close transition handler", started)
verify(closeDesktopTaskTransitionHandler)
@@ -241,12 +233,16 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() {
@Test
@EnableFlags(
Flags.FLAG_ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS,
- Flags.FLAG_ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS_BUGFIX)
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS_BUGFIX,
+ )
fun startAnimation_withClosingLastDesktopTask_dispatchesTransition() {
val wct = WindowContainerTransaction()
val transition = mock<IBinder>()
- val transitionInfo = createCloseTransitionInfo(
- task = createTask(WINDOWING_MODE_FREEFORM), withWallpaper = true)
+ val transitionInfo =
+ createCloseTransitionInfo(
+ task = createTask(WINDOWING_MODE_FREEFORM),
+ withWallpaper = true,
+ )
whenever(transitions.dispatchTransition(any(), any(), any(), any(), any(), any()))
.thenReturn(mock())
whenever(transitions.startTransition(WindowManager.TRANSIT_CLOSE, wct, mixedHandler))
@@ -258,7 +254,7 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() {
info = transitionInfo,
startTransaction = mock(),
finishTransaction = mock(),
- finishCallback = {}
+ finishCallback = {},
)
verify(transitions)
@@ -268,14 +264,14 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() {
any(),
any(),
any(),
- eq(mixedHandler)
+ eq(mixedHandler),
)
verify(interactionJankMonitor)
.begin(
closingTaskLeash,
context,
mockHandler,
- CUJ_DESKTOP_MODE_EXIT_MODE_ON_LAST_WINDOW_CLOSE
+ CUJ_DESKTOP_MODE_EXIT_MODE_ON_LAST_WINDOW_CLOSE,
)
}
@@ -283,7 +279,8 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() {
@DisableFlags(
Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP,
Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS,
- Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS_BUGFIX)
+ Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS_BUGFIX,
+ )
fun startLaunchTransition_immersiveAndAppLaunchFlagsDisabled_doesNotUseMixedHandler() {
val wct = WindowContainerTransaction()
val task = createTask(WINDOWING_MODE_FREEFORM)
@@ -294,7 +291,7 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() {
transitionType = TRANSIT_OPEN,
wct = wct,
taskId = task.taskId,
- exitingImmersiveTask = null
+ exitingImmersiveTask = null,
)
verify(transitions).startTransition(TRANSIT_OPEN, wct, /* handler= */ null)
@@ -312,7 +309,7 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() {
transitionType = TRANSIT_OPEN,
wct = wct,
taskId = task.taskId,
- exitingImmersiveTask = null
+ exitingImmersiveTask = null,
)
verify(transitions).startTransition(TRANSIT_OPEN, wct, mixedHandler)
@@ -321,7 +318,8 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() {
@Test
@EnableFlags(
Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS,
- Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS_BUGFIX)
+ Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS_BUGFIX,
+ )
fun startLaunchTransition_desktopAppLaunchEnabled_usesMixedHandler() {
val wct = WindowContainerTransaction()
val task = createTask(WINDOWING_MODE_FREEFORM)
@@ -332,7 +330,7 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() {
transitionType = TRANSIT_OPEN,
wct = wct,
taskId = task.taskId,
- exitingImmersiveTask = null
+ exitingImmersiveTask = null,
)
verify(transitions).startTransition(TRANSIT_OPEN, wct, mixedHandler)
@@ -357,24 +355,22 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() {
val otherChange = createChange(createTask(WINDOWING_MODE_FREEFORM))
mixedHandler.startAnimation(
transition,
- createCloseTransitionInfo(
- TRANSIT_OPEN,
- listOf(launchTaskChange, otherChange)
- ),
+ createCloseTransitionInfo(TRANSIT_OPEN, listOf(launchTaskChange, otherChange)),
SurfaceControl.Transaction(),
SurfaceControl.Transaction(),
- ) { }
-
- verify(transitions).dispatchTransition(
- eq(transition),
- argThat { info ->
- info.changes.contains(launchTaskChange) && info.changes.contains(otherChange)
- },
- any(),
- any(),
- any(),
- eq(mixedHandler),
- )
+ ) {}
+
+ verify(transitions)
+ .dispatchTransition(
+ eq(transition),
+ argThat { info ->
+ info.changes.contains(launchTaskChange) && info.changes.contains(otherChange)
+ },
+ any(),
+ any(),
+ any(),
+ eq(mixedHandler),
+ )
}
@Test
@@ -397,32 +393,32 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() {
val immersiveChange = createChange(immersiveTask)
mixedHandler.startAnimation(
transition,
- createCloseTransitionInfo(
- TRANSIT_OPEN,
- listOf(launchTaskChange, immersiveChange)
- ),
+ createCloseTransitionInfo(TRANSIT_OPEN, listOf(launchTaskChange, immersiveChange)),
SurfaceControl.Transaction(),
SurfaceControl.Transaction(),
- ) { }
+ ) {}
verify(desktopImmersiveController)
.animateResizeChange(eq(immersiveChange), any(), any(), any())
- verify(transitions).dispatchTransition(
- eq(transition),
- argThat { info ->
- info.changes.contains(launchTaskChange) && !info.changes.contains(immersiveChange)
- },
- any(),
- any(),
- any(),
- eq(mixedHandler),
- )
+ verify(transitions)
+ .dispatchTransition(
+ eq(transition),
+ argThat { info ->
+ info.changes.contains(launchTaskChange) &&
+ !info.changes.contains(immersiveChange)
+ },
+ any(),
+ any(),
+ any(),
+ eq(mixedHandler),
+ )
}
@Test
@EnableFlags(
Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS,
- Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS_BUGFIX)
+ Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS_BUGFIX,
+ )
fun startAndAnimateLaunchTransition_noMinimizeChange_doesNotReparentMinimizeChange() {
val wct = WindowContainerTransaction()
val launchingTask = createTask(WINDOWING_MODE_FREEFORM)
@@ -439,22 +435,19 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() {
)
mixedHandler.startAnimation(
transition,
- createCloseTransitionInfo(
- TRANSIT_OPEN,
- listOf(launchTaskChange)
- ),
+ createCloseTransitionInfo(TRANSIT_OPEN, listOf(launchTaskChange)),
SurfaceControl.Transaction(),
SurfaceControl.Transaction(),
- ) { }
+ ) {}
- verify(rootTaskDisplayAreaOrganizer, times(0))
- .reparentToDisplayArea(anyInt(), any(), any())
+ verify(rootTaskDisplayAreaOrganizer, times(0)).reparentToDisplayArea(anyInt(), any(), any())
}
@Test
@EnableFlags(
Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS,
- Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS_BUGFIX)
+ Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS_BUGFIX,
+ )
fun startAndAnimateLaunchTransition_withMinimizeChange_reparentsMinimizeChange() {
val wct = WindowContainerTransaction()
val launchingTask = createTask(WINDOWING_MODE_FREEFORM)
@@ -473,22 +466,20 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() {
)
mixedHandler.startAnimation(
transition,
- createCloseTransitionInfo(
- TRANSIT_OPEN,
- listOf(launchTaskChange, minimizeChange)
- ),
+ createCloseTransitionInfo(TRANSIT_OPEN, listOf(launchTaskChange, minimizeChange)),
SurfaceControl.Transaction(),
SurfaceControl.Transaction(),
- ) { }
+ ) {}
- verify(rootTaskDisplayAreaOrganizer).reparentToDisplayArea(
- anyInt(), eq(minimizeChange.leash), any())
+ verify(rootTaskDisplayAreaOrganizer)
+ .reparentToDisplayArea(anyInt(), eq(minimizeChange.leash), any())
}
@Test
@EnableFlags(
Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS,
- Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS_BUGFIX)
+ Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS_BUGFIX,
+ )
fun startAnimation_pendingTransition_noLaunchChange_returnsFalse() {
val wct = WindowContainerTransaction()
val launchingTask = createTask(WINDOWING_MODE_FREEFORM)
@@ -505,15 +496,13 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() {
)
)
- val started = mixedHandler.startAnimation(
- transition,
- createCloseTransitionInfo(
- TRANSIT_OPEN,
- listOf(nonLaunchTaskChange)
- ),
- SurfaceControl.Transaction(),
- SurfaceControl.Transaction(),
- ) { }
+ val started =
+ mixedHandler.startAnimation(
+ transition,
+ createCloseTransitionInfo(TRANSIT_OPEN, listOf(nonLaunchTaskChange)),
+ SurfaceControl.Transaction(),
+ SurfaceControl.Transaction(),
+ ) {}
assertFalse("Should not start animation without launching desktop task", started)
}
@@ -529,21 +518,18 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() {
whenever(transitions.dispatchTransition(eq(transition), any(), any(), any(), any(), any()))
.thenReturn(mock())
- mixedHandler.startLaunchTransition(
- transitionType = TRANSIT_OPEN,
- wct = wct,
- taskId = null,
- )
+ mixedHandler.startLaunchTransition(transitionType = TRANSIT_OPEN, wct = wct, taskId = null)
- val started = mixedHandler.startAnimation(
- transition,
- createCloseTransitionInfo(
- TRANSIT_OPEN,
- listOf(createChange(task, mode = TRANSIT_OPEN))
- ),
- StubTransaction(),
- StubTransaction(),
- ) { }
+ val started =
+ mixedHandler.startAnimation(
+ transition,
+ createCloseTransitionInfo(
+ TRANSIT_OPEN,
+ listOf(createChange(task, mode = TRANSIT_OPEN)),
+ ),
+ StubTransaction(),
+ StubTransaction(),
+ ) {}
assertThat(started).isEqualTo(true)
}
@@ -569,15 +555,13 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() {
val immersiveChange = createChange(immersiveTask, mode = TRANSIT_CHANGE)
val openingChange = createChange(openingTask, mode = TRANSIT_OPEN)
- val started = mixedHandler.startAnimation(
- transition,
- createCloseTransitionInfo(
- TRANSIT_OPEN,
- listOf(immersiveChange, openingChange)
- ),
- StubTransaction(),
- StubTransaction(),
- ) { }
+ val started =
+ mixedHandler.startAnimation(
+ transition,
+ createCloseTransitionInfo(TRANSIT_OPEN, listOf(immersiveChange, openingChange)),
+ StubTransaction(),
+ StubTransaction(),
+ ) {}
assertThat(started).isEqualTo(true)
verify(desktopImmersiveController)
@@ -587,7 +571,8 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() {
@Test
@EnableFlags(
Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS,
- Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS_BUGFIX)
+ Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS_BUGFIX,
+ )
fun addPendingAndAnimateLaunchTransition_noMinimizeChange_doesNotReparentMinimizeChange() {
val wct = WindowContainerTransaction()
val launchingTask = createTask(WINDOWING_MODE_FREEFORM)
@@ -606,22 +591,19 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() {
)
mixedHandler.startAnimation(
transition,
- createCloseTransitionInfo(
- TRANSIT_OPEN,
- listOf(launchTaskChange)
- ),
+ createCloseTransitionInfo(TRANSIT_OPEN, listOf(launchTaskChange)),
SurfaceControl.Transaction(),
SurfaceControl.Transaction(),
- ) { }
+ ) {}
- verify(rootTaskDisplayAreaOrganizer, times(0))
- .reparentToDisplayArea(anyInt(), any(), any())
+ verify(rootTaskDisplayAreaOrganizer, times(0)).reparentToDisplayArea(anyInt(), any(), any())
}
@Test
@EnableFlags(
Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS,
- Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS_BUGFIX)
+ Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS_BUGFIX,
+ )
fun addPendingAndAnimateLaunchTransition_withMinimizeChange_reparentsMinimizeChange() {
val wct = WindowContainerTransaction()
val launchingTask = createTask(WINDOWING_MODE_FREEFORM)
@@ -642,16 +624,13 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() {
)
mixedHandler.startAnimation(
transition,
- createCloseTransitionInfo(
- TRANSIT_OPEN,
- listOf(launchTaskChange, minimizeChange)
- ),
+ createCloseTransitionInfo(TRANSIT_OPEN, listOf(launchTaskChange, minimizeChange)),
SurfaceControl.Transaction(),
SurfaceControl.Transaction(),
- ) { }
+ ) {}
- verify(rootTaskDisplayAreaOrganizer).reparentToDisplayArea(
- anyInt(), eq(minimizeChange.leash), any())
+ verify(rootTaskDisplayAreaOrganizer)
+ .reparentToDisplayArea(anyInt(), eq(minimizeChange.leash), any())
}
@Test
@@ -672,13 +651,10 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() {
val launchTaskChange = createChange(launchingTask)
mixedHandler.startAnimation(
transition,
- createCloseTransitionInfo(
- TRANSIT_OPEN,
- listOf(launchTaskChange)
- ),
+ createCloseTransitionInfo(TRANSIT_OPEN, listOf(launchTaskChange)),
SurfaceControl.Transaction(),
SurfaceControl.Transaction(),
- ) { }
+ ) {}
assertThat(mixedHandler.pendingMixedTransitions).isEmpty()
}
@@ -701,7 +677,7 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() {
mixedHandler.onTransitionConsumed(
transition = transition,
aborted = true,
- finishTransaction = SurfaceControl.Transaction()
+ finishTransaction = SurfaceControl.Transaction(),
)
assertThat(mixedHandler.pendingMixedTransitions).isEmpty()
@@ -714,8 +690,14 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() {
val transition = Binder()
whenever(desktopRepository.getExpandedTaskCount(any())).thenReturn(2)
whenever(
- desktopBackNavigationTransitionHandler.startAnimation(any(), any(), any(), any(), any())
- )
+ desktopBackNavigationTransitionHandler.startAnimation(
+ any(),
+ any(),
+ any(),
+ any(),
+ any(),
+ )
+ )
.thenReturn(true)
mixedHandler.addPendingMixedTransition(
PendingMixedTransition.Minimize(
@@ -726,24 +708,24 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() {
)
val minimizingTaskChange = createChange(minimizingTask)
- val started = mixedHandler.startAnimation(
- transition = transition,
- info =
- createCloseTransitionInfo(
- TRANSIT_TO_BACK,
- listOf(minimizingTaskChange)
- ),
- startTransaction = mock(),
- finishTransaction = mock(),
- finishCallback = {}
- )
+ val started =
+ mixedHandler.startAnimation(
+ transition = transition,
+ info = createCloseTransitionInfo(TRANSIT_TO_BACK, listOf(minimizingTaskChange)),
+ startTransaction = mock(),
+ finishTransaction = mock(),
+ finishCallback = {},
+ )
assertTrue("Should delegate animation to back navigation transition handler", started)
verify(desktopBackNavigationTransitionHandler)
.startAnimation(
eq(transition),
argThat { info -> info.changes.contains(minimizingTaskChange) },
- any(), any(), any())
+ any(),
+ any(),
+ any(),
+ )
}
@Test
@@ -753,8 +735,14 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() {
val transition = Binder()
whenever(desktopRepository.getExpandedTaskCount(any())).thenReturn(2)
whenever(
- desktopBackNavigationTransitionHandler.startAnimation(any(), any(), any(), any(), any())
- )
+ desktopBackNavigationTransitionHandler.startAnimation(
+ any(),
+ any(),
+ any(),
+ any(),
+ any(),
+ )
+ )
.thenReturn(true)
mixedHandler.addPendingMixedTransition(
PendingMixedTransition.Minimize(
@@ -767,14 +755,10 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() {
val minimizingTaskChange = createChange(minimizingTask)
mixedHandler.startAnimation(
transition = transition,
- info =
- createCloseTransitionInfo(
- TRANSIT_TO_BACK,
- listOf(minimizingTaskChange)
- ),
+ info = createCloseTransitionInfo(TRANSIT_TO_BACK, listOf(minimizingTaskChange)),
startTransaction = mock(),
finishTransaction = mock(),
- finishCallback = {}
+ finishCallback = {},
)
verify(transitions)
@@ -784,7 +768,7 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() {
any(),
any(),
any(),
- eq(mixedHandler)
+ eq(mixedHandler),
)
}
@@ -814,14 +798,15 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() {
private fun createCloseTransitionInfo(
@TransitionType type: Int,
- changes: List<TransitionInfo.Change> = emptyList()
- ): TransitionInfo = TransitionInfo(type, /* flags= */ 0).apply {
- changes.forEach { change -> addChange(change) }
- }
+ changes: List<TransitionInfo.Change> = emptyList(),
+ ): TransitionInfo =
+ TransitionInfo(type, /* flags= */ 0).apply {
+ changes.forEach { change -> addChange(change) }
+ }
private fun createChange(
task: RunningTaskInfo,
- @TransitionInfo.TransitionMode mode: Int = TRANSIT_NONE
+ @TransitionInfo.TransitionMode mode: Int = TRANSIT_NONE,
): TransitionInfo.Change =
TransitionInfo.Change(task.token, SurfaceControl()).apply {
taskInfo = task
@@ -838,8 +823,6 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() {
RunningTaskInfo().apply {
token = WindowContainerToken(mock<IWindowContainerToken>())
baseIntent =
- Intent().apply {
- component = DesktopWallpaperActivity.wallpaperActivityComponent
- }
+ Intent().apply { component = DesktopWallpaperActivity.wallpaperActivityComponent }
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeEventLoggerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeEventLoggerTest.kt
index 2f225f22cce0..abd707817621 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeEventLoggerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeEventLoggerTest.kt
@@ -53,9 +53,7 @@ import org.mockito.kotlin.eq
import org.mockito.kotlin.mock
import org.mockito.kotlin.whenever
-/**
- * Tests for [DesktopModeEventLogger].
- */
+/** Tests for [DesktopModeEventLogger]. */
class DesktopModeEventLoggerTest : ShellTestCase() {
private val desktopModeEventLogger = DesktopModeEventLogger()
@@ -64,13 +62,13 @@ class DesktopModeEventLoggerTest : ShellTestCase() {
@JvmField
@Rule(order = 0)
- val extendedMockitoRule = ExtendedMockitoRule.Builder(this)
- .mockStatic(FrameworkStatsLog::class.java)
- .mockStatic(EventLogTags::class.java).build()!!
+ val extendedMockitoRule =
+ ExtendedMockitoRule.Builder(this)
+ .mockStatic(FrameworkStatsLog::class.java)
+ .mockStatic(EventLogTags::class.java)
+ .build()!!
- @JvmField
- @Rule(order = 1)
- val setFlagsRule = SetFlagsRule()
+ @JvmField @Rule(order = 1) val setFlagsRule = SetFlagsRule()
@Before
fun setUp() {
@@ -95,14 +93,14 @@ class DesktopModeEventLoggerTest : ShellTestCase() {
/* exit_reason */
eq(0),
/* sessionId */
- eq(sessionId)
+ eq(sessionId),
)
}
verifyZeroInteractions(staticMockMarker(FrameworkStatsLog::class.java))
verify {
EventLogTags.writeWmShellEnterDesktopMode(
eq(EnterReason.KEYBOARD_SHORTCUT_ENTER.reason),
- eq(sessionId)
+ eq(sessionId),
)
}
verifyZeroInteractions(staticMockMarker(EventLogTags::class.java))
@@ -127,14 +125,14 @@ class DesktopModeEventLoggerTest : ShellTestCase() {
/* exit_reason */
eq(0),
/* sessionId */
- eq(sessionId)
+ eq(sessionId),
)
}
verifyZeroInteractions(staticMockMarker(FrameworkStatsLog::class.java))
verify {
EventLogTags.writeWmShellEnterDesktopMode(
eq(EnterReason.KEYBOARD_SHORTCUT_ENTER.reason),
- eq(sessionId)
+ eq(sessionId),
)
}
verifyZeroInteractions(staticMockMarker(EventLogTags::class.java))
@@ -164,14 +162,14 @@ class DesktopModeEventLoggerTest : ShellTestCase() {
/* exit_reason */
eq(FrameworkStatsLog.DESKTOP_MODE_UICHANGED__EXIT_REASON__DRAG_TO_EXIT),
/* sessionId */
- eq(sessionId)
+ eq(sessionId),
)
}
verifyZeroInteractions(staticMockMarker(FrameworkStatsLog::class.java))
verify {
EventLogTags.writeWmShellExitDesktopMode(
eq(ExitReason.DRAG_TO_EXIT.reason),
- eq(sessionId)
+ eq(sessionId),
)
}
verifyZeroInteractions(staticMockMarker(EventLogTags::class.java))
@@ -214,16 +212,13 @@ class DesktopModeEventLoggerTest : ShellTestCase() {
eq(UNSET_MINIMIZE_REASON),
eq(UNSET_UNMINIMIZE_REASON),
/* visible_task_count */
- eq(TASK_COUNT)
+ eq(TASK_COUNT),
)
}
verifyZeroInteractions(staticMockMarker(FrameworkStatsLog::class.java))
verify {
EventLogTags.writeWmShellDesktopModeTaskUpdate(
- eq(
- FrameworkStatsLog
- .DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_ADDED
- ),
+ eq(FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_ADDED),
eq(TASK_UPDATE.instanceId),
eq(TASK_UPDATE.uid),
eq(TASK_UPDATE.taskHeight),
@@ -233,7 +228,7 @@ class DesktopModeEventLoggerTest : ShellTestCase() {
eq(sessionId),
eq(UNSET_MINIMIZE_REASON),
eq(UNSET_UNMINIMIZE_REASON),
- eq(TASK_COUNT)
+ eq(TASK_COUNT),
)
}
verifyZeroInteractions(staticMockMarker(EventLogTags::class.java))
@@ -275,16 +270,13 @@ class DesktopModeEventLoggerTest : ShellTestCase() {
eq(UNSET_MINIMIZE_REASON),
eq(UNSET_UNMINIMIZE_REASON),
/* visible_task_count */
- eq(TASK_COUNT)
+ eq(TASK_COUNT),
)
}
verifyZeroInteractions(staticMockMarker(FrameworkStatsLog::class.java))
verify {
EventLogTags.writeWmShellDesktopModeTaskUpdate(
- eq(
- FrameworkStatsLog
- .DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_REMOVED
- ),
+ eq(FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_REMOVED),
eq(TASK_UPDATE.instanceId),
eq(TASK_UPDATE.uid),
eq(TASK_UPDATE.taskHeight),
@@ -294,7 +286,7 @@ class DesktopModeEventLoggerTest : ShellTestCase() {
eq(sessionId),
eq(UNSET_MINIMIZE_REASON),
eq(UNSET_UNMINIMIZE_REASON),
- eq(TASK_COUNT)
+ eq(TASK_COUNT),
)
}
verifyZeroInteractions(staticMockMarker(EventLogTags::class.java))
@@ -339,7 +331,7 @@ class DesktopModeEventLoggerTest : ShellTestCase() {
eq(UNSET_MINIMIZE_REASON),
eq(UNSET_UNMINIMIZE_REASON),
/* visible_task_count */
- eq(TASK_COUNT)
+ eq(TASK_COUNT),
)
}
verifyZeroInteractions(staticMockMarker(FrameworkStatsLog::class.java))
@@ -358,7 +350,7 @@ class DesktopModeEventLoggerTest : ShellTestCase() {
eq(sessionId),
eq(UNSET_MINIMIZE_REASON),
eq(UNSET_UNMINIMIZE_REASON),
- eq(TASK_COUNT)
+ eq(TASK_COUNT),
)
}
verifyZeroInteractions(staticMockMarker(EventLogTags::class.java))
@@ -399,7 +391,7 @@ class DesktopModeEventLoggerTest : ShellTestCase() {
/* unminimize_reason */
eq(UNSET_UNMINIMIZE_REASON),
/* visible_task_count */
- eq(TASK_COUNT)
+ eq(TASK_COUNT),
)
}
verifyZeroInteractions(staticMockMarker(FrameworkStatsLog::class.java))
@@ -418,7 +410,7 @@ class DesktopModeEventLoggerTest : ShellTestCase() {
eq(sessionId),
eq(MinimizeReason.TASK_LIMIT.reason),
eq(UNSET_UNMINIMIZE_REASON),
- eq(TASK_COUNT)
+ eq(TASK_COUNT),
)
}
verifyZeroInteractions(staticMockMarker(EventLogTags::class.java))
@@ -459,7 +451,7 @@ class DesktopModeEventLoggerTest : ShellTestCase() {
/* unminimize_reason */
eq(UnminimizeReason.TASKBAR_TAP.reason),
/* visible_task_count */
- eq(TASK_COUNT)
+ eq(TASK_COUNT),
)
}
verifyZeroInteractions(staticMockMarker(FrameworkStatsLog::class.java))
@@ -478,7 +470,7 @@ class DesktopModeEventLoggerTest : ShellTestCase() {
eq(sessionId),
eq(UNSET_MINIMIZE_REASON),
eq(UnminimizeReason.TASKBAR_TAP.reason),
- eq(TASK_COUNT)
+ eq(TASK_COUNT),
)
}
verifyZeroInteractions(staticMockMarker(EventLogTags::class.java))
@@ -486,8 +478,11 @@ class DesktopModeEventLoggerTest : ShellTestCase() {
@Test
fun logTaskResizingStarted_noOngoingSession_doesNotLog() {
- desktopModeEventLogger.logTaskResizingStarted(ResizeTrigger.CORNER,
- InputMethod.UNKNOWN_INPUT_METHOD, createTaskInfo())
+ desktopModeEventLogger.logTaskResizingStarted(
+ ResizeTrigger.CORNER,
+ InputMethod.UNKNOWN_INPUT_METHOD,
+ createTaskInfo(),
+ )
verifyZeroInteractions(staticMockMarker(FrameworkStatsLog::class.java))
verifyZeroInteractions(staticMockMarker(EventLogTags::class.java))
@@ -498,19 +493,33 @@ class DesktopModeEventLoggerTest : ShellTestCase() {
fun logTaskResizingStarted_logsTaskSizeUpdatedWithStartResizingStage() {
val sessionId = startDesktopModeSession()
- desktopModeEventLogger.logTaskResizingStarted(ResizeTrigger.CORNER,
- InputMethod.UNKNOWN_INPUT_METHOD, createTaskInfo(), TASK_SIZE_UPDATE.taskWidth,
- TASK_SIZE_UPDATE.taskHeight, displayController)
+ desktopModeEventLogger.logTaskResizingStarted(
+ ResizeTrigger.CORNER,
+ InputMethod.UNKNOWN_INPUT_METHOD,
+ createTaskInfo(),
+ TASK_SIZE_UPDATE.taskWidth,
+ TASK_SIZE_UPDATE.taskHeight,
+ displayController,
+ )
verify {
FrameworkStatsLog.write(
eq(FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED),
/* resize_trigger */
- eq(FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED__RESIZE_TRIGGER__CORNER_RESIZE_TRIGGER),
+ eq(
+ FrameworkStatsLog
+ .DESKTOP_MODE_TASK_SIZE_UPDATED__RESIZE_TRIGGER__CORNER_RESIZE_TRIGGER
+ ),
/* resizing_stage */
- eq(FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED__RESIZING_STAGE__START_RESIZING_STAGE),
+ eq(
+ FrameworkStatsLog
+ .DESKTOP_MODE_TASK_SIZE_UPDATED__RESIZING_STAGE__START_RESIZING_STAGE
+ ),
/* input_method */
- eq(FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED__INPUT_METHOD__UNKNOWN_INPUT_METHOD),
+ eq(
+ FrameworkStatsLog
+ .DESKTOP_MODE_TASK_SIZE_UPDATED__INPUT_METHOD__UNKNOWN_INPUT_METHOD
+ ),
/* desktop_mode_session_id */
eq(sessionId),
/* instance_id */
@@ -530,8 +539,11 @@ class DesktopModeEventLoggerTest : ShellTestCase() {
@Test
fun logTaskResizingEnded_noOngoingSession_doesNotLog() {
- desktopModeEventLogger.logTaskResizingEnded(ResizeTrigger.CORNER,
- InputMethod.UNKNOWN_INPUT_METHOD, createTaskInfo())
+ desktopModeEventLogger.logTaskResizingEnded(
+ ResizeTrigger.CORNER,
+ InputMethod.UNKNOWN_INPUT_METHOD,
+ createTaskInfo(),
+ )
verifyZeroInteractions(staticMockMarker(FrameworkStatsLog::class.java))
verifyZeroInteractions(staticMockMarker(EventLogTags::class.java))
@@ -542,18 +554,31 @@ class DesktopModeEventLoggerTest : ShellTestCase() {
fun logTaskResizingEnded_logsTaskSizeUpdatedWithEndResizingStage() {
val sessionId = startDesktopModeSession()
- desktopModeEventLogger.logTaskResizingEnded(ResizeTrigger.CORNER,
- InputMethod.UNKNOWN_INPUT_METHOD, createTaskInfo(), displayController = displayController)
+ desktopModeEventLogger.logTaskResizingEnded(
+ ResizeTrigger.CORNER,
+ InputMethod.UNKNOWN_INPUT_METHOD,
+ createTaskInfo(),
+ displayController = displayController,
+ )
verify {
FrameworkStatsLog.write(
eq(FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED),
/* resize_trigger */
- eq(FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED__RESIZE_TRIGGER__CORNER_RESIZE_TRIGGER),
+ eq(
+ FrameworkStatsLog
+ .DESKTOP_MODE_TASK_SIZE_UPDATED__RESIZE_TRIGGER__CORNER_RESIZE_TRIGGER
+ ),
/* resizing_stage */
- eq(FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED__RESIZING_STAGE__END_RESIZING_STAGE),
+ eq(
+ FrameworkStatsLog
+ .DESKTOP_MODE_TASK_SIZE_UPDATED__RESIZING_STAGE__END_RESIZING_STAGE
+ ),
/* input_method */
- eq(FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED__INPUT_METHOD__UNKNOWN_INPUT_METHOD),
+ eq(
+ FrameworkStatsLog
+ .DESKTOP_MODE_TASK_SIZE_UPDATED__INPUT_METHOD__UNKNOWN_INPUT_METHOD
+ ),
/* desktop_mode_session_id */
eq(sessionId),
/* instance_id */
@@ -582,9 +607,12 @@ class DesktopModeEventLoggerTest : ShellTestCase() {
fun logTaskInfoStateInit_logsTaskInfoChangedStateInit() {
desktopModeEventLogger.logTaskInfoStateInit()
verify {
- FrameworkStatsLog.write(eq(FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE),
+ FrameworkStatsLog.write(
+ eq(FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE),
/* task_event */
- eq(FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_INIT_STATSD),
+ eq(
+ FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_INIT_STATSD
+ ),
/* instance_id */
eq(0),
/* uid */
@@ -604,13 +632,14 @@ class DesktopModeEventLoggerTest : ShellTestCase() {
/* unminimize_reason */
eq(UNSET_UNMINIMIZE_REASON),
/* visible_task_count */
- eq(0)
+ eq(0),
)
}
}
private fun createTaskInfo(): RunningTaskInfo {
- return TestRunningTaskInfoBuilder().setTaskId(TASK_ID)
+ return TestRunningTaskInfoBuilder()
+ .setTaskId(TASK_ID)
.setUid(TASK_UID)
.setBounds(Rect(TASK_X, TASK_Y, TASK_WIDTH, TASK_HEIGHT))
.build()
@@ -628,27 +657,42 @@ class DesktopModeEventLoggerTest : ShellTestCase() {
private const val DISPLAY_HEIGHT = 500
private const val DISPLAY_AREA = DISPLAY_HEIGHT * DISPLAY_WIDTH
- private val TASK_UPDATE = TaskUpdate(
- TASK_ID, TASK_UID, TASK_HEIGHT, TASK_WIDTH, TASK_X, TASK_Y,
- visibleTaskCount = TASK_COUNT,
- )
+ private val TASK_UPDATE =
+ TaskUpdate(
+ TASK_ID,
+ TASK_UID,
+ TASK_HEIGHT,
+ TASK_WIDTH,
+ TASK_X,
+ TASK_Y,
+ visibleTaskCount = TASK_COUNT,
+ )
- private val TASK_SIZE_UPDATE = TaskSizeUpdate(
- resizeTrigger = ResizeTrigger.UNKNOWN_RESIZE_TRIGGER,
- inputMethod = InputMethod.UNKNOWN_INPUT_METHOD,
- TASK_ID,
- TASK_UID,
- TASK_HEIGHT,
- TASK_WIDTH,
- DISPLAY_AREA,
- )
+ private val TASK_SIZE_UPDATE =
+ TaskSizeUpdate(
+ resizeTrigger = ResizeTrigger.UNKNOWN_RESIZE_TRIGGER,
+ inputMethod = InputMethod.UNKNOWN_INPUT_METHOD,
+ TASK_ID,
+ TASK_UID,
+ TASK_HEIGHT,
+ TASK_WIDTH,
+ DISPLAY_AREA,
+ )
private fun createTaskUpdate(
minimizeReason: MinimizeReason? = null,
unminimizeReason: UnminimizeReason? = null,
- ) = TaskUpdate(
- TASK_ID, TASK_UID, TASK_HEIGHT, TASK_WIDTH, TASK_X, TASK_Y, minimizeReason,
- unminimizeReason, TASK_COUNT
- )
+ ) =
+ TaskUpdate(
+ TASK_ID,
+ TASK_UID,
+ TASK_HEIGHT,
+ TASK_WIDTH,
+ TASK_X,
+ TASK_Y,
+ minimizeReason,
+ unminimizeReason,
+ TASK_COUNT,
+ )
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandlerTest.kt
index e57ae2a86859..413e7bc5d1d6 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandlerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandlerTest.kt
@@ -30,49 +30,49 @@ import android.view.Display.DEFAULT_DISPLAY
import android.view.KeyEvent
import android.window.DisplayAreaInfo
import androidx.test.filters.SmallTest
+import com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer
+import com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn
+import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession
+import com.android.dx.mockito.inline.extended.StaticMockitoSession
import com.android.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER
import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE
import com.android.window.flags.Flags.FLAG_ENABLE_DISPLAY_FOCUS_IN_SHELL_TRANSITIONS
import com.android.window.flags.Flags.FLAG_ENABLE_MOVE_TO_NEXT_DISPLAY_SHORTCUT
+import com.android.window.flags.Flags.FLAG_ENABLE_TASK_RESIZING_KEYBOARD_SHORTCUTS
import com.android.wm.shell.MockToken
import com.android.wm.shell.RootTaskDisplayAreaOrganizer
import com.android.wm.shell.ShellTaskOrganizer
import com.android.wm.shell.ShellTestCase
-import com.android.wm.shell.desktopmode.DesktopTestHelpers.createFreeformTask
-import com.android.wm.shell.transition.FocusTransitionObserver
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import org.junit.Before
-import org.junit.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.kotlin.eq
-import org.mockito.kotlin.whenever
-import com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer
-import com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn
-import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession
-import com.android.dx.mockito.inline.extended.StaticMockitoSession
-import com.android.window.flags.Flags.FLAG_ENABLE_TASK_RESIZING_KEYBOARD_SHORTCUTS
import com.android.wm.shell.TestShellExecutor
import com.android.wm.shell.common.DisplayController
import com.android.wm.shell.common.DisplayLayout
+import com.android.wm.shell.desktopmode.DesktopTestHelpers.createFreeformTask
import com.android.wm.shell.desktopmode.common.ToggleTaskSizeInteraction
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
import com.android.wm.shell.sysui.ShellInit
+import com.android.wm.shell.transition.FocusTransitionObserver
import com.android.wm.shell.windowdecor.DesktopModeWindowDecorViewModel
+import com.google.common.truth.Truth.assertThat
import java.util.Optional
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancel
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.setMain
import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
import org.mockito.Mockito.anyInt
import org.mockito.Mockito.spy
import org.mockito.Mockito.verify
import org.mockito.kotlin.any
+import org.mockito.kotlin.eq
import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
import org.mockito.quality.Strictness
/**
@@ -130,21 +130,24 @@ class DesktopModeKeyGestureHandlerTest : ShellTestCase() {
whenever(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)).thenReturn(tda)
doAnswer {
- keyGestureEventHandler = (it.arguments[0] as KeyGestureEventHandler)
- null
- }.whenever(inputManager).registerKeyGestureEventHandler(any())
+ keyGestureEventHandler = (it.arguments[0] as KeyGestureEventHandler)
+ null
+ }
+ .whenever(inputManager)
+ .registerKeyGestureEventHandler(any())
shellInit.init()
- desktopModeKeyGestureHandler = DesktopModeKeyGestureHandler(
- context,
- Optional.of(desktopModeWindowDecorViewModel),
- Optional.of(desktopTasksController),
- inputManager,
- shellTaskOrganizer,
- focusTransitionObserver,
- testExecutor,
- displayController
- )
+ desktopModeKeyGestureHandler =
+ DesktopModeKeyGestureHandler(
+ context,
+ Optional.of(desktopModeWindowDecorViewModel),
+ Optional.of(desktopTasksController),
+ inputManager,
+ shellTaskOrganizer,
+ focusTransitionObserver,
+ testExecutor,
+ displayController,
+ )
}
@After
@@ -160,7 +163,7 @@ class DesktopModeKeyGestureHandlerTest : ShellTestCase() {
@EnableFlags(
FLAG_ENABLE_DISPLAY_FOCUS_IN_SHELL_TRANSITIONS,
FLAG_ENABLE_MOVE_TO_NEXT_DISPLAY_SHORTCUT,
- FLAG_USE_KEY_GESTURE_EVENT_HANDLER
+ FLAG_USE_KEY_GESTURE_EVENT_HANDLER,
)
fun keyGestureMoveToNextDisplay_shouldMoveToNextDisplay() {
// Set up two display ids
@@ -176,12 +179,13 @@ class DesktopModeKeyGestureHandlerTest : ShellTestCase() {
whenever(shellTaskOrganizer.getRunningTasks()).thenReturn(arrayListOf(task))
whenever(focusTransitionObserver.hasGlobalFocus(eq(task))).thenReturn(true)
- val event = KeyGestureEvent.Builder()
- .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_MOVE_TO_NEXT_DISPLAY)
- .setDisplayId(SECOND_DISPLAY)
- .setKeycodes(intArrayOf(KeyEvent.KEYCODE_D))
- .setModifierState(KeyEvent.META_META_ON or KeyEvent.META_CTRL_ON)
- .build()
+ val event =
+ KeyGestureEvent.Builder()
+ .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_MOVE_TO_NEXT_DISPLAY)
+ .setDisplayId(SECOND_DISPLAY)
+ .setKeycodes(intArrayOf(KeyEvent.KEYCODE_D))
+ .setModifierState(KeyEvent.META_META_ON or KeyEvent.META_CTRL_ON)
+ .build()
val result = keyGestureEventHandler.handleKeyGestureEvent(event, null)
testExecutor.flushAll()
@@ -190,108 +194,102 @@ class DesktopModeKeyGestureHandlerTest : ShellTestCase() {
}
@Test
- @EnableFlags(
- FLAG_USE_KEY_GESTURE_EVENT_HANDLER,
- FLAG_ENABLE_TASK_RESIZING_KEYBOARD_SHORTCUTS
- )
+ @EnableFlags(FLAG_USE_KEY_GESTURE_EVENT_HANDLER, FLAG_ENABLE_TASK_RESIZING_KEYBOARD_SHORTCUTS)
fun keyGestureSnapLeft_shouldSnapResizeTaskToLeft() {
val task = setUpFreeformTask()
task.isFocused = true
whenever(shellTaskOrganizer.getRunningTasks()).thenReturn(arrayListOf(task))
whenever(focusTransitionObserver.hasGlobalFocus(eq(task))).thenReturn(true)
- val event = KeyGestureEvent.Builder()
- .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_SNAP_LEFT_FREEFORM_WINDOW)
- .setKeycodes(intArrayOf(KeyEvent.KEYCODE_LEFT_BRACKET))
- .setModifierState(KeyEvent.META_META_ON)
- .build()
+ val event =
+ KeyGestureEvent.Builder()
+ .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_SNAP_LEFT_FREEFORM_WINDOW)
+ .setKeycodes(intArrayOf(KeyEvent.KEYCODE_LEFT_BRACKET))
+ .setModifierState(KeyEvent.META_META_ON)
+ .build()
val result = keyGestureEventHandler.handleKeyGestureEvent(event, null)
testExecutor.flushAll()
assertThat(result).isTrue()
- verify(desktopModeWindowDecorViewModel).onSnapResize(
- task.taskId,
- true,
- DesktopModeEventLogger.Companion.InputMethod.KEYBOARD,
- /* fromMenu= */ false
- )
+ verify(desktopModeWindowDecorViewModel)
+ .onSnapResize(
+ task.taskId,
+ true,
+ DesktopModeEventLogger.Companion.InputMethod.KEYBOARD,
+ /* fromMenu= */ false,
+ )
}
@Test
- @EnableFlags(
- FLAG_USE_KEY_GESTURE_EVENT_HANDLER,
- FLAG_ENABLE_TASK_RESIZING_KEYBOARD_SHORTCUTS
- )
+ @EnableFlags(FLAG_USE_KEY_GESTURE_EVENT_HANDLER, FLAG_ENABLE_TASK_RESIZING_KEYBOARD_SHORTCUTS)
fun keyGestureSnapRight_shouldSnapResizeTaskToRight() {
val task = setUpFreeformTask()
task.isFocused = true
whenever(shellTaskOrganizer.getRunningTasks()).thenReturn(arrayListOf(task))
whenever(focusTransitionObserver.hasGlobalFocus(eq(task))).thenReturn(true)
- val event = KeyGestureEvent.Builder()
- .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_SNAP_RIGHT_FREEFORM_WINDOW)
- .setKeycodes(intArrayOf(KeyEvent.KEYCODE_RIGHT_BRACKET))
- .setModifierState(KeyEvent.META_META_ON)
- .build()
+ val event =
+ KeyGestureEvent.Builder()
+ .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_SNAP_RIGHT_FREEFORM_WINDOW)
+ .setKeycodes(intArrayOf(KeyEvent.KEYCODE_RIGHT_BRACKET))
+ .setModifierState(KeyEvent.META_META_ON)
+ .build()
val result = keyGestureEventHandler.handleKeyGestureEvent(event, null)
testExecutor.flushAll()
assertThat(result).isTrue()
- verify(desktopModeWindowDecorViewModel).onSnapResize(
- task.taskId,
- false,
- DesktopModeEventLogger.Companion.InputMethod.KEYBOARD,
- /* fromMenu= */ false
- )
+ verify(desktopModeWindowDecorViewModel)
+ .onSnapResize(
+ task.taskId,
+ false,
+ DesktopModeEventLogger.Companion.InputMethod.KEYBOARD,
+ /* fromMenu= */ false,
+ )
}
@Test
- @EnableFlags(
- FLAG_USE_KEY_GESTURE_EVENT_HANDLER,
- FLAG_ENABLE_TASK_RESIZING_KEYBOARD_SHORTCUTS
- )
+ @EnableFlags(FLAG_USE_KEY_GESTURE_EVENT_HANDLER, FLAG_ENABLE_TASK_RESIZING_KEYBOARD_SHORTCUTS)
fun keyGestureToggleFreeformWindowSize_shouldToggleTaskSize() {
val task = setUpFreeformTask()
task.isFocused = true
whenever(shellTaskOrganizer.getRunningTasks()).thenReturn(arrayListOf(task))
whenever(focusTransitionObserver.hasGlobalFocus(eq(task))).thenReturn(true)
- val event = KeyGestureEvent.Builder()
- .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_MAXIMIZE_FREEFORM_WINDOW)
- .setKeycodes(intArrayOf(KeyEvent.KEYCODE_EQUALS))
- .setModifierState(KeyEvent.META_META_ON)
- .build()
+ val event =
+ KeyGestureEvent.Builder()
+ .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_MAXIMIZE_FREEFORM_WINDOW)
+ .setKeycodes(intArrayOf(KeyEvent.KEYCODE_EQUALS))
+ .setModifierState(KeyEvent.META_META_ON)
+ .build()
val result = keyGestureEventHandler.handleKeyGestureEvent(event, null)
testExecutor.flushAll()
assertThat(result).isTrue()
- verify(desktopTasksController).toggleDesktopTaskSize(
- task,
- ToggleTaskSizeInteraction(
- isMaximized = isTaskMaximized(task, displayController),
- source = ToggleTaskSizeInteraction.Source.KEYBOARD_SHORTCUT,
- inputMethod =
- DesktopModeEventLogger.Companion.InputMethod.KEYBOARD,
- ),
- )
+ verify(desktopTasksController)
+ .toggleDesktopTaskSize(
+ task,
+ ToggleTaskSizeInteraction(
+ isMaximized = isTaskMaximized(task, displayController),
+ source = ToggleTaskSizeInteraction.Source.KEYBOARD_SHORTCUT,
+ inputMethod = DesktopModeEventLogger.Companion.InputMethod.KEYBOARD,
+ ),
+ )
}
@Test
- @EnableFlags(
- FLAG_USE_KEY_GESTURE_EVENT_HANDLER,
- FLAG_ENABLE_TASK_RESIZING_KEYBOARD_SHORTCUTS
- )
+ @EnableFlags(FLAG_USE_KEY_GESTURE_EVENT_HANDLER, FLAG_ENABLE_TASK_RESIZING_KEYBOARD_SHORTCUTS)
fun keyGestureMinimizeFreeformWindow_shouldMinimizeTask() {
val task = setUpFreeformTask()
task.isFocused = true
whenever(shellTaskOrganizer.getRunningTasks()).thenReturn(arrayListOf(task))
whenever(focusTransitionObserver.hasGlobalFocus(eq(task))).thenReturn(true)
- val event = KeyGestureEvent.Builder()
- .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_MINIMIZE_FREEFORM_WINDOW)
- .setKeycodes(intArrayOf(KeyEvent.KEYCODE_MINUS))
- .setModifierState(KeyEvent.META_META_ON)
- .build()
+ val event =
+ KeyGestureEvent.Builder()
+ .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_MINIMIZE_FREEFORM_WINDOW)
+ .setKeycodes(intArrayOf(KeyEvent.KEYCODE_MINUS))
+ .setModifierState(KeyEvent.META_META_ON)
+ .build()
val result = keyGestureEventHandler.handleKeyGestureEvent(event, null)
testExecutor.flushAll()
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt
index 7c4ce4acfc9c..43684fb92b64 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt
@@ -87,700 +87,735 @@ import org.mockito.kotlin.whenever
@RunWith(AndroidTestingRunner::class)
class DesktopModeLoggerTransitionObserverTest : ShellTestCase() {
- @JvmField
- @Rule
- val extendedMockitoRule =
- ExtendedMockitoRule.Builder(this)
- .mockStatic(DesktopModeStatus::class.java)
- .mockStatic(SystemProperties::class.java)
- .mockStatic(Trace::class.java)
- .build()!!
-
- private val testExecutor = mock<ShellExecutor>()
- private val mockShellInit = mock<ShellInit>()
- private val transitions = mock<Transitions>()
- private val context = mock<Context>()
-
- private lateinit var transitionObserver: DesktopModeLoggerTransitionObserver
- private lateinit var shellInit: ShellInit
- private lateinit var desktopModeEventLogger: DesktopModeEventLogger
-
- @Before
- fun setup() {
- whenever(DesktopModeStatus.canEnterDesktopMode(any())).thenReturn(true)
- shellInit = spy(ShellInit(testExecutor))
- desktopModeEventLogger = mock<DesktopModeEventLogger>()
-
- transitionObserver = DesktopModeLoggerTransitionObserver(
- context, mockShellInit, transitions, desktopModeEventLogger)
- val initRunnableCaptor = ArgumentCaptor.forClass(Runnable::class.java)
- verify(mockShellInit).addInitCallback(initRunnableCaptor.capture(), same(transitionObserver))
- initRunnableCaptor.value.run()
- // verify this initialisation interaction to leave the desktopmodeEventLogger mock in a
- // consistent state with no outstanding interactions when test cases start executing.
- verify(desktopModeEventLogger).logTaskInfoStateInit()
- }
-
- @Test
- fun testInitialiseVisibleTasksSystemProperty() {
- ExtendedMockito.verify {
- SystemProperties.set(
- eq(DesktopModeLoggerTransitionObserver.VISIBLE_TASKS_COUNTER_SYSTEM_PROPERTY),
- eq(DesktopModeLoggerTransitionObserver
- .VISIBLE_TASKS_COUNTER_SYSTEM_PROPERTY_DEFAULT_VALUE))
- }
- }
-
- @Test
- fun testRegistersObserverAtInit() {
- verify(transitions).registerObserver(same(transitionObserver))
- }
-
- @Test
- fun transitOpen_notFreeformWindow_doesNotLogTaskAddedOrSessionEnter() {
- val change = createChange(TRANSIT_OPEN, createTaskInfo(WINDOWING_MODE_FULLSCREEN))
- val transitionInfo = TransitionInfoBuilder(TRANSIT_OPEN, 0).addChange(change).build()
-
- callOnTransitionReady(transitionInfo)
-
- verify(desktopModeEventLogger, never()).logSessionEnter(any())
- verify(desktopModeEventLogger, never()).logTaskAdded(any())
- }
-
- @Test
- fun transitOpen_logTaskAddedAndEnterReasonAppFreeformIntent() {
- val change = createChange(TRANSIT_OPEN, createTaskInfo(WINDOWING_MODE_FREEFORM))
- val transitionInfo = TransitionInfoBuilder(TRANSIT_OPEN, 0).addChange(change).build()
-
- callOnTransitionReady(transitionInfo)
-
- verifyTaskAddedAndEnterLogging(EnterReason.APP_FREEFORM_INTENT,
- DEFAULT_TASK_UPDATE.copy(visibleTaskCount = 1))
- }
-
- @Test
- fun transitEndDragToDesktop_logTaskAddedAndEnterReasonAppHandleDrag() {
- val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(WINDOWING_MODE_FREEFORM))
- // task change is finalised when drag ends
- val transitionInfo =
- TransitionInfoBuilder(Transitions.TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP, 0)
- .addChange(change)
- .build()
-
- callOnTransitionReady(transitionInfo)
-
- verifyTaskAddedAndEnterLogging(EnterReason.APP_HANDLE_DRAG,
- DEFAULT_TASK_UPDATE.copy(visibleTaskCount = 1))
- }
-
- @Test
- fun transitEnterDesktopByButtonTap_logTaskAddedAndEnterReasonButtonTap() {
- val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(WINDOWING_MODE_FREEFORM))
- val transitionInfo =
- TransitionInfoBuilder(TRANSIT_ENTER_DESKTOP_FROM_APP_HANDLE_MENU_BUTTON, 0)
- .addChange(change)
- .build()
-
- callOnTransitionReady(transitionInfo)
-
- verifyTaskAddedAndEnterLogging(EnterReason.APP_HANDLE_MENU_BUTTON,
- DEFAULT_TASK_UPDATE.copy(visibleTaskCount = 1))
- }
-
- @Test
- fun transitEnterDesktopFromAppFromOverview_logTaskAddedAndEnterReasonAppFromOverview() {
- val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(WINDOWING_MODE_FREEFORM))
- val transitionInfo =
- TransitionInfoBuilder(TRANSIT_ENTER_DESKTOP_FROM_APP_FROM_OVERVIEW, 0)
- .addChange(change)
- .build()
-
- callOnTransitionReady(transitionInfo)
-
- verifyTaskAddedAndEnterLogging(EnterReason.APP_FROM_OVERVIEW,
- DEFAULT_TASK_UPDATE.copy(visibleTaskCount = 1))
- }
-
- @Test
- fun transitEnterDesktopFromKeyboardShortcut_logTaskAddedAndEnterReasonKeyboardShortcut() {
- val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(WINDOWING_MODE_FREEFORM))
- val transitionInfo =
- TransitionInfoBuilder(TRANSIT_ENTER_DESKTOP_FROM_KEYBOARD_SHORTCUT, 0)
- .addChange(change)
- .build()
-
- callOnTransitionReady(transitionInfo)
-
- verifyTaskAddedAndEnterLogging(EnterReason.KEYBOARD_SHORTCUT_ENTER,
- DEFAULT_TASK_UPDATE.copy(visibleTaskCount = 1))
- }
-
- @Test
- fun transitToFront_logTaskAddedAndEnterReasonOverview() {
- val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(WINDOWING_MODE_FREEFORM))
- val transitionInfo = TransitionInfoBuilder(TRANSIT_TO_FRONT, 0).addChange(change).build()
-
- callOnTransitionReady(transitionInfo)
-
- verifyTaskAddedAndEnterLogging(EnterReason.OVERVIEW,
- DEFAULT_TASK_UPDATE.copy(visibleTaskCount = 1))
- }
-
- @Test
- fun transitToFront_previousTransitionExitToOverview_logTaskAddedAndEnterReasonOverview() {
- // previous exit to overview transition
- // add a freeform task
- val previousTaskInfo = createTaskInfo(WINDOWING_MODE_FREEFORM)
- transitionObserver.addTaskInfosToCachedMap(previousTaskInfo)
- transitionObserver.isSessionActive = true
- val previousTransitionInfo =
- TransitionInfoBuilder(TRANSIT_TO_FRONT, TRANSIT_FLAG_IS_RECENTS)
- .addChange(createChange(TRANSIT_TO_BACK, previousTaskInfo))
- .build()
-
- callOnTransitionReady(previousTransitionInfo)
-
- verifyTaskRemovedAndExitLogging(
- ExitReason.RETURN_HOME_OR_OVERVIEW, DEFAULT_TASK_UPDATE
- )
-
- // Enter desktop mode from cancelled recents has no transition. Enter is detected on the
- // next transition involving freeform windows
-
- // TRANSIT_TO_FRONT
- val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(WINDOWING_MODE_FREEFORM))
- val transitionInfo = TransitionInfoBuilder(TRANSIT_TO_FRONT, 0).addChange(change).build()
-
- callOnTransitionReady(transitionInfo)
-
- verifyTaskAddedAndEnterLogging(EnterReason.OVERVIEW,
- DEFAULT_TASK_UPDATE.copy(visibleTaskCount = 1))
- }
-
- @Test
- fun transitChange_previousTransitionExitToOverview_logTaskAddedAndEnterReasonOverview() {
- // previous exit to overview transition
- // add a freeform task
- val previousTaskInfo = createTaskInfo(WINDOWING_MODE_FREEFORM)
- transitionObserver.addTaskInfosToCachedMap(previousTaskInfo)
- transitionObserver.isSessionActive = true
- val previousTransitionInfo =
- TransitionInfoBuilder(TRANSIT_TO_FRONT, TRANSIT_FLAG_IS_RECENTS)
- .addChange(createChange(TRANSIT_TO_BACK, previousTaskInfo))
- .build()
-
- callOnTransitionReady(previousTransitionInfo)
-
- verifyTaskRemovedAndExitLogging(
- ExitReason.RETURN_HOME_OR_OVERVIEW, DEFAULT_TASK_UPDATE
- )
-
- // Enter desktop mode from cancelled recents has no transition. Enter is detected on the
- // next transition involving freeform windows
-
- // TRANSIT_CHANGE
- val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(WINDOWING_MODE_FREEFORM))
- val transitionInfo = TransitionInfoBuilder(TRANSIT_CHANGE, 0).addChange(change).build()
-
- callOnTransitionReady(transitionInfo)
-
- verifyTaskAddedAndEnterLogging(EnterReason.OVERVIEW,
- DEFAULT_TASK_UPDATE.copy(visibleTaskCount = 1))
- }
-
- @Test
- fun transitOpen_previousTransitionExitToOverview_logTaskAddedAndEnterReasonOverview() {
- // previous exit to overview transition
- // add a freeform task
- val previousTaskInfo = createTaskInfo(WINDOWING_MODE_FREEFORM)
- transitionObserver.addTaskInfosToCachedMap(previousTaskInfo)
- transitionObserver.isSessionActive = true
- val previousTransitionInfo =
- TransitionInfoBuilder(TRANSIT_TO_FRONT, TRANSIT_FLAG_IS_RECENTS)
- .addChange(createChange(TRANSIT_TO_BACK, previousTaskInfo))
- .build()
-
- callOnTransitionReady(previousTransitionInfo)
-
- verifyTaskRemovedAndExitLogging(
- ExitReason.RETURN_HOME_OR_OVERVIEW, DEFAULT_TASK_UPDATE
- )
-
- // Enter desktop mode from cancelled recents has no transition. Enter is detected on the
- // next transition involving freeform windows
-
- // TRANSIT_OPEN
- val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(WINDOWING_MODE_FREEFORM))
- val transitionInfo = TransitionInfoBuilder(TRANSIT_OPEN, 0).addChange(change).build()
-
- callOnTransitionReady(transitionInfo)
-
- verifyTaskAddedAndEnterLogging(EnterReason.OVERVIEW,
- DEFAULT_TASK_UPDATE.copy(visibleTaskCount = 1))
- }
-
- @Test
- @Suppress("ktlint:standard:max-line-length")
- fun transitEnterDesktopFromAppFromOverview_previousTransitionExitToOverview_logTaskAddedAndEnterReasonAppFromOverview() {
- // Tests for AppFromOverview precedence in compared to cancelled Overview
-
- // previous exit to overview transition
- // add a freeform task
- val previousTaskInfo = createTaskInfo(WINDOWING_MODE_FREEFORM)
- transitionObserver.addTaskInfosToCachedMap(previousTaskInfo)
- transitionObserver.isSessionActive = true
- val previousTransitionInfo =
- TransitionInfoBuilder(TRANSIT_TO_FRONT, TRANSIT_FLAG_IS_RECENTS)
- .addChange(createChange(TRANSIT_TO_BACK, previousTaskInfo))
- .build()
-
- callOnTransitionReady(previousTransitionInfo)
-
- verifyTaskRemovedAndExitLogging(
- ExitReason.RETURN_HOME_OR_OVERVIEW, DEFAULT_TASK_UPDATE
- )
-
- // TRANSIT_ENTER_DESKTOP_FROM_APP_FROM_OVERVIEW
- val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(WINDOWING_MODE_FREEFORM))
- val transitionInfo =
- TransitionInfoBuilder(TRANSIT_ENTER_DESKTOP_FROM_APP_FROM_OVERVIEW, 0)
- .addChange(change)
- .build()
-
- callOnTransitionReady(transitionInfo)
-
- verifyTaskAddedAndEnterLogging(EnterReason.APP_FROM_OVERVIEW,
- DEFAULT_TASK_UPDATE.copy(visibleTaskCount = 1))
- }
-
- @Test
- fun transitEnterDesktopFromUnknown_logTaskAddedAndEnterReasonUnknown() {
- val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(WINDOWING_MODE_FREEFORM))
- val transitionInfo =
- TransitionInfoBuilder(TRANSIT_ENTER_DESKTOP_FROM_UNKNOWN, 0).addChange(change).build()
-
- callOnTransitionReady(transitionInfo)
-
- verifyTaskAddedAndEnterLogging(EnterReason.UNKNOWN_ENTER,
- DEFAULT_TASK_UPDATE.copy(visibleTaskCount = 1))
- }
-
- @Test
- fun transitWake_logTaskAddedAndEnterReasonScreenOn() {
- val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(WINDOWING_MODE_FREEFORM))
- val transitionInfo = TransitionInfoBuilder(TRANSIT_WAKE, 0).addChange(change).build()
-
- callOnTransitionReady(transitionInfo)
-
- verifyTaskAddedAndEnterLogging(EnterReason.SCREEN_ON,
- DEFAULT_TASK_UPDATE.copy(visibleTaskCount = 1))
- }
-
- @Test
- fun transitBack_previousExitReasonScreenOff_logTaskAddedAndEnterReasonScreenOn() {
- val freeformTask = createTaskInfo(WINDOWING_MODE_FREEFORM)
- // Previous Exit reason recorded as Screen Off
- transitionObserver.addTaskInfosToCachedMap(freeformTask)
- transitionObserver.isSessionActive = true
- callOnTransitionReady(TransitionInfoBuilder(TRANSIT_SLEEP).build())
- verifyTaskRemovedAndExitLogging(ExitReason.SCREEN_OFF, DEFAULT_TASK_UPDATE)
- // Enter desktop through back transition, this happens when user enters after dismissing
- // keyguard
- val change = createChange(TRANSIT_TO_FRONT, freeformTask)
- val transitionInfo = TransitionInfoBuilder(TRANSIT_TO_BACK, 0).addChange(change).build()
-
- callOnTransitionReady(transitionInfo)
-
- verifyTaskAddedAndEnterLogging(EnterReason.SCREEN_ON,
- DEFAULT_TASK_UPDATE.copy(visibleTaskCount = 1))
- }
-
- @Test
- fun transitEndDragToDesktop_previousExitReasonScreenOff_logTaskAddedAndEnterReasonAppDrag() {
- val freeformTask = createTaskInfo(WINDOWING_MODE_FREEFORM)
- // Previous Exit reason recorded as Screen Off
- transitionObserver.addTaskInfosToCachedMap(freeformTask)
- transitionObserver.isSessionActive = true
- callOnTransitionReady(TransitionInfoBuilder(TRANSIT_SLEEP).build())
- verifyTaskRemovedAndExitLogging(ExitReason.SCREEN_OFF, DEFAULT_TASK_UPDATE)
-
- // Enter desktop through app handle drag. This represents cases where instead of moving to
- // desktop right after turning the screen on, we move to fullscreen then move another task
- // to desktop
- val transitionInfo =
- TransitionInfoBuilder(Transitions.TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP, 0)
- .addChange(createChange(TRANSIT_TO_FRONT, freeformTask))
- .build()
- callOnTransitionReady(transitionInfo)
-
- verifyTaskAddedAndEnterLogging(EnterReason.APP_HANDLE_DRAG,
- DEFAULT_TASK_UPDATE.copy(visibleTaskCount = 1))
- }
-
- @Test
- fun transitSleep_logTaskRemovedAndExitReasonScreenOff() {
- // add a freeform task
- transitionObserver.addTaskInfosToCachedMap(createTaskInfo(WINDOWING_MODE_FREEFORM))
- transitionObserver.isSessionActive = true
-
- val transitionInfo = TransitionInfoBuilder(TRANSIT_SLEEP).build()
- callOnTransitionReady(transitionInfo)
-
- verifyTaskRemovedAndExitLogging(ExitReason.SCREEN_OFF, DEFAULT_TASK_UPDATE)
- }
-
- @Test
- fun transitExitDesktopTaskDrag_logTaskRemovedAndExitReasonDragToExit() {
- // add a freeform task
- transitionObserver.addTaskInfosToCachedMap(createTaskInfo(WINDOWING_MODE_FREEFORM))
- transitionObserver.isSessionActive = true
-
- // window mode changing from FREEFORM to FULLSCREEN
- val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(WINDOWING_MODE_FULLSCREEN))
- val transitionInfo =
- TransitionInfoBuilder(TRANSIT_EXIT_DESKTOP_MODE_TASK_DRAG).addChange(change).build()
- callOnTransitionReady(transitionInfo)
-
- verifyTaskRemovedAndExitLogging(ExitReason.DRAG_TO_EXIT, DEFAULT_TASK_UPDATE)
- }
-
- @Test
- fun transitExitDesktopAppHandleButton_logTaskRemovedAndExitReasonButton() {
- // add a freeform task
- transitionObserver.addTaskInfosToCachedMap(createTaskInfo(WINDOWING_MODE_FREEFORM))
- transitionObserver.isSessionActive = true
-
- // window mode changing from FREEFORM to FULLSCREEN
- val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(WINDOWING_MODE_FULLSCREEN))
- val transitionInfo =
- TransitionInfoBuilder(TRANSIT_EXIT_DESKTOP_MODE_HANDLE_MENU_BUTTON)
- .addChange(change)
- .build()
- callOnTransitionReady(transitionInfo)
-
- verifyTaskRemovedAndExitLogging(ExitReason.APP_HANDLE_MENU_BUTTON_EXIT, DEFAULT_TASK_UPDATE)
- }
-
- @Test
- fun transitExitDesktopUsingKeyboard_logTaskRemovedAndExitReasonKeyboard() {
- // add a freeform task
- transitionObserver.addTaskInfosToCachedMap(createTaskInfo(WINDOWING_MODE_FREEFORM))
- transitionObserver.isSessionActive = true
-
- // window mode changing from FREEFORM to FULLSCREEN
- val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(WINDOWING_MODE_FULLSCREEN))
- val transitionInfo =
- TransitionInfoBuilder(TRANSIT_EXIT_DESKTOP_MODE_KEYBOARD_SHORTCUT).addChange(change).build()
- callOnTransitionReady(transitionInfo)
-
- verifyTaskRemovedAndExitLogging(ExitReason.KEYBOARD_SHORTCUT_EXIT, DEFAULT_TASK_UPDATE)
- }
-
- @Test
- fun transitExitDesktopUnknown_logTaskRemovedAndExitReasonUnknown() {
- // add a freeform task
- transitionObserver.addTaskInfosToCachedMap(createTaskInfo(WINDOWING_MODE_FREEFORM))
- transitionObserver.isSessionActive = true
-
- // window mode changing from FREEFORM to FULLSCREEN
- val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(WINDOWING_MODE_FULLSCREEN))
- val transitionInfo =
- TransitionInfoBuilder(TRANSIT_EXIT_DESKTOP_MODE_UNKNOWN).addChange(change).build()
- callOnTransitionReady(transitionInfo)
-
- verifyTaskRemovedAndExitLogging(ExitReason.UNKNOWN_EXIT, DEFAULT_TASK_UPDATE)
- }
-
- @Test
- fun transitToFrontWithFlagRecents_logTaskRemovedAndExitReasonOverview() {
- // add a freeform task
- transitionObserver.addTaskInfosToCachedMap(createTaskInfo(WINDOWING_MODE_FREEFORM))
- transitionObserver.isSessionActive = true
-
- // recents transition
- val change = createChange(TRANSIT_TO_BACK, createTaskInfo(WINDOWING_MODE_FREEFORM))
- val transitionInfo =
- TransitionInfoBuilder(TRANSIT_TO_FRONT, TRANSIT_FLAG_IS_RECENTS).addChange(change).build()
- callOnTransitionReady(transitionInfo)
-
- verifyTaskRemovedAndExitLogging(
- ExitReason.RETURN_HOME_OR_OVERVIEW, DEFAULT_TASK_UPDATE
- )
- }
-
- @Test
- fun transitClose_logTaskRemovedAndExitReasonTaskFinished() {
- // add a freeform task
- transitionObserver.addTaskInfosToCachedMap(createTaskInfo(WINDOWING_MODE_FREEFORM))
- transitionObserver.isSessionActive = true
-
- // task closing
- val change = createChange(TRANSIT_CLOSE, createTaskInfo(WINDOWING_MODE_FULLSCREEN))
- val transitionInfo = TransitionInfoBuilder(TRANSIT_CLOSE).addChange(change).build()
- callOnTransitionReady(transitionInfo)
-
- verifyTaskRemovedAndExitLogging(ExitReason.TASK_FINISHED, DEFAULT_TASK_UPDATE)
- }
-
- @Test
- fun transitMinimize_logExitReasongMinimized() {
- // add a freeform task
- transitionObserver.addTaskInfosToCachedMap(createTaskInfo(WINDOWING_MODE_FREEFORM))
- transitionObserver.isSessionActive = true
-
- // minimize the task
- val change = createChange(TRANSIT_MINIMIZE, createTaskInfo(WINDOWING_MODE_FULLSCREEN))
- val transitionInfo = TransitionInfoBuilder(TRANSIT_MINIMIZE).addChange(change).build()
- callOnTransitionReady(transitionInfo)
-
- assertFalse(transitionObserver.isSessionActive)
- verify(desktopModeEventLogger, times(1)).logSessionExit(eq(ExitReason.TASK_MINIMIZED))
- verify(desktopModeEventLogger, times(1)).logTaskRemoved(eq(DEFAULT_TASK_UPDATE))
- verifyZeroInteractions(desktopModeEventLogger)
- }
-
- @Test
- fun sessionExitByRecents_cancelledAnimation_sessionRestored() {
- // add a freeform task to an existing session
- val taskInfo = createTaskInfo(WINDOWING_MODE_FREEFORM)
- transitionObserver.addTaskInfosToCachedMap(taskInfo)
- transitionObserver.isSessionActive = true
-
- // recents transition sent freeform window to back
- val change = createChange(TRANSIT_TO_BACK, taskInfo)
- val transitionInfo1 =
- TransitionInfoBuilder(TRANSIT_TO_FRONT, TRANSIT_FLAG_IS_RECENTS).addChange(change).build()
- callOnTransitionReady(transitionInfo1)
-
- verifyTaskRemovedAndExitLogging(
- ExitReason.RETURN_HOME_OR_OVERVIEW, DEFAULT_TASK_UPDATE
- )
-
- val transitionInfo2 = TransitionInfoBuilder(TRANSIT_NONE).build()
- callOnTransitionReady(transitionInfo2)
-
- verifyTaskAddedAndEnterLogging(EnterReason.OVERVIEW,
- DEFAULT_TASK_UPDATE.copy(visibleTaskCount = 1))
- }
-
- @Test
- fun sessionAlreadyStarted_newFreeformTaskAdded_logsTaskAdded() {
- // add an existing freeform task
- transitionObserver.addTaskInfosToCachedMap(createTaskInfo(WINDOWING_MODE_FREEFORM))
- transitionObserver.isSessionActive = true
-
- // new freeform task added
- val change = createChange(TRANSIT_OPEN, createTaskInfo(WINDOWING_MODE_FREEFORM, id = 2))
- val transitionInfo = TransitionInfoBuilder(TRANSIT_OPEN, 0).addChange(change).build()
- callOnTransitionReady(transitionInfo)
-
- verify(desktopModeEventLogger, times(1))
- .logTaskAdded(eq(DEFAULT_TASK_UPDATE.copy(instanceId = 2, visibleTaskCount = 2)))
- verify(desktopModeEventLogger, never()).logSessionEnter(any())
- }
-
- @Test
- fun sessionAlreadyStarted_taskPositionChanged_logsTaskUpdate() {
- // add an existing freeform task
- val taskInfo = createTaskInfo(WINDOWING_MODE_FREEFORM)
- transitionObserver.addTaskInfosToCachedMap(taskInfo)
- transitionObserver.isSessionActive = true
-
- // task position changed
- val newTaskInfo = createTaskInfo(WINDOWING_MODE_FREEFORM, taskX = DEFAULT_TASK_X + 100)
- val transitionInfo =
- TransitionInfoBuilder(TRANSIT_CHANGE, 0)
- .addChange(createChange(TRANSIT_CHANGE, newTaskInfo))
- .build()
- callOnTransitionReady(transitionInfo)
-
- verify(desktopModeEventLogger, times(1))
- .logTaskInfoChanged(
- eq(DEFAULT_TASK_UPDATE.copy(taskX = DEFAULT_TASK_X + 100, visibleTaskCount = 1))
+ @JvmField
+ @Rule
+ val extendedMockitoRule =
+ ExtendedMockitoRule.Builder(this)
+ .mockStatic(DesktopModeStatus::class.java)
+ .mockStatic(SystemProperties::class.java)
+ .mockStatic(Trace::class.java)
+ .build()!!
+
+ private val testExecutor = mock<ShellExecutor>()
+ private val mockShellInit = mock<ShellInit>()
+ private val transitions = mock<Transitions>()
+ private val context = mock<Context>()
+
+ private lateinit var transitionObserver: DesktopModeLoggerTransitionObserver
+ private lateinit var shellInit: ShellInit
+ private lateinit var desktopModeEventLogger: DesktopModeEventLogger
+
+ @Before
+ fun setup() {
+ whenever(DesktopModeStatus.canEnterDesktopMode(any())).thenReturn(true)
+ shellInit = spy(ShellInit(testExecutor))
+ desktopModeEventLogger = mock<DesktopModeEventLogger>()
+
+ transitionObserver =
+ DesktopModeLoggerTransitionObserver(
+ context,
+ mockShellInit,
+ transitions,
+ desktopModeEventLogger,
+ )
+ val initRunnableCaptor = ArgumentCaptor.forClass(Runnable::class.java)
+ verify(mockShellInit)
+ .addInitCallback(initRunnableCaptor.capture(), same(transitionObserver))
+ initRunnableCaptor.value.run()
+ // verify this initialisation interaction to leave the desktopmodeEventLogger mock in a
+ // consistent state with no outstanding interactions when test cases start executing.
+ verify(desktopModeEventLogger).logTaskInfoStateInit()
+ }
+
+ @Test
+ fun testInitialiseVisibleTasksSystemProperty() {
+ ExtendedMockito.verify {
+ SystemProperties.set(
+ eq(DesktopModeLoggerTransitionObserver.VISIBLE_TASKS_COUNTER_SYSTEM_PROPERTY),
+ eq(
+ DesktopModeLoggerTransitionObserver
+ .VISIBLE_TASKS_COUNTER_SYSTEM_PROPERTY_DEFAULT_VALUE
+ ),
+ )
+ }
+ }
+
+ @Test
+ fun testRegistersObserverAtInit() {
+ verify(transitions).registerObserver(same(transitionObserver))
+ }
+
+ @Test
+ fun transitOpen_notFreeformWindow_doesNotLogTaskAddedOrSessionEnter() {
+ val change = createChange(TRANSIT_OPEN, createTaskInfo(WINDOWING_MODE_FULLSCREEN))
+ val transitionInfo = TransitionInfoBuilder(TRANSIT_OPEN, 0).addChange(change).build()
+
+ callOnTransitionReady(transitionInfo)
+
+ verify(desktopModeEventLogger, never()).logSessionEnter(any())
+ verify(desktopModeEventLogger, never()).logTaskAdded(any())
+ }
+
+ @Test
+ fun transitOpen_logTaskAddedAndEnterReasonAppFreeformIntent() {
+ val change = createChange(TRANSIT_OPEN, createTaskInfo(WINDOWING_MODE_FREEFORM))
+ val transitionInfo = TransitionInfoBuilder(TRANSIT_OPEN, 0).addChange(change).build()
+
+ callOnTransitionReady(transitionInfo)
+
+ verifyTaskAddedAndEnterLogging(
+ EnterReason.APP_FREEFORM_INTENT,
+ DEFAULT_TASK_UPDATE.copy(visibleTaskCount = 1),
)
- verifyZeroInteractions(desktopModeEventLogger)
- }
-
- @Test
- fun sessionAlreadyStarted_taskResized_logsTaskUpdate() {
- // add an existing freeform task
- val taskInfo = createTaskInfo(WINDOWING_MODE_FREEFORM)
- transitionObserver.addTaskInfosToCachedMap(taskInfo)
- transitionObserver.isSessionActive = true
-
- // task resized
- val newTaskInfo =
- createTaskInfo(
- WINDOWING_MODE_FREEFORM,
- taskWidth = DEFAULT_TASK_WIDTH + 100,
- taskHeight = DEFAULT_TASK_HEIGHT - 100)
- val transitionInfo =
- TransitionInfoBuilder(TRANSIT_CHANGE, 0)
- .addChange(createChange(TRANSIT_CHANGE, newTaskInfo))
- .build()
- callOnTransitionReady(transitionInfo)
-
- verify(desktopModeEventLogger, times(1))
- .logTaskInfoChanged(
- eq(
- DEFAULT_TASK_UPDATE.copy(
- taskWidth = DEFAULT_TASK_WIDTH + 100, taskHeight = DEFAULT_TASK_HEIGHT - 100,
- visibleTaskCount = 1))
+ }
+
+ @Test
+ fun transitEndDragToDesktop_logTaskAddedAndEnterReasonAppHandleDrag() {
+ val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(WINDOWING_MODE_FREEFORM))
+ // task change is finalised when drag ends
+ val transitionInfo =
+ TransitionInfoBuilder(Transitions.TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP, 0)
+ .addChange(change)
+ .build()
+
+ callOnTransitionReady(transitionInfo)
+
+ verifyTaskAddedAndEnterLogging(
+ EnterReason.APP_HANDLE_DRAG,
+ DEFAULT_TASK_UPDATE.copy(visibleTaskCount = 1),
)
- verifyZeroInteractions(desktopModeEventLogger)
- }
-
- @Test
- fun sessionAlreadyStarted_multipleTasksUpdated_logsTaskUpdateForCorrectTask() {
- // add 2 existing freeform task
- val taskInfo1 = createTaskInfo(WINDOWING_MODE_FREEFORM)
- val taskInfo2 = createTaskInfo(WINDOWING_MODE_FREEFORM, id = 2)
- transitionObserver.addTaskInfosToCachedMap(taskInfo1)
- transitionObserver.addTaskInfosToCachedMap(taskInfo2)
- transitionObserver.isSessionActive = true
-
- // task 1 position update
- val newTaskInfo1 = createTaskInfo(WINDOWING_MODE_FREEFORM, taskX = DEFAULT_TASK_X + 100)
- val transitionInfo1 =
- TransitionInfoBuilder(TRANSIT_CHANGE, 0)
- .addChange(createChange(TRANSIT_CHANGE, newTaskInfo1))
- .build()
- callOnTransitionReady(transitionInfo1)
-
- verify(desktopModeEventLogger, times(1))
- .logTaskInfoChanged(
- eq(DEFAULT_TASK_UPDATE.copy(
- taskX = DEFAULT_TASK_X + 100, visibleTaskCount = 2))
+ }
+
+ @Test
+ fun transitEnterDesktopByButtonTap_logTaskAddedAndEnterReasonButtonTap() {
+ val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(WINDOWING_MODE_FREEFORM))
+ val transitionInfo =
+ TransitionInfoBuilder(TRANSIT_ENTER_DESKTOP_FROM_APP_HANDLE_MENU_BUTTON, 0)
+ .addChange(change)
+ .build()
+
+ callOnTransitionReady(transitionInfo)
+
+ verifyTaskAddedAndEnterLogging(
+ EnterReason.APP_HANDLE_MENU_BUTTON,
+ DEFAULT_TASK_UPDATE.copy(visibleTaskCount = 1),
)
- verifyZeroInteractions(desktopModeEventLogger)
-
- // task 2 resize
- val newTaskInfo2 =
- createTaskInfo(
- WINDOWING_MODE_FREEFORM,
- id = 2,
- taskWidth = DEFAULT_TASK_WIDTH + 100,
- taskHeight = DEFAULT_TASK_HEIGHT - 100)
- val transitionInfo2 =
- TransitionInfoBuilder(TRANSIT_CHANGE, 0)
- .addChange(createChange(TRANSIT_CHANGE, newTaskInfo2))
- .build()
-
- callOnTransitionReady(transitionInfo2)
-
- verify(desktopModeEventLogger, times(1))
- .logTaskInfoChanged(
- eq(
- DEFAULT_TASK_UPDATE.copy(
- instanceId = 2,
- taskWidth = DEFAULT_TASK_WIDTH + 100,
- taskHeight = DEFAULT_TASK_HEIGHT - 100,
- visibleTaskCount = 2)),
- )
- verifyZeroInteractions(desktopModeEventLogger)
- }
-
- @Test
- fun sessionAlreadyStarted_freeformTaskRemoved_logsTaskRemoved() {
- // add two existing freeform tasks
- transitionObserver.addTaskInfosToCachedMap(createTaskInfo(WINDOWING_MODE_FREEFORM))
- transitionObserver.addTaskInfosToCachedMap(createTaskInfo(WINDOWING_MODE_FREEFORM, id = 2))
- transitionObserver.isSessionActive = true
-
- // new freeform task closed
- val change = createChange(TRANSIT_CLOSE, createTaskInfo(WINDOWING_MODE_FREEFORM, id = 2))
- val transitionInfo = TransitionInfoBuilder(TRANSIT_CLOSE, 0).addChange(change).build()
- callOnTransitionReady(transitionInfo)
-
- verify(desktopModeEventLogger, times(1))
- .logTaskRemoved(
- eq(DEFAULT_TASK_UPDATE.copy(
- instanceId = 2, visibleTaskCount = 1))
+ }
+
+ @Test
+ fun transitEnterDesktopFromAppFromOverview_logTaskAddedAndEnterReasonAppFromOverview() {
+ val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(WINDOWING_MODE_FREEFORM))
+ val transitionInfo =
+ TransitionInfoBuilder(TRANSIT_ENTER_DESKTOP_FROM_APP_FROM_OVERVIEW, 0)
+ .addChange(change)
+ .build()
+
+ callOnTransitionReady(transitionInfo)
+
+ verifyTaskAddedAndEnterLogging(
+ EnterReason.APP_FROM_OVERVIEW,
+ DEFAULT_TASK_UPDATE.copy(visibleTaskCount = 1),
)
- verify(desktopModeEventLogger, never()).logSessionExit(any())
- }
-
- /** Simulate calling the onTransitionReady() method */
- private fun callOnTransitionReady(transitionInfo: TransitionInfo) {
- val transition = mock<IBinder>()
- val startT = mock<SurfaceControl.Transaction>()
- val finishT = mock<SurfaceControl.Transaction>()
-
- transitionObserver.onTransitionReady(transition, transitionInfo, startT, finishT)
- }
-
- private fun verifyTaskAddedAndEnterLogging(enterReason: EnterReason, taskUpdate: TaskUpdate) {
- assertTrue(transitionObserver.isSessionActive)
- verify(desktopModeEventLogger, times(1)).logSessionEnter(eq(enterReason))
- verify(desktopModeEventLogger, times(1)).logTaskAdded(eq(taskUpdate))
- ExtendedMockito.verify {
- Trace.setCounter(
- eq(Trace.TRACE_TAG_WINDOW_MANAGER),
- eq(DesktopModeLoggerTransitionObserver.VISIBLE_TASKS_COUNTER_NAME),
- eq(taskUpdate.visibleTaskCount.toLong()))
- }
- ExtendedMockito.verify {
- SystemProperties.set(
- eq(DesktopModeLoggerTransitionObserver.VISIBLE_TASKS_COUNTER_SYSTEM_PROPERTY),
- eq(taskUpdate.visibleTaskCount.toString()))
- }
- verifyZeroInteractions(desktopModeEventLogger)
- }
-
- private fun verifyTaskRemovedAndExitLogging(
- exitReason: ExitReason,
- taskUpdate: TaskUpdate
- ) {
- assertFalse(transitionObserver.isSessionActive)
- verify(desktopModeEventLogger, times(1)).logTaskRemoved(eq(taskUpdate))
- verify(desktopModeEventLogger, times(1)).logSessionExit(eq(exitReason))
- verifyZeroInteractions(desktopModeEventLogger)
- }
-
- private companion object {
- const val DEFAULT_TASK_ID = 1
- const val DEFAULT_TASK_UID = 2
- const val DEFAULT_TASK_HEIGHT = 100
- const val DEFAULT_TASK_WIDTH = 200
- const val DEFAULT_TASK_X = 30
- const val DEFAULT_TASK_Y = 70
- const val DEFAULT_VISIBLE_TASK_COUNT = 0
- val DEFAULT_TASK_UPDATE =
- TaskUpdate(
- DEFAULT_TASK_ID,
- DEFAULT_TASK_UID,
- DEFAULT_TASK_HEIGHT,
- DEFAULT_TASK_WIDTH,
- DEFAULT_TASK_X,
- DEFAULT_TASK_Y,
- visibleTaskCount = DEFAULT_VISIBLE_TASK_COUNT,
+ }
+
+ @Test
+ fun transitEnterDesktopFromKeyboardShortcut_logTaskAddedAndEnterReasonKeyboardShortcut() {
+ val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(WINDOWING_MODE_FREEFORM))
+ val transitionInfo =
+ TransitionInfoBuilder(TRANSIT_ENTER_DESKTOP_FROM_KEYBOARD_SHORTCUT, 0)
+ .addChange(change)
+ .build()
+
+ callOnTransitionReady(transitionInfo)
+
+ verifyTaskAddedAndEnterLogging(
+ EnterReason.KEYBOARD_SHORTCUT_ENTER,
+ DEFAULT_TASK_UPDATE.copy(visibleTaskCount = 1),
)
+ }
+
+ @Test
+ fun transitToFront_logTaskAddedAndEnterReasonOverview() {
+ val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(WINDOWING_MODE_FREEFORM))
+ val transitionInfo = TransitionInfoBuilder(TRANSIT_TO_FRONT, 0).addChange(change).build()
- fun createTaskInfo(
- windowMode: Int,
- id: Int = DEFAULT_TASK_ID,
- uid: Int = DEFAULT_TASK_UID,
- taskHeight: Int = DEFAULT_TASK_HEIGHT,
- taskWidth: Int = DEFAULT_TASK_WIDTH,
- taskX: Int = DEFAULT_TASK_X,
- taskY: Int = DEFAULT_TASK_Y,
- ) =
- ActivityManager.RunningTaskInfo().apply {
- taskId = id
- effectiveUid = uid
- configuration.windowConfiguration.apply {
- windowingMode = windowMode
- positionInParent = Point(taskX, taskY)
- bounds.set(Rect(taskX, taskY, taskX + taskWidth, taskY + taskHeight))
- }
+ callOnTransitionReady(transitionInfo)
+
+ verifyTaskAddedAndEnterLogging(
+ EnterReason.OVERVIEW,
+ DEFAULT_TASK_UPDATE.copy(visibleTaskCount = 1),
+ )
+ }
+
+ @Test
+ fun transitToFront_previousTransitionExitToOverview_logTaskAddedAndEnterReasonOverview() {
+ // previous exit to overview transition
+ // add a freeform task
+ val previousTaskInfo = createTaskInfo(WINDOWING_MODE_FREEFORM)
+ transitionObserver.addTaskInfosToCachedMap(previousTaskInfo)
+ transitionObserver.isSessionActive = true
+ val previousTransitionInfo =
+ TransitionInfoBuilder(TRANSIT_TO_FRONT, TRANSIT_FLAG_IS_RECENTS)
+ .addChange(createChange(TRANSIT_TO_BACK, previousTaskInfo))
+ .build()
+
+ callOnTransitionReady(previousTransitionInfo)
+
+ verifyTaskRemovedAndExitLogging(ExitReason.RETURN_HOME_OR_OVERVIEW, DEFAULT_TASK_UPDATE)
+
+ // Enter desktop mode from cancelled recents has no transition. Enter is detected on the
+ // next transition involving freeform windows
+
+ // TRANSIT_TO_FRONT
+ val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(WINDOWING_MODE_FREEFORM))
+ val transitionInfo = TransitionInfoBuilder(TRANSIT_TO_FRONT, 0).addChange(change).build()
+
+ callOnTransitionReady(transitionInfo)
+
+ verifyTaskAddedAndEnterLogging(
+ EnterReason.OVERVIEW,
+ DEFAULT_TASK_UPDATE.copy(visibleTaskCount = 1),
+ )
+ }
+
+ @Test
+ fun transitChange_previousTransitionExitToOverview_logTaskAddedAndEnterReasonOverview() {
+ // previous exit to overview transition
+ // add a freeform task
+ val previousTaskInfo = createTaskInfo(WINDOWING_MODE_FREEFORM)
+ transitionObserver.addTaskInfosToCachedMap(previousTaskInfo)
+ transitionObserver.isSessionActive = true
+ val previousTransitionInfo =
+ TransitionInfoBuilder(TRANSIT_TO_FRONT, TRANSIT_FLAG_IS_RECENTS)
+ .addChange(createChange(TRANSIT_TO_BACK, previousTaskInfo))
+ .build()
+
+ callOnTransitionReady(previousTransitionInfo)
+
+ verifyTaskRemovedAndExitLogging(ExitReason.RETURN_HOME_OR_OVERVIEW, DEFAULT_TASK_UPDATE)
+
+ // Enter desktop mode from cancelled recents has no transition. Enter is detected on the
+ // next transition involving freeform windows
+
+ // TRANSIT_CHANGE
+ val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(WINDOWING_MODE_FREEFORM))
+ val transitionInfo = TransitionInfoBuilder(TRANSIT_CHANGE, 0).addChange(change).build()
+
+ callOnTransitionReady(transitionInfo)
+
+ verifyTaskAddedAndEnterLogging(
+ EnterReason.OVERVIEW,
+ DEFAULT_TASK_UPDATE.copy(visibleTaskCount = 1),
+ )
+ }
+
+ @Test
+ fun transitOpen_previousTransitionExitToOverview_logTaskAddedAndEnterReasonOverview() {
+ // previous exit to overview transition
+ // add a freeform task
+ val previousTaskInfo = createTaskInfo(WINDOWING_MODE_FREEFORM)
+ transitionObserver.addTaskInfosToCachedMap(previousTaskInfo)
+ transitionObserver.isSessionActive = true
+ val previousTransitionInfo =
+ TransitionInfoBuilder(TRANSIT_TO_FRONT, TRANSIT_FLAG_IS_RECENTS)
+ .addChange(createChange(TRANSIT_TO_BACK, previousTaskInfo))
+ .build()
+
+ callOnTransitionReady(previousTransitionInfo)
+
+ verifyTaskRemovedAndExitLogging(ExitReason.RETURN_HOME_OR_OVERVIEW, DEFAULT_TASK_UPDATE)
+
+ // Enter desktop mode from cancelled recents has no transition. Enter is detected on the
+ // next transition involving freeform windows
+
+ // TRANSIT_OPEN
+ val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(WINDOWING_MODE_FREEFORM))
+ val transitionInfo = TransitionInfoBuilder(TRANSIT_OPEN, 0).addChange(change).build()
+
+ callOnTransitionReady(transitionInfo)
+
+ verifyTaskAddedAndEnterLogging(
+ EnterReason.OVERVIEW,
+ DEFAULT_TASK_UPDATE.copy(visibleTaskCount = 1),
+ )
+ }
+
+ @Test
+ @Suppress("ktlint:standard:max-line-length")
+ fun transitEnterDesktopFromAppFromOverview_previousTransitionExitToOverview_logTaskAddedAndEnterReasonAppFromOverview() {
+ // Tests for AppFromOverview precedence in compared to cancelled Overview
+
+ // previous exit to overview transition
+ // add a freeform task
+ val previousTaskInfo = createTaskInfo(WINDOWING_MODE_FREEFORM)
+ transitionObserver.addTaskInfosToCachedMap(previousTaskInfo)
+ transitionObserver.isSessionActive = true
+ val previousTransitionInfo =
+ TransitionInfoBuilder(TRANSIT_TO_FRONT, TRANSIT_FLAG_IS_RECENTS)
+ .addChange(createChange(TRANSIT_TO_BACK, previousTaskInfo))
+ .build()
+
+ callOnTransitionReady(previousTransitionInfo)
+
+ verifyTaskRemovedAndExitLogging(ExitReason.RETURN_HOME_OR_OVERVIEW, DEFAULT_TASK_UPDATE)
+
+ // TRANSIT_ENTER_DESKTOP_FROM_APP_FROM_OVERVIEW
+ val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(WINDOWING_MODE_FREEFORM))
+ val transitionInfo =
+ TransitionInfoBuilder(TRANSIT_ENTER_DESKTOP_FROM_APP_FROM_OVERVIEW, 0)
+ .addChange(change)
+ .build()
+
+ callOnTransitionReady(transitionInfo)
+
+ verifyTaskAddedAndEnterLogging(
+ EnterReason.APP_FROM_OVERVIEW,
+ DEFAULT_TASK_UPDATE.copy(visibleTaskCount = 1),
+ )
+ }
+
+ @Test
+ fun transitEnterDesktopFromUnknown_logTaskAddedAndEnterReasonUnknown() {
+ val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(WINDOWING_MODE_FREEFORM))
+ val transitionInfo =
+ TransitionInfoBuilder(TRANSIT_ENTER_DESKTOP_FROM_UNKNOWN, 0).addChange(change).build()
+
+ callOnTransitionReady(transitionInfo)
+
+ verifyTaskAddedAndEnterLogging(
+ EnterReason.UNKNOWN_ENTER,
+ DEFAULT_TASK_UPDATE.copy(visibleTaskCount = 1),
+ )
+ }
+
+ @Test
+ fun transitWake_logTaskAddedAndEnterReasonScreenOn() {
+ val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(WINDOWING_MODE_FREEFORM))
+ val transitionInfo = TransitionInfoBuilder(TRANSIT_WAKE, 0).addChange(change).build()
+
+ callOnTransitionReady(transitionInfo)
+
+ verifyTaskAddedAndEnterLogging(
+ EnterReason.SCREEN_ON,
+ DEFAULT_TASK_UPDATE.copy(visibleTaskCount = 1),
+ )
+ }
+
+ @Test
+ fun transitBack_previousExitReasonScreenOff_logTaskAddedAndEnterReasonScreenOn() {
+ val freeformTask = createTaskInfo(WINDOWING_MODE_FREEFORM)
+ // Previous Exit reason recorded as Screen Off
+ transitionObserver.addTaskInfosToCachedMap(freeformTask)
+ transitionObserver.isSessionActive = true
+ callOnTransitionReady(TransitionInfoBuilder(TRANSIT_SLEEP).build())
+ verifyTaskRemovedAndExitLogging(ExitReason.SCREEN_OFF, DEFAULT_TASK_UPDATE)
+ // Enter desktop through back transition, this happens when user enters after dismissing
+ // keyguard
+ val change = createChange(TRANSIT_TO_FRONT, freeformTask)
+ val transitionInfo = TransitionInfoBuilder(TRANSIT_TO_BACK, 0).addChange(change).build()
+
+ callOnTransitionReady(transitionInfo)
+
+ verifyTaskAddedAndEnterLogging(
+ EnterReason.SCREEN_ON,
+ DEFAULT_TASK_UPDATE.copy(visibleTaskCount = 1),
+ )
+ }
+
+ @Test
+ fun transitEndDragToDesktop_previousExitReasonScreenOff_logTaskAddedAndEnterReasonAppDrag() {
+ val freeformTask = createTaskInfo(WINDOWING_MODE_FREEFORM)
+ // Previous Exit reason recorded as Screen Off
+ transitionObserver.addTaskInfosToCachedMap(freeformTask)
+ transitionObserver.isSessionActive = true
+ callOnTransitionReady(TransitionInfoBuilder(TRANSIT_SLEEP).build())
+ verifyTaskRemovedAndExitLogging(ExitReason.SCREEN_OFF, DEFAULT_TASK_UPDATE)
+
+ // Enter desktop through app handle drag. This represents cases where instead of moving to
+ // desktop right after turning the screen on, we move to fullscreen then move another task
+ // to desktop
+ val transitionInfo =
+ TransitionInfoBuilder(Transitions.TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP, 0)
+ .addChange(createChange(TRANSIT_TO_FRONT, freeformTask))
+ .build()
+ callOnTransitionReady(transitionInfo)
+
+ verifyTaskAddedAndEnterLogging(
+ EnterReason.APP_HANDLE_DRAG,
+ DEFAULT_TASK_UPDATE.copy(visibleTaskCount = 1),
+ )
+ }
+
+ @Test
+ fun transitSleep_logTaskRemovedAndExitReasonScreenOff() {
+ // add a freeform task
+ transitionObserver.addTaskInfosToCachedMap(createTaskInfo(WINDOWING_MODE_FREEFORM))
+ transitionObserver.isSessionActive = true
+
+ val transitionInfo = TransitionInfoBuilder(TRANSIT_SLEEP).build()
+ callOnTransitionReady(transitionInfo)
+
+ verifyTaskRemovedAndExitLogging(ExitReason.SCREEN_OFF, DEFAULT_TASK_UPDATE)
+ }
+
+ @Test
+ fun transitExitDesktopTaskDrag_logTaskRemovedAndExitReasonDragToExit() {
+ // add a freeform task
+ transitionObserver.addTaskInfosToCachedMap(createTaskInfo(WINDOWING_MODE_FREEFORM))
+ transitionObserver.isSessionActive = true
+
+ // window mode changing from FREEFORM to FULLSCREEN
+ val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(WINDOWING_MODE_FULLSCREEN))
+ val transitionInfo =
+ TransitionInfoBuilder(TRANSIT_EXIT_DESKTOP_MODE_TASK_DRAG).addChange(change).build()
+ callOnTransitionReady(transitionInfo)
+
+ verifyTaskRemovedAndExitLogging(ExitReason.DRAG_TO_EXIT, DEFAULT_TASK_UPDATE)
+ }
+
+ @Test
+ fun transitExitDesktopAppHandleButton_logTaskRemovedAndExitReasonButton() {
+ // add a freeform task
+ transitionObserver.addTaskInfosToCachedMap(createTaskInfo(WINDOWING_MODE_FREEFORM))
+ transitionObserver.isSessionActive = true
+
+ // window mode changing from FREEFORM to FULLSCREEN
+ val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(WINDOWING_MODE_FULLSCREEN))
+ val transitionInfo =
+ TransitionInfoBuilder(TRANSIT_EXIT_DESKTOP_MODE_HANDLE_MENU_BUTTON)
+ .addChange(change)
+ .build()
+ callOnTransitionReady(transitionInfo)
+
+ verifyTaskRemovedAndExitLogging(ExitReason.APP_HANDLE_MENU_BUTTON_EXIT, DEFAULT_TASK_UPDATE)
+ }
+
+ @Test
+ fun transitExitDesktopUsingKeyboard_logTaskRemovedAndExitReasonKeyboard() {
+ // add a freeform task
+ transitionObserver.addTaskInfosToCachedMap(createTaskInfo(WINDOWING_MODE_FREEFORM))
+ transitionObserver.isSessionActive = true
+
+ // window mode changing from FREEFORM to FULLSCREEN
+ val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(WINDOWING_MODE_FULLSCREEN))
+ val transitionInfo =
+ TransitionInfoBuilder(TRANSIT_EXIT_DESKTOP_MODE_KEYBOARD_SHORTCUT)
+ .addChange(change)
+ .build()
+ callOnTransitionReady(transitionInfo)
+
+ verifyTaskRemovedAndExitLogging(ExitReason.KEYBOARD_SHORTCUT_EXIT, DEFAULT_TASK_UPDATE)
+ }
+
+ @Test
+ fun transitExitDesktopUnknown_logTaskRemovedAndExitReasonUnknown() {
+ // add a freeform task
+ transitionObserver.addTaskInfosToCachedMap(createTaskInfo(WINDOWING_MODE_FREEFORM))
+ transitionObserver.isSessionActive = true
+
+ // window mode changing from FREEFORM to FULLSCREEN
+ val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(WINDOWING_MODE_FULLSCREEN))
+ val transitionInfo =
+ TransitionInfoBuilder(TRANSIT_EXIT_DESKTOP_MODE_UNKNOWN).addChange(change).build()
+ callOnTransitionReady(transitionInfo)
+
+ verifyTaskRemovedAndExitLogging(ExitReason.UNKNOWN_EXIT, DEFAULT_TASK_UPDATE)
+ }
+
+ @Test
+ fun transitToFrontWithFlagRecents_logTaskRemovedAndExitReasonOverview() {
+ // add a freeform task
+ transitionObserver.addTaskInfosToCachedMap(createTaskInfo(WINDOWING_MODE_FREEFORM))
+ transitionObserver.isSessionActive = true
+
+ // recents transition
+ val change = createChange(TRANSIT_TO_BACK, createTaskInfo(WINDOWING_MODE_FREEFORM))
+ val transitionInfo =
+ TransitionInfoBuilder(TRANSIT_TO_FRONT, TRANSIT_FLAG_IS_RECENTS)
+ .addChange(change)
+ .build()
+ callOnTransitionReady(transitionInfo)
+
+ verifyTaskRemovedAndExitLogging(ExitReason.RETURN_HOME_OR_OVERVIEW, DEFAULT_TASK_UPDATE)
+ }
+
+ @Test
+ fun transitClose_logTaskRemovedAndExitReasonTaskFinished() {
+ // add a freeform task
+ transitionObserver.addTaskInfosToCachedMap(createTaskInfo(WINDOWING_MODE_FREEFORM))
+ transitionObserver.isSessionActive = true
+
+ // task closing
+ val change = createChange(TRANSIT_CLOSE, createTaskInfo(WINDOWING_MODE_FULLSCREEN))
+ val transitionInfo = TransitionInfoBuilder(TRANSIT_CLOSE).addChange(change).build()
+ callOnTransitionReady(transitionInfo)
+
+ verifyTaskRemovedAndExitLogging(ExitReason.TASK_FINISHED, DEFAULT_TASK_UPDATE)
+ }
+
+ @Test
+ fun transitMinimize_logExitReasongMinimized() {
+ // add a freeform task
+ transitionObserver.addTaskInfosToCachedMap(createTaskInfo(WINDOWING_MODE_FREEFORM))
+ transitionObserver.isSessionActive = true
+
+ // minimize the task
+ val change = createChange(TRANSIT_MINIMIZE, createTaskInfo(WINDOWING_MODE_FULLSCREEN))
+ val transitionInfo = TransitionInfoBuilder(TRANSIT_MINIMIZE).addChange(change).build()
+ callOnTransitionReady(transitionInfo)
+
+ assertFalse(transitionObserver.isSessionActive)
+ verify(desktopModeEventLogger, times(1)).logSessionExit(eq(ExitReason.TASK_MINIMIZED))
+ verify(desktopModeEventLogger, times(1)).logTaskRemoved(eq(DEFAULT_TASK_UPDATE))
+ verifyZeroInteractions(desktopModeEventLogger)
+ }
+
+ @Test
+ fun sessionExitByRecents_cancelledAnimation_sessionRestored() {
+ // add a freeform task to an existing session
+ val taskInfo = createTaskInfo(WINDOWING_MODE_FREEFORM)
+ transitionObserver.addTaskInfosToCachedMap(taskInfo)
+ transitionObserver.isSessionActive = true
+
+ // recents transition sent freeform window to back
+ val change = createChange(TRANSIT_TO_BACK, taskInfo)
+ val transitionInfo1 =
+ TransitionInfoBuilder(TRANSIT_TO_FRONT, TRANSIT_FLAG_IS_RECENTS)
+ .addChange(change)
+ .build()
+ callOnTransitionReady(transitionInfo1)
+
+ verifyTaskRemovedAndExitLogging(ExitReason.RETURN_HOME_OR_OVERVIEW, DEFAULT_TASK_UPDATE)
+
+ val transitionInfo2 = TransitionInfoBuilder(TRANSIT_NONE).build()
+ callOnTransitionReady(transitionInfo2)
+
+ verifyTaskAddedAndEnterLogging(
+ EnterReason.OVERVIEW,
+ DEFAULT_TASK_UPDATE.copy(visibleTaskCount = 1),
+ )
+ }
+
+ @Test
+ fun sessionAlreadyStarted_newFreeformTaskAdded_logsTaskAdded() {
+ // add an existing freeform task
+ transitionObserver.addTaskInfosToCachedMap(createTaskInfo(WINDOWING_MODE_FREEFORM))
+ transitionObserver.isSessionActive = true
+
+ // new freeform task added
+ val change = createChange(TRANSIT_OPEN, createTaskInfo(WINDOWING_MODE_FREEFORM, id = 2))
+ val transitionInfo = TransitionInfoBuilder(TRANSIT_OPEN, 0).addChange(change).build()
+ callOnTransitionReady(transitionInfo)
+
+ verify(desktopModeEventLogger, times(1))
+ .logTaskAdded(eq(DEFAULT_TASK_UPDATE.copy(instanceId = 2, visibleTaskCount = 2)))
+ verify(desktopModeEventLogger, never()).logSessionEnter(any())
+ }
+
+ @Test
+ fun sessionAlreadyStarted_taskPositionChanged_logsTaskUpdate() {
+ // add an existing freeform task
+ val taskInfo = createTaskInfo(WINDOWING_MODE_FREEFORM)
+ transitionObserver.addTaskInfosToCachedMap(taskInfo)
+ transitionObserver.isSessionActive = true
+
+ // task position changed
+ val newTaskInfo = createTaskInfo(WINDOWING_MODE_FREEFORM, taskX = DEFAULT_TASK_X + 100)
+ val transitionInfo =
+ TransitionInfoBuilder(TRANSIT_CHANGE, 0)
+ .addChange(createChange(TRANSIT_CHANGE, newTaskInfo))
+ .build()
+ callOnTransitionReady(transitionInfo)
+
+ verify(desktopModeEventLogger, times(1))
+ .logTaskInfoChanged(
+ eq(DEFAULT_TASK_UPDATE.copy(taskX = DEFAULT_TASK_X + 100, visibleTaskCount = 1))
+ )
+ verifyZeroInteractions(desktopModeEventLogger)
+ }
+
+ @Test
+ fun sessionAlreadyStarted_taskResized_logsTaskUpdate() {
+ // add an existing freeform task
+ val taskInfo = createTaskInfo(WINDOWING_MODE_FREEFORM)
+ transitionObserver.addTaskInfosToCachedMap(taskInfo)
+ transitionObserver.isSessionActive = true
+
+ // task resized
+ val newTaskInfo =
+ createTaskInfo(
+ WINDOWING_MODE_FREEFORM,
+ taskWidth = DEFAULT_TASK_WIDTH + 100,
+ taskHeight = DEFAULT_TASK_HEIGHT - 100,
+ )
+ val transitionInfo =
+ TransitionInfoBuilder(TRANSIT_CHANGE, 0)
+ .addChange(createChange(TRANSIT_CHANGE, newTaskInfo))
+ .build()
+ callOnTransitionReady(transitionInfo)
+
+ verify(desktopModeEventLogger, times(1))
+ .logTaskInfoChanged(
+ eq(
+ DEFAULT_TASK_UPDATE.copy(
+ taskWidth = DEFAULT_TASK_WIDTH + 100,
+ taskHeight = DEFAULT_TASK_HEIGHT - 100,
+ visibleTaskCount = 1,
+ )
+ )
+ )
+ verifyZeroInteractions(desktopModeEventLogger)
+ }
+
+ @Test
+ fun sessionAlreadyStarted_multipleTasksUpdated_logsTaskUpdateForCorrectTask() {
+ // add 2 existing freeform task
+ val taskInfo1 = createTaskInfo(WINDOWING_MODE_FREEFORM)
+ val taskInfo2 = createTaskInfo(WINDOWING_MODE_FREEFORM, id = 2)
+ transitionObserver.addTaskInfosToCachedMap(taskInfo1)
+ transitionObserver.addTaskInfosToCachedMap(taskInfo2)
+ transitionObserver.isSessionActive = true
+
+ // task 1 position update
+ val newTaskInfo1 = createTaskInfo(WINDOWING_MODE_FREEFORM, taskX = DEFAULT_TASK_X + 100)
+ val transitionInfo1 =
+ TransitionInfoBuilder(TRANSIT_CHANGE, 0)
+ .addChange(createChange(TRANSIT_CHANGE, newTaskInfo1))
+ .build()
+ callOnTransitionReady(transitionInfo1)
+
+ verify(desktopModeEventLogger, times(1))
+ .logTaskInfoChanged(
+ eq(DEFAULT_TASK_UPDATE.copy(taskX = DEFAULT_TASK_X + 100, visibleTaskCount = 2))
+ )
+ verifyZeroInteractions(desktopModeEventLogger)
+
+ // task 2 resize
+ val newTaskInfo2 =
+ createTaskInfo(
+ WINDOWING_MODE_FREEFORM,
+ id = 2,
+ taskWidth = DEFAULT_TASK_WIDTH + 100,
+ taskHeight = DEFAULT_TASK_HEIGHT - 100,
+ )
+ val transitionInfo2 =
+ TransitionInfoBuilder(TRANSIT_CHANGE, 0)
+ .addChange(createChange(TRANSIT_CHANGE, newTaskInfo2))
+ .build()
+
+ callOnTransitionReady(transitionInfo2)
+
+ verify(desktopModeEventLogger, times(1))
+ .logTaskInfoChanged(
+ eq(
+ DEFAULT_TASK_UPDATE.copy(
+ instanceId = 2,
+ taskWidth = DEFAULT_TASK_WIDTH + 100,
+ taskHeight = DEFAULT_TASK_HEIGHT - 100,
+ visibleTaskCount = 2,
+ )
+ )
+ )
+ verifyZeroInteractions(desktopModeEventLogger)
+ }
+
+ @Test
+ fun sessionAlreadyStarted_freeformTaskRemoved_logsTaskRemoved() {
+ // add two existing freeform tasks
+ transitionObserver.addTaskInfosToCachedMap(createTaskInfo(WINDOWING_MODE_FREEFORM))
+ transitionObserver.addTaskInfosToCachedMap(createTaskInfo(WINDOWING_MODE_FREEFORM, id = 2))
+ transitionObserver.isSessionActive = true
+
+ // new freeform task closed
+ val change = createChange(TRANSIT_CLOSE, createTaskInfo(WINDOWING_MODE_FREEFORM, id = 2))
+ val transitionInfo = TransitionInfoBuilder(TRANSIT_CLOSE, 0).addChange(change).build()
+ callOnTransitionReady(transitionInfo)
+
+ verify(desktopModeEventLogger, times(1))
+ .logTaskRemoved(eq(DEFAULT_TASK_UPDATE.copy(instanceId = 2, visibleTaskCount = 1)))
+ verify(desktopModeEventLogger, never()).logSessionExit(any())
+ }
+
+ /** Simulate calling the onTransitionReady() method */
+ private fun callOnTransitionReady(transitionInfo: TransitionInfo) {
+ val transition = mock<IBinder>()
+ val startT = mock<SurfaceControl.Transaction>()
+ val finishT = mock<SurfaceControl.Transaction>()
+
+ transitionObserver.onTransitionReady(transition, transitionInfo, startT, finishT)
+ }
+
+ private fun verifyTaskAddedAndEnterLogging(enterReason: EnterReason, taskUpdate: TaskUpdate) {
+ assertTrue(transitionObserver.isSessionActive)
+ verify(desktopModeEventLogger, times(1)).logSessionEnter(eq(enterReason))
+ verify(desktopModeEventLogger, times(1)).logTaskAdded(eq(taskUpdate))
+ ExtendedMockito.verify {
+ Trace.setCounter(
+ eq(Trace.TRACE_TAG_WINDOW_MANAGER),
+ eq(DesktopModeLoggerTransitionObserver.VISIBLE_TASKS_COUNTER_NAME),
+ eq(taskUpdate.visibleTaskCount.toLong()),
+ )
}
+ ExtendedMockito.verify {
+ SystemProperties.set(
+ eq(DesktopModeLoggerTransitionObserver.VISIBLE_TASKS_COUNTER_SYSTEM_PROPERTY),
+ eq(taskUpdate.visibleTaskCount.toString()),
+ )
+ }
+ verifyZeroInteractions(desktopModeEventLogger)
+ }
+
+ private fun verifyTaskRemovedAndExitLogging(exitReason: ExitReason, taskUpdate: TaskUpdate) {
+ assertFalse(transitionObserver.isSessionActive)
+ verify(desktopModeEventLogger, times(1)).logTaskRemoved(eq(taskUpdate))
+ verify(desktopModeEventLogger, times(1)).logSessionExit(eq(exitReason))
+ verifyZeroInteractions(desktopModeEventLogger)
+ }
+
+ private companion object {
+ const val DEFAULT_TASK_ID = 1
+ const val DEFAULT_TASK_UID = 2
+ const val DEFAULT_TASK_HEIGHT = 100
+ const val DEFAULT_TASK_WIDTH = 200
+ const val DEFAULT_TASK_X = 30
+ const val DEFAULT_TASK_Y = 70
+ const val DEFAULT_VISIBLE_TASK_COUNT = 0
+ val DEFAULT_TASK_UPDATE =
+ TaskUpdate(
+ DEFAULT_TASK_ID,
+ DEFAULT_TASK_UID,
+ DEFAULT_TASK_HEIGHT,
+ DEFAULT_TASK_WIDTH,
+ DEFAULT_TASK_X,
+ DEFAULT_TASK_Y,
+ visibleTaskCount = DEFAULT_VISIBLE_TASK_COUNT,
+ )
- fun createChange(mode: Int, taskInfo: ActivityManager.RunningTaskInfo): Change {
- val change =
- Change(WindowContainerToken(mock<IWindowContainerToken>()), mock<SurfaceControl>())
- change.mode = mode
- change.taskInfo = taskInfo
- return change
+ fun createTaskInfo(
+ windowMode: Int,
+ id: Int = DEFAULT_TASK_ID,
+ uid: Int = DEFAULT_TASK_UID,
+ taskHeight: Int = DEFAULT_TASK_HEIGHT,
+ taskWidth: Int = DEFAULT_TASK_WIDTH,
+ taskX: Int = DEFAULT_TASK_X,
+ taskY: Int = DEFAULT_TASK_Y,
+ ) =
+ ActivityManager.RunningTaskInfo().apply {
+ taskId = id
+ effectiveUid = uid
+ configuration.windowConfiguration.apply {
+ windowingMode = windowMode
+ positionInParent = Point(taskX, taskY)
+ bounds.set(Rect(taskX, taskY, taskX + taskWidth, taskY + taskHeight))
+ }
+ }
+
+ fun createChange(mode: Int, taskInfo: ActivityManager.RunningTaskInfo): Change {
+ val change =
+ Change(WindowContainerToken(mock<IWindowContainerToken>()), mock<SurfaceControl>())
+ change.mode = mode
+ change.taskInfo = taskInfo
+ return change
+ }
}
- }
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTransitionTypesTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTransitionTypesTest.kt
index db4e93de9541..f6eed5da6cad 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTransitionTypesTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTransitionTypesTest.kt
@@ -18,18 +18,18 @@ package com.android.wm.shell.desktopmode
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
-import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_EXIT_DESKTOP_MODE_HANDLE_MENU_BUTTON
-import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_EXIT_DESKTOP_MODE_KEYBOARD_SHORTCUT
-import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_EXIT_DESKTOP_MODE_TASK_DRAG
-import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_EXIT_DESKTOP_MODE_UNKNOWN
import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_ENTER_DESKTOP_FROM_APP_FROM_OVERVIEW
import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_ENTER_DESKTOP_FROM_APP_HANDLE_MENU_BUTTON
import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_ENTER_DESKTOP_FROM_KEYBOARD_SHORTCUT
import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_ENTER_DESKTOP_FROM_UNKNOWN
+import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_EXIT_DESKTOP_MODE_HANDLE_MENU_BUTTON
+import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_EXIT_DESKTOP_MODE_KEYBOARD_SHORTCUT
+import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_EXIT_DESKTOP_MODE_TASK_DRAG
+import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_EXIT_DESKTOP_MODE_UNKNOWN
import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.getEnterTransitionType
import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.getExitTransitionType
-import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource.APP_HANDLE_MENU_BUTTON
import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource.APP_FROM_OVERVIEW
+import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource.APP_HANDLE_MENU_BUTTON
import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource.KEYBOARD_SHORTCUT
import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource.TASK_DRAG
import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource.UNKNOWN
@@ -53,8 +53,7 @@ class DesktopModeTransitionTypesTest {
.isEqualTo(TRANSIT_ENTER_DESKTOP_FROM_APP_HANDLE_MENU_BUTTON)
assertThat(APP_FROM_OVERVIEW.getEnterTransitionType())
.isEqualTo(TRANSIT_ENTER_DESKTOP_FROM_APP_FROM_OVERVIEW)
- assertThat(TASK_DRAG.getEnterTransitionType())
- .isEqualTo(TRANSIT_ENTER_DESKTOP_FROM_UNKNOWN)
+ assertThat(TASK_DRAG.getEnterTransitionType()).isEqualTo(TRANSIT_ENTER_DESKTOP_FROM_UNKNOWN)
assertThat(KEYBOARD_SHORTCUT.getEnterTransitionType())
.isEqualTo(TRANSIT_ENTER_DESKTOP_FROM_KEYBOARD_SHORTCUT)
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeUiEventLoggerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeUiEventLoggerTest.kt
index 94698e2fc0fb..72b1fd9af117 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeUiEventLoggerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeUiEventLoggerTest.kt
@@ -16,7 +16,6 @@
package com.android.wm.shell.desktopmode
-
import android.content.ComponentName
import android.content.pm.ApplicationInfo
import android.content.pm.PackageManager
@@ -67,8 +66,7 @@ class DesktopModeUiEventLoggerTest : ShellTestCase() {
@Test
fun log_eventLogged() {
- val event =
- DESKTOP_WINDOW_EDGE_DRAG_RESIZE
+ val event = DESKTOP_WINDOW_EDGE_DRAG_RESIZE
logger.log(UID, PACKAGE_NAME, event)
assertThat(uiEventLoggerFake.numLogs()).isEqualTo(1)
assertThat(uiEventLoggerFake.eventId(0)).isEqualTo(event.id)
@@ -97,8 +95,7 @@ class DesktopModeUiEventLoggerTest : ShellTestCase() {
@Test
fun logWithInstanceId_eventLogged() {
- val event =
- DESKTOP_WINDOW_EDGE_DRAG_RESIZE
+ val event = DESKTOP_WINDOW_EDGE_DRAG_RESIZE
logger.logWithInstanceId(INSTANCE_ID, UID, PACKAGE_NAME, event)
assertThat(uiEventLoggerFake.numLogs()).isEqualTo(1)
assertThat(uiEventLoggerFake.eventId(0)).isEqualTo(event.id)
@@ -109,12 +106,12 @@ class DesktopModeUiEventLoggerTest : ShellTestCase() {
@Test
fun logWithTaskInfo_eventLogged() {
- val event =
- DESKTOP_WINDOW_EDGE_DRAG_RESIZE
- val taskInfo = TestRunningTaskInfoBuilder()
- .setUserId(USER_ID)
- .setBaseActivity(ComponentName(PACKAGE_NAME, "test"))
- .build()
+ val event = DESKTOP_WINDOW_EDGE_DRAG_RESIZE
+ val taskInfo =
+ TestRunningTaskInfoBuilder()
+ .setUserId(USER_ID)
+ .setBaseActivity(ComponentName(PACKAGE_NAME, "test"))
+ .build()
whenever(mockPackageManager.getApplicationInfoAsUser(PACKAGE_NAME, /* flags= */ 0, USER_ID))
.thenReturn(ApplicationInfo().apply { uid = UID })
logger.log(taskInfo, event)
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 935e6d052f5e..e46d2c7147ed 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
@@ -66,74 +66,109 @@ class DesktopModeVisualIndicatorTest : ShellTestCase() {
fun testFullscreenRegionCalculation() {
createVisualIndicator(DesktopModeVisualIndicator.DragStartState.FROM_FULLSCREEN)
var testRegion = visualIndicator.calculateFullscreenRegion(displayLayout, CAPTION_HEIGHT)
- assertThat(testRegion.bounds).isEqualTo(Rect(0, Short.MIN_VALUE.toInt(), 2400,
- 2 * STABLE_INSETS.top))
+ assertThat(testRegion.bounds)
+ .isEqualTo(Rect(0, Short.MIN_VALUE.toInt(), 2400, 2 * STABLE_INSETS.top))
createVisualIndicator(DesktopModeVisualIndicator.DragStartState.FROM_FREEFORM)
testRegion = visualIndicator.calculateFullscreenRegion(displayLayout, CAPTION_HEIGHT)
val transitionHeight = SystemBarUtils.getStatusBarHeight(context)
- val toFullscreenScale = mContext.resources.getFloat(
- R.dimen.desktop_mode_fullscreen_region_scale
- )
+ val toFullscreenScale =
+ mContext.resources.getFloat(R.dimen.desktop_mode_fullscreen_region_scale)
val toFullscreenWidth = displayLayout.width() * toFullscreenScale
- assertThat(testRegion.bounds).isEqualTo(Rect(
- (DISPLAY_BOUNDS.width() / 2f - toFullscreenWidth / 2f).toInt(),
- Short.MIN_VALUE.toInt(),
- (DISPLAY_BOUNDS.width() / 2f + toFullscreenWidth / 2f).toInt(),
- transitionHeight))
+ assertThat(testRegion.bounds)
+ .isEqualTo(
+ Rect(
+ (DISPLAY_BOUNDS.width() / 2f - toFullscreenWidth / 2f).toInt(),
+ Short.MIN_VALUE.toInt(),
+ (DISPLAY_BOUNDS.width() / 2f + toFullscreenWidth / 2f).toInt(),
+ transitionHeight,
+ )
+ )
createVisualIndicator(DesktopModeVisualIndicator.DragStartState.FROM_SPLIT)
testRegion = visualIndicator.calculateFullscreenRegion(displayLayout, CAPTION_HEIGHT)
- assertThat(testRegion.bounds).isEqualTo(Rect(0, Short.MIN_VALUE.toInt(), 2400,
- 2 * STABLE_INSETS.top))
+ assertThat(testRegion.bounds)
+ .isEqualTo(Rect(0, Short.MIN_VALUE.toInt(), 2400, 2 * STABLE_INSETS.top))
createVisualIndicator(DesktopModeVisualIndicator.DragStartState.DRAGGED_INTENT)
testRegion = visualIndicator.calculateFullscreenRegion(displayLayout, CAPTION_HEIGHT)
- assertThat(testRegion.bounds).isEqualTo(Rect(0, Short.MIN_VALUE.toInt(), 2400,
- transitionHeight))
+ assertThat(testRegion.bounds)
+ .isEqualTo(Rect(0, Short.MIN_VALUE.toInt(), 2400, transitionHeight))
}
@Test
fun testSplitLeftRegionCalculation() {
- val transitionHeight = context.resources.getDimensionPixelSize(
- R.dimen.desktop_mode_split_from_desktop_height)
+ val transitionHeight =
+ context.resources.getDimensionPixelSize(R.dimen.desktop_mode_split_from_desktop_height)
createVisualIndicator(DesktopModeVisualIndicator.DragStartState.FROM_FULLSCREEN)
- var testRegion = visualIndicator.calculateSplitLeftRegion(displayLayout,
- TRANSITION_AREA_WIDTH, CAPTION_HEIGHT)
+ var testRegion =
+ visualIndicator.calculateSplitLeftRegion(
+ displayLayout,
+ TRANSITION_AREA_WIDTH,
+ CAPTION_HEIGHT,
+ )
assertThat(testRegion.bounds).isEqualTo(Rect(0, -50, 32, 1600))
createVisualIndicator(DesktopModeVisualIndicator.DragStartState.FROM_FREEFORM)
- testRegion = visualIndicator.calculateSplitLeftRegion(displayLayout,
- TRANSITION_AREA_WIDTH, CAPTION_HEIGHT)
+ testRegion =
+ visualIndicator.calculateSplitLeftRegion(
+ displayLayout,
+ TRANSITION_AREA_WIDTH,
+ CAPTION_HEIGHT,
+ )
assertThat(testRegion.bounds).isEqualTo(Rect(0, transitionHeight, 32, 1600))
createVisualIndicator(DesktopModeVisualIndicator.DragStartState.FROM_SPLIT)
- testRegion = visualIndicator.calculateSplitLeftRegion(displayLayout,
- TRANSITION_AREA_WIDTH, CAPTION_HEIGHT)
+ testRegion =
+ visualIndicator.calculateSplitLeftRegion(
+ displayLayout,
+ TRANSITION_AREA_WIDTH,
+ CAPTION_HEIGHT,
+ )
assertThat(testRegion.bounds).isEqualTo(Rect(0, -50, 32, 1600))
createVisualIndicator(DesktopModeVisualIndicator.DragStartState.DRAGGED_INTENT)
- testRegion = visualIndicator.calculateSplitLeftRegion(displayLayout,
- TRANSITION_AREA_WIDTH, CAPTION_HEIGHT)
+ testRegion =
+ visualIndicator.calculateSplitLeftRegion(
+ displayLayout,
+ TRANSITION_AREA_WIDTH,
+ CAPTION_HEIGHT,
+ )
assertThat(testRegion.bounds).isEqualTo(Rect(0, -50, 32, 1600))
}
@Test
fun testSplitRightRegionCalculation() {
- val transitionHeight = context.resources.getDimensionPixelSize(
- R.dimen.desktop_mode_split_from_desktop_height)
+ val transitionHeight =
+ context.resources.getDimensionPixelSize(R.dimen.desktop_mode_split_from_desktop_height)
createVisualIndicator(DesktopModeVisualIndicator.DragStartState.FROM_FULLSCREEN)
- var testRegion = visualIndicator.calculateSplitRightRegion(displayLayout,
- TRANSITION_AREA_WIDTH, CAPTION_HEIGHT)
+ var testRegion =
+ visualIndicator.calculateSplitRightRegion(
+ displayLayout,
+ TRANSITION_AREA_WIDTH,
+ CAPTION_HEIGHT,
+ )
assertThat(testRegion.bounds).isEqualTo(Rect(2368, -50, 2400, 1600))
createVisualIndicator(DesktopModeVisualIndicator.DragStartState.FROM_FREEFORM)
- testRegion = visualIndicator.calculateSplitRightRegion(displayLayout,
- TRANSITION_AREA_WIDTH, CAPTION_HEIGHT)
+ testRegion =
+ visualIndicator.calculateSplitRightRegion(
+ displayLayout,
+ TRANSITION_AREA_WIDTH,
+ CAPTION_HEIGHT,
+ )
assertThat(testRegion.bounds).isEqualTo(Rect(2368, transitionHeight, 2400, 1600))
createVisualIndicator(DesktopModeVisualIndicator.DragStartState.FROM_SPLIT)
- testRegion = visualIndicator.calculateSplitRightRegion(displayLayout,
- TRANSITION_AREA_WIDTH, CAPTION_HEIGHT)
+ testRegion =
+ visualIndicator.calculateSplitRightRegion(
+ displayLayout,
+ TRANSITION_AREA_WIDTH,
+ CAPTION_HEIGHT,
+ )
assertThat(testRegion.bounds).isEqualTo(Rect(2368, -50, 2400, 1600))
createVisualIndicator(DesktopModeVisualIndicator.DragStartState.DRAGGED_INTENT)
- testRegion = visualIndicator.calculateSplitRightRegion(displayLayout,
- TRANSITION_AREA_WIDTH, CAPTION_HEIGHT)
+ testRegion =
+ visualIndicator.calculateSplitRightRegion(
+ displayLayout,
+ TRANSITION_AREA_WIDTH,
+ CAPTION_HEIGHT,
+ )
assertThat(testRegion.bounds).isEqualTo(Rect(2368, -50, 2400, 1600))
}
@@ -141,10 +176,12 @@ class DesktopModeVisualIndicatorTest : ShellTestCase() {
fun testDefaultIndicators() {
createVisualIndicator(DesktopModeVisualIndicator.DragStartState.FROM_FULLSCREEN)
var result = visualIndicator.updateIndicatorType(PointF(-10000f, 500f))
- assertThat(result).isEqualTo(DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_LEFT_INDICATOR)
+ assertThat(result)
+ .isEqualTo(DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_LEFT_INDICATOR)
createVisualIndicator(DesktopModeVisualIndicator.DragStartState.FROM_SPLIT)
result = visualIndicator.updateIndicatorType(PointF(10000f, 500f))
- assertThat(result).isEqualTo(DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_RIGHT_INDICATOR)
+ assertThat(result)
+ .isEqualTo(DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_RIGHT_INDICATOR)
createVisualIndicator(DesktopModeVisualIndicator.DragStartState.DRAGGED_INTENT)
result = visualIndicator.updateIndicatorType(PointF(500f, 10000f))
assertThat(result).isEqualTo(DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR)
@@ -154,8 +191,16 @@ class DesktopModeVisualIndicatorTest : ShellTestCase() {
}
private fun createVisualIndicator(dragStartState: DesktopModeVisualIndicator.DragStartState) {
- visualIndicator = DesktopModeVisualIndicator(syncQueue, taskInfo, displayController,
- context, taskSurface, taskDisplayAreaOrganizer, dragStartState)
+ visualIndicator =
+ DesktopModeVisualIndicator(
+ syncQueue,
+ taskInfo,
+ displayController,
+ context,
+ taskSurface,
+ taskDisplayAreaOrganizer,
+ dragStartState,
+ )
}
companion object {
@@ -163,11 +208,12 @@ class DesktopModeVisualIndicatorTest : ShellTestCase() {
private const val CAPTION_HEIGHT = 50
private val DISPLAY_BOUNDS = Rect(0, 0, 2400, 1600)
private const val NAVBAR_HEIGHT = 50
- private val STABLE_INSETS = Rect(
- DISPLAY_BOUNDS.left,
- DISPLAY_BOUNDS.top + CAPTION_HEIGHT,
- DISPLAY_BOUNDS.right,
- DISPLAY_BOUNDS.bottom - NAVBAR_HEIGHT
- )
+ private val STABLE_INSETS =
+ Rect(
+ DISPLAY_BOUNDS.left,
+ DISPLAY_BOUNDS.top + CAPTION_HEIGHT,
+ DISPLAY_BOUNDS.right,
+ DISPLAY_BOUNDS.bottom - NAVBAR_HEIGHT,
+ )
}
-} \ No newline at end of file
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt
index 344140d91ab3..e777ec7b55f6 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt
@@ -76,15 +76,9 @@ class DesktopRepositoryTest : ShellTestCase() {
datastoreScope = CoroutineScope(Dispatchers.Unconfined + SupervisorJob())
shellInit = spy(ShellInit(testExecutor))
- repo =
- DesktopRepository(
- persistentRepository,
- datastoreScope,
- DEFAULT_USER_ID
- )
- whenever(runBlocking { persistentRepository.readDesktop(any(), any()) }).thenReturn(
- Desktop.getDefaultInstance()
- )
+ repo = DesktopRepository(persistentRepository, datastoreScope, DEFAULT_USER_ID)
+ whenever(runBlocking { persistentRepository.readDesktop(any(), any()) })
+ .thenReturn(Desktop.getDefaultInstance())
shellInit.init()
}
@@ -245,7 +239,7 @@ class DesktopRepositoryTest : ShellTestCase() {
DEFAULT_DESKTOP_ID,
visibleTasks = ArraySet(arrayOf(1)),
minimizedTasks = ArraySet(),
- freeformTasksInZOrder = arrayListOf()
+ freeformTasksInZOrder = arrayListOf(),
)
verify(persistentRepository)
.addOrUpdateDesktop(
@@ -253,7 +247,7 @@ class DesktopRepositoryTest : ShellTestCase() {
DEFAULT_DESKTOP_ID,
visibleTasks = ArraySet(arrayOf(1, 2)),
minimizedTasks = ArraySet(),
- freeformTasksInZOrder = arrayListOf()
+ freeformTasksInZOrder = arrayListOf(),
)
}
}
@@ -441,8 +435,8 @@ class DesktopRepositoryTest : ShellTestCase() {
}
/**
- * When a task vanishes, the displayId of the task is set to INVALID_DISPLAY.
- * This tests that task is removed from the last parent display when it vanishes.
+ * When a task vanishes, the displayId of the task is set to INVALID_DISPLAY. This tests that
+ * task is removed from the last parent display when it vanishes.
*/
@Test
fun updateTask_removeVisibleTasksRemovesTaskWithInvalidDisplay() {
@@ -562,7 +556,7 @@ class DesktopRepositoryTest : ShellTestCase() {
DEFAULT_DESKTOP_ID,
visibleTasks = ArraySet(),
minimizedTasks = ArraySet(),
- freeformTasksInZOrder = arrayListOf(5)
+ freeformTasksInZOrder = arrayListOf(5),
)
verify(persistentRepository)
.addOrUpdateDesktop(
@@ -570,7 +564,7 @@ class DesktopRepositoryTest : ShellTestCase() {
DEFAULT_DESKTOP_ID,
visibleTasks = ArraySet(arrayOf(5)),
minimizedTasks = ArraySet(),
- freeformTasksInZOrder = arrayListOf(6, 5)
+ freeformTasksInZOrder = arrayListOf(6, 5),
)
verify(persistentRepository)
.addOrUpdateDesktop(
@@ -578,10 +572,10 @@ class DesktopRepositoryTest : ShellTestCase() {
DEFAULT_DESKTOP_ID,
visibleTasks = ArraySet(arrayOf(5, 6)),
minimizedTasks = ArraySet(),
- freeformTasksInZOrder = arrayListOf(7, 6, 5)
+ freeformTasksInZOrder = arrayListOf(7, 6, 5),
)
}
- }
+ }
@Test
fun addTask_alreadyExists_movesToTop() {
@@ -628,7 +622,7 @@ class DesktopRepositoryTest : ShellTestCase() {
DEFAULT_DESKTOP_ID,
visibleTasks = ArraySet(),
minimizedTasks = ArraySet(),
- freeformTasksInZOrder = arrayListOf(5)
+ freeformTasksInZOrder = arrayListOf(5),
)
verify(persistentRepository)
.addOrUpdateDesktop(
@@ -636,7 +630,7 @@ class DesktopRepositoryTest : ShellTestCase() {
DEFAULT_DESKTOP_ID,
visibleTasks = ArraySet(arrayOf(5)),
minimizedTasks = ArraySet(),
- freeformTasksInZOrder = arrayListOf(6, 5)
+ freeformTasksInZOrder = arrayListOf(6, 5),
)
verify(persistentRepository)
.addOrUpdateDesktop(
@@ -644,7 +638,7 @@ class DesktopRepositoryTest : ShellTestCase() {
DEFAULT_DESKTOP_ID,
visibleTasks = ArraySet(arrayOf(5, 6)),
minimizedTasks = ArraySet(),
- freeformTasksInZOrder = arrayListOf(7, 6, 5)
+ freeformTasksInZOrder = arrayListOf(7, 6, 5),
)
verify(persistentRepository, times(2))
.addOrUpdateDesktop(
@@ -652,10 +646,10 @@ class DesktopRepositoryTest : ShellTestCase() {
DEFAULT_DESKTOP_ID,
visibleTasks = ArraySet(arrayOf(5, 7)),
minimizedTasks = ArraySet(arrayOf(6)),
- freeformTasksInZOrder = arrayListOf(7, 6, 5)
+ freeformTasksInZOrder = arrayListOf(7, 6, 5),
)
}
- }
+ }
@Test
fun addTask_taskIsUnminimized_noop() {
@@ -694,7 +688,7 @@ class DesktopRepositoryTest : ShellTestCase() {
DEFAULT_DESKTOP_ID,
visibleTasks = ArraySet(),
minimizedTasks = ArraySet(),
- freeformTasksInZOrder = arrayListOf(1)
+ freeformTasksInZOrder = arrayListOf(1),
)
verify(persistentRepository)
.addOrUpdateDesktop(
@@ -702,7 +696,7 @@ class DesktopRepositoryTest : ShellTestCase() {
DEFAULT_DESKTOP_ID,
visibleTasks = ArraySet(),
minimizedTasks = ArraySet(),
- freeformTasksInZOrder = ArrayList()
+ freeformTasksInZOrder = ArrayList(),
)
}
}
@@ -731,7 +725,7 @@ class DesktopRepositoryTest : ShellTestCase() {
DEFAULT_DESKTOP_ID,
visibleTasks = ArraySet(),
minimizedTasks = ArraySet(),
- freeformTasksInZOrder = arrayListOf(1)
+ freeformTasksInZOrder = arrayListOf(1),
)
verify(persistentRepository)
.addOrUpdateDesktop(
@@ -739,7 +733,7 @@ class DesktopRepositoryTest : ShellTestCase() {
DEFAULT_DESKTOP_ID,
visibleTasks = ArraySet(),
minimizedTasks = ArraySet(),
- freeformTasksInZOrder = ArrayList()
+ freeformTasksInZOrder = ArrayList(),
)
}
}
@@ -768,7 +762,7 @@ class DesktopRepositoryTest : ShellTestCase() {
DEFAULT_DESKTOP_ID,
visibleTasks = ArraySet(),
minimizedTasks = ArraySet(),
- freeformTasksInZOrder = arrayListOf(1)
+ freeformTasksInZOrder = arrayListOf(1),
)
verify(persistentRepository, never())
.addOrUpdateDesktop(
@@ -776,7 +770,7 @@ class DesktopRepositoryTest : ShellTestCase() {
DEFAULT_DESKTOP_ID,
visibleTasks = ArraySet(),
minimizedTasks = ArraySet(),
- freeformTasksInZOrder = ArrayList()
+ freeformTasksInZOrder = ArrayList(),
)
}
}
@@ -928,7 +922,6 @@ class DesktopRepositoryTest : ShellTestCase() {
assertThat(repo.isMinimizedTask(taskId = 2)).isFalse()
}
-
@Test
fun updateTask_minimizedTaskBecomesVisible_unminimizesTask() {
repo.minimizeTask(displayId = 10, taskId = 2)
@@ -1056,6 +1049,7 @@ class DesktopRepositoryTest : ShellTestCase() {
class TestListener : DesktopRepository.ActiveTasksListener {
var activeChangesOnDefaultDisplay = 0
var activeChangesOnSecondaryDisplay = 0
+
override fun onActiveTasksChanged(displayId: Int) {
when (displayId) {
DEFAULT_DISPLAY -> activeChangesOnDefaultDisplay++
@@ -1093,4 +1087,4 @@ class DesktopRepositoryTest : ShellTestCase() {
private const val DEFAULT_USER_ID = 1000
private const val DEFAULT_DESKTOP_ID = 0
}
-} \ No newline at end of file
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTaskChangeListenerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTaskChangeListenerTest.kt
index b4daa6637f83..19ab9113bc7a 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTaskChangeListenerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTaskChangeListenerTest.kt
@@ -22,7 +22,6 @@ import android.platform.test.flag.junit.SetFlagsRule
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION
-import com.android.wm.shell.desktopmode.DesktopUserRepositories
import com.android.wm.shell.ShellTestCase
import com.android.wm.shell.desktopmode.DesktopTestHelpers.createFreeformTask
import com.android.wm.shell.desktopmode.DesktopTestHelpers.createFullscreenTask
@@ -45,167 +44,144 @@ import org.mockito.kotlin.whenever
@RunWith(AndroidTestingRunner::class)
class DesktopTaskChangeListenerTest : ShellTestCase() {
- @JvmField @Rule val setFlagsRule = SetFlagsRule()
+ @JvmField @Rule val setFlagsRule = SetFlagsRule()
- private lateinit var desktopTaskChangeListener: DesktopTaskChangeListener
+ private lateinit var desktopTaskChangeListener: DesktopTaskChangeListener
- private val desktopUserRepositories = mock<DesktopUserRepositories>()
- private val desktopRepository = mock<DesktopRepository>()
+ private val desktopUserRepositories = mock<DesktopUserRepositories>()
+ private val desktopRepository = mock<DesktopRepository>()
- @Before
- fun setUp() {
- desktopTaskChangeListener = DesktopTaskChangeListener(desktopUserRepositories)
+ @Before
+ fun setUp() {
+ desktopTaskChangeListener = DesktopTaskChangeListener(desktopUserRepositories)
- whenever(desktopUserRepositories.current).thenReturn(desktopRepository)
- whenever(desktopUserRepositories.getProfile(anyInt())).thenReturn(desktopRepository)
- }
+ whenever(desktopUserRepositories.current).thenReturn(desktopRepository)
+ whenever(desktopUserRepositories.getProfile(anyInt())).thenReturn(desktopRepository)
+ }
- @Test
- fun onTaskOpening_fullscreenTask_notActiveDesktopTask_noop() {
- val task = createFullscreenTask().apply { isVisible = true }
- whenever(desktopUserRepositories.current.isActiveTask(task.taskId))
- .thenReturn(false)
+ @Test
+ fun onTaskOpening_fullscreenTask_notActiveDesktopTask_noop() {
+ val task = createFullscreenTask().apply { isVisible = true }
+ whenever(desktopUserRepositories.current.isActiveTask(task.taskId)).thenReturn(false)
- desktopTaskChangeListener.onTaskOpening(task)
+ desktopTaskChangeListener.onTaskOpening(task)
- verify(desktopUserRepositories.current, never())
- .addTask(task.displayId, task.taskId, task.isVisible)
- verify(desktopUserRepositories.current, never())
- .removeFreeformTask(task.displayId, task.taskId)
- }
+ verify(desktopUserRepositories.current, never())
+ .addTask(task.displayId, task.taskId, task.isVisible)
+ verify(desktopUserRepositories.current, never())
+ .removeFreeformTask(task.displayId, task.taskId)
+ }
- @Test
- fun onTaskOpening_freeformTask_activeDesktopTask_removesTaskFromRepo() {
- val task = createFullscreenTask().apply { isVisible = true }
- whenever(desktopUserRepositories.current.isActiveTask(task.taskId))
- .thenReturn(true)
-
- desktopTaskChangeListener.onTaskOpening(task)
-
- verify(desktopUserRepositories.current).removeFreeformTask(task.displayId, task.taskId)
- }
+ @Test
+ fun onTaskOpening_freeformTask_activeDesktopTask_removesTaskFromRepo() {
+ val task = createFullscreenTask().apply { isVisible = true }
+ whenever(desktopUserRepositories.current.isActiveTask(task.taskId)).thenReturn(true)
- @Test
- fun onTaskOpening_freeformTask_visibleDesktopTask_addsTaskToRepository() {
- val task = createFreeformTask().apply { isVisible = true }
- whenever(desktopUserRepositories.current.isActiveTask(task.taskId))
- .thenReturn(false)
-
- desktopTaskChangeListener.onTaskOpening(task)
-
- verify(desktopUserRepositories.current)
- .addTask(task.displayId, task.taskId, task.isVisible)
- }
-
- @Test
- fun onTaskOpening_freeformTask_nonVisibleDesktopTask_addsTaskToRepository() {
- val task = createFreeformTask().apply { isVisible = false }
- whenever(desktopUserRepositories.current.isActiveTask(task.taskId))
- .thenReturn(true)
-
- desktopTaskChangeListener.onTaskOpening(task)
-
- verify(desktopUserRepositories.current)
- .addTask(task.displayId, task.taskId, task.isVisible)
- }
-
- @Test
- fun onTaskChanging_freeformTaskOutsideDesktop_removesTaskFromRepo() {
- val task = createFullscreenTask().apply { isVisible = true }
- whenever(desktopUserRepositories.current.isActiveTask(task.taskId))
- .thenReturn(true)
-
- desktopTaskChangeListener.onTaskChanging(task)
-
- verify(desktopUserRepositories.current)
- .removeFreeformTask(task.displayId, task.taskId)
- }
-
- @Test
- fun onTaskChanging_visibleTaskInDesktop_updatesTaskVisibility() {
- val task = createFreeformTask().apply { isVisible = true }
- whenever(desktopUserRepositories.current.isActiveTask(task.taskId))
- .thenReturn(true)
-
- desktopTaskChangeListener.onTaskChanging(task)
-
- verify(desktopUserRepositories.current)
- .updateTask(task.displayId, task.taskId, task.isVisible)
- }
-
- @Test
- fun onTaskChanging_nonVisibleTask_updatesTaskVisibility() {
- val task = createFreeformTask().apply { isVisible = false }
- whenever(desktopUserRepositories.current.isActiveTask(task.taskId))
- .thenReturn(true)
-
- desktopTaskChangeListener.onTaskChanging(task)
-
- verify(desktopUserRepositories.current)
- .updateTask(task.displayId, task.taskId, task.isVisible)
- }
-
- @Test
- fun onTaskMovingToFront_freeformTaskOutsideDesktop_removesTaskFromRepo() {
- val task = createFullscreenTask().apply { isVisible = true }
- whenever(desktopUserRepositories.current.isActiveTask(task.taskId))
- .thenReturn(true)
-
- desktopTaskChangeListener.onTaskMovingToFront(task)
-
- verify(desktopUserRepositories.current)
- .removeFreeformTask(task.displayId, task.taskId)
- }
-
- @Test
- @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION)
- fun onTaskClosing_backNavEnabled_nonClosingTask_minimizesTaskInRepo() {
- val task = createFreeformTask().apply { isVisible = true }
- whenever(desktopUserRepositories.current.isActiveTask(task.taskId))
- .thenReturn(true)
- whenever(desktopUserRepositories.current.isClosingTask(task.taskId))
- .thenReturn(false)
-
- desktopTaskChangeListener.onTaskClosing(task)
-
- verify(desktopUserRepositories.current)
- .updateTask(task.displayId, task.taskId, isVisible = false)
- verify(desktopUserRepositories.current)
- .minimizeTask(task.displayId, task.taskId)
- }
-
- @Test
- @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION)
- fun onTaskClosing_backNavDisabled_closingTask_removesTaskInRepo() {
- val task = createFreeformTask().apply { isVisible = true }
- whenever(desktopUserRepositories.current.isActiveTask(task.taskId))
- .thenReturn(true)
- whenever(desktopUserRepositories.current.isClosingTask(task.taskId))
- .thenReturn(true)
-
- desktopTaskChangeListener.onTaskClosing(task)
-
- verify(desktopUserRepositories.current, never())
- .minimizeTask(task.displayId, task.taskId)
- verify(desktopUserRepositories.current)
- .removeClosingTask(task.taskId)
- verify(desktopUserRepositories.current)
- .removeFreeformTask(task.displayId, task.taskId)
- }
-
- @Test
- @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION)
- fun onTaskClosing_backNavEnabled_closingTask_removesTaskFromRepo() {
- val task = createFreeformTask().apply { isVisible = true }
- whenever(desktopUserRepositories.current.isActiveTask(task.taskId))
- .thenReturn(true)
- whenever(desktopUserRepositories.current.isClosingTask(task.taskId))
- .thenReturn(true)
-
- desktopTaskChangeListener.onTaskClosing(task)
-
- verify(desktopUserRepositories.current).removeClosingTask(task.taskId)
- verify(desktopUserRepositories.current)
- .removeFreeformTask(task.displayId, task.taskId)
- }
+ desktopTaskChangeListener.onTaskOpening(task)
+
+ verify(desktopUserRepositories.current).removeFreeformTask(task.displayId, task.taskId)
+ }
+
+ @Test
+ fun onTaskOpening_freeformTask_visibleDesktopTask_addsTaskToRepository() {
+ val task = createFreeformTask().apply { isVisible = true }
+ whenever(desktopUserRepositories.current.isActiveTask(task.taskId)).thenReturn(false)
+
+ desktopTaskChangeListener.onTaskOpening(task)
+
+ verify(desktopUserRepositories.current).addTask(task.displayId, task.taskId, task.isVisible)
+ }
+
+ @Test
+ fun onTaskOpening_freeformTask_nonVisibleDesktopTask_addsTaskToRepository() {
+ val task = createFreeformTask().apply { isVisible = false }
+ whenever(desktopUserRepositories.current.isActiveTask(task.taskId)).thenReturn(true)
+
+ desktopTaskChangeListener.onTaskOpening(task)
+
+ verify(desktopUserRepositories.current).addTask(task.displayId, task.taskId, task.isVisible)
+ }
+
+ @Test
+ fun onTaskChanging_freeformTaskOutsideDesktop_removesTaskFromRepo() {
+ val task = createFullscreenTask().apply { isVisible = true }
+ whenever(desktopUserRepositories.current.isActiveTask(task.taskId)).thenReturn(true)
+
+ desktopTaskChangeListener.onTaskChanging(task)
+
+ verify(desktopUserRepositories.current).removeFreeformTask(task.displayId, task.taskId)
+ }
+
+ @Test
+ fun onTaskChanging_visibleTaskInDesktop_updatesTaskVisibility() {
+ val task = createFreeformTask().apply { isVisible = true }
+ whenever(desktopUserRepositories.current.isActiveTask(task.taskId)).thenReturn(true)
+
+ desktopTaskChangeListener.onTaskChanging(task)
+
+ verify(desktopUserRepositories.current)
+ .updateTask(task.displayId, task.taskId, task.isVisible)
+ }
+
+ @Test
+ fun onTaskChanging_nonVisibleTask_updatesTaskVisibility() {
+ val task = createFreeformTask().apply { isVisible = false }
+ whenever(desktopUserRepositories.current.isActiveTask(task.taskId)).thenReturn(true)
+
+ desktopTaskChangeListener.onTaskChanging(task)
+
+ verify(desktopUserRepositories.current)
+ .updateTask(task.displayId, task.taskId, task.isVisible)
+ }
+
+ @Test
+ fun onTaskMovingToFront_freeformTaskOutsideDesktop_removesTaskFromRepo() {
+ val task = createFullscreenTask().apply { isVisible = true }
+ whenever(desktopUserRepositories.current.isActiveTask(task.taskId)).thenReturn(true)
+
+ desktopTaskChangeListener.onTaskMovingToFront(task)
+
+ verify(desktopUserRepositories.current).removeFreeformTask(task.displayId, task.taskId)
+ }
+
+ @Test
+ @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION)
+ fun onTaskClosing_backNavEnabled_nonClosingTask_minimizesTaskInRepo() {
+ val task = createFreeformTask().apply { isVisible = true }
+ whenever(desktopUserRepositories.current.isActiveTask(task.taskId)).thenReturn(true)
+ whenever(desktopUserRepositories.current.isClosingTask(task.taskId)).thenReturn(false)
+
+ desktopTaskChangeListener.onTaskClosing(task)
+
+ verify(desktopUserRepositories.current)
+ .updateTask(task.displayId, task.taskId, isVisible = false)
+ verify(desktopUserRepositories.current).minimizeTask(task.displayId, task.taskId)
+ }
+
+ @Test
+ @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION)
+ fun onTaskClosing_backNavDisabled_closingTask_removesTaskInRepo() {
+ val task = createFreeformTask().apply { isVisible = true }
+ whenever(desktopUserRepositories.current.isActiveTask(task.taskId)).thenReturn(true)
+ whenever(desktopUserRepositories.current.isClosingTask(task.taskId)).thenReturn(true)
+
+ desktopTaskChangeListener.onTaskClosing(task)
+
+ verify(desktopUserRepositories.current, never()).minimizeTask(task.displayId, task.taskId)
+ verify(desktopUserRepositories.current).removeClosingTask(task.taskId)
+ verify(desktopUserRepositories.current).removeFreeformTask(task.displayId, task.taskId)
+ }
+
+ @Test
+ @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION)
+ fun onTaskClosing_backNavEnabled_closingTask_removesTaskFromRepo() {
+ val task = createFreeformTask().apply { isVisible = true }
+ whenever(desktopUserRepositories.current.isActiveTask(task.taskId)).thenReturn(true)
+ whenever(desktopUserRepositories.current.isClosingTask(task.taskId)).thenReturn(true)
+
+ desktopTaskChangeListener.onTaskClosing(task)
+
+ verify(desktopUserRepositories.current).removeClosingTask(task.taskId)
+ verify(desktopUserRepositories.current).removeFreeformTask(task.displayId, task.taskId)
+ }
}
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 0b12d228a0c2..0eb88e368054 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
@@ -81,9 +81,9 @@ import com.android.dx.mockito.inline.extended.ExtendedMockito.never
import com.android.dx.mockito.inline.extended.StaticMockitoSession
import com.android.internal.jank.InteractionJankMonitor
import com.android.window.flags.Flags
-import com.android.window.flags.Flags.FLAG_ENABLE_PER_DISPLAY_DESKTOP_WALLPAPER_ACTIVITY
import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE
import com.android.window.flags.Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP
+import com.android.window.flags.Flags.FLAG_ENABLE_PER_DISPLAY_DESKTOP_WALLPAPER_ACTIVITY
import com.android.wm.shell.MockToken
import com.android.wm.shell.R
import com.android.wm.shell.RootTaskDisplayAreaOrganizer
@@ -96,7 +96,6 @@ import com.android.wm.shell.common.DisplayLayout
import com.android.wm.shell.common.MultiInstanceHelper
import com.android.wm.shell.common.ShellExecutor
import com.android.wm.shell.common.SyncTransactionQueue
-import com.android.wm.shell.desktopmode.common.ToggleTaskSizeInteraction
import com.android.wm.shell.desktopmode.DesktopImmersiveController.ExitResult
import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.InputMethod
import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ResizeTrigger
@@ -109,6 +108,7 @@ import com.android.wm.shell.desktopmode.DesktopTestHelpers.createHomeTask
import com.android.wm.shell.desktopmode.DesktopTestHelpers.createSplitScreenTask
import com.android.wm.shell.desktopmode.EnterDesktopTaskTransitionHandler.FREEFORM_ANIMATION_DURATION
import com.android.wm.shell.desktopmode.ExitDesktopTaskTransitionHandler.FULLSCREEN_ANIMATION_DURATION
+import com.android.wm.shell.desktopmode.common.ToggleTaskSizeInteraction
import com.android.wm.shell.desktopmode.minimize.DesktopWindowLimitRemoteHandler
import com.android.wm.shell.desktopmode.persistence.Desktop
import com.android.wm.shell.desktopmode.persistence.DesktopPersistentRepository
@@ -137,8 +137,8 @@ import com.android.wm.shell.windowdecor.DesktopModeWindowDecoration
import com.android.wm.shell.windowdecor.tiling.DesktopTilingDecorViewModel
import com.google.common.truth.Truth.assertThat
import com.google.common.truth.Truth.assertWithMessage
-import java.util.function.Consumer
import java.util.Optional
+import java.util.function.Consumer
import junit.framework.Assert.assertFalse
import junit.framework.Assert.assertTrue
import kotlin.test.assertIs
@@ -167,8 +167,8 @@ import org.mockito.Mockito.anyInt
import org.mockito.Mockito.clearInvocations
import org.mockito.Mockito.mock
import org.mockito.Mockito.spy
-import org.mockito.Mockito.verify
import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
import org.mockito.kotlin.any
import org.mockito.kotlin.anyOrNull
import org.mockito.kotlin.argumentCaptor
@@ -189,4415 +189,4737 @@ import org.mockito.quality.Strictness
@EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
class DesktopTasksControllerTest : ShellTestCase() {
- @JvmField @Rule val setFlagsRule = SetFlagsRule()
-
- @Mock lateinit var testExecutor: ShellExecutor
- @Mock lateinit var shellCommandHandler: ShellCommandHandler
- @Mock lateinit var shellController: ShellController
- @Mock lateinit var displayController: DisplayController
- @Mock lateinit var displayLayout: DisplayLayout
- @Mock lateinit var shellTaskOrganizer: ShellTaskOrganizer
- @Mock lateinit var syncQueue: SyncTransactionQueue
- @Mock lateinit var rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer
- @Mock lateinit var transitions: Transitions
- @Mock lateinit var keyguardManager: KeyguardManager
- @Mock lateinit var mReturnToDragStartAnimator: ReturnToDragStartAnimator
- @Mock lateinit var desktopMixedTransitionHandler: DesktopMixedTransitionHandler
- @Mock lateinit var exitDesktopTransitionHandler: ExitDesktopTaskTransitionHandler
- @Mock lateinit var enterDesktopTransitionHandler: EnterDesktopTaskTransitionHandler
- @Mock lateinit var dragAndDropTransitionHandler: DesktopModeDragAndDropTransitionHandler
- @Mock
- lateinit var toggleResizeDesktopTaskTransitionHandler: ToggleResizeDesktopTaskTransitionHandler
- @Mock lateinit var dragToDesktopTransitionHandler: DragToDesktopTransitionHandler
- @Mock
- lateinit var mMockDesktopImmersiveController: DesktopImmersiveController
- @Mock lateinit var splitScreenController: SplitScreenController
- @Mock lateinit var recentsTransitionHandler: RecentsTransitionHandler
- @Mock lateinit var dragAndDropController: DragAndDropController
- @Mock lateinit var multiInstanceHelper: MultiInstanceHelper
- @Mock lateinit var desktopModeVisualIndicator: DesktopModeVisualIndicator
- @Mock lateinit var recentTasksController: RecentTasksController
- @Mock
- private lateinit var mockInteractionJankMonitor: InteractionJankMonitor
- @Mock private lateinit var mockSurface: SurfaceControl
- @Mock private lateinit var taskbarDesktopTaskListener: TaskbarDesktopTaskListener
- @Mock private lateinit var freeformTaskTransitionStarter: FreeformTaskTransitionStarter
- @Mock private lateinit var mockHandler: Handler
- @Mock private lateinit var desktopModeEventLogger: DesktopModeEventLogger
- @Mock private lateinit var desktopModeUiEventLogger: DesktopModeUiEventLogger
- @Mock lateinit var persistentRepository: DesktopPersistentRepository
- @Mock lateinit var motionEvent: MotionEvent
- @Mock lateinit var repositoryInitializer: DesktopRepositoryInitializer
- @Mock private lateinit var mockToast: Toast
- private lateinit var mockitoSession: StaticMockitoSession
- @Mock
- private lateinit var desktopTilingDecorViewModel: DesktopTilingDecorViewModel
- @Mock
- private lateinit var desktopWindowDecoration: DesktopModeWindowDecoration
- @Mock private lateinit var resources: Resources
- @Mock
- lateinit var desktopModeEnterExitTransitionListener: DesktopModeEntryExitTransitionListener
- @Mock private lateinit var userManager: UserManager
- private lateinit var controller: DesktopTasksController
- private lateinit var shellInit: ShellInit
- private lateinit var taskRepository: DesktopRepository
- private lateinit var userRepositories: DesktopUserRepositories
- private lateinit var desktopTasksLimiter: DesktopTasksLimiter
- private lateinit var recentsTransitionStateListener: RecentsTransitionStateListener
- private lateinit var testScope: CoroutineScope
-
- private val shellExecutor = TestShellExecutor()
-
- // Mock running tasks are registered here so we can get the list from mock shell task organizer
- private val runningTasks = mutableListOf<RunningTaskInfo>()
-
- private val DISPLAY_DIMENSION_SHORT = 1600
- private val DISPLAY_DIMENSION_LONG = 2560
- private val DEFAULT_LANDSCAPE_BOUNDS = Rect(320, 75, 2240, 1275)
- private val DEFAULT_PORTRAIT_BOUNDS = Rect(200, 165, 1400, 2085)
- private val RESIZABLE_LANDSCAPE_BOUNDS = Rect(25, 435, 1575, 1635)
- private val RESIZABLE_PORTRAIT_BOUNDS = Rect(680, 75, 1880, 1275)
- private val UNRESIZABLE_LANDSCAPE_BOUNDS = Rect(25, 449, 1575, 1611)
- private val UNRESIZABLE_PORTRAIT_BOUNDS = Rect(830, 75, 1730, 1275)
-
- @Before
- fun setUp() {
- Dispatchers.setMain(StandardTestDispatcher())
- mockitoSession =
- mockitoSession()
- .strictness(Strictness.LENIENT)
- .spyStatic(DesktopModeStatus::class.java)
- .spyStatic(Toast::class.java)
- .startMocking()
- doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
-
- testScope = CoroutineScope(Dispatchers.Unconfined + SupervisorJob())
- shellInit = spy(ShellInit(testExecutor))
- userRepositories =
- DesktopUserRepositories(
- context,
- shellInit,
- shellController,
- persistentRepository,
- repositoryInitializer,
- testScope,
- userManager)
- desktopTasksLimiter =
- DesktopTasksLimiter(
+ @JvmField @Rule val setFlagsRule = SetFlagsRule()
+
+ @Mock lateinit var testExecutor: ShellExecutor
+ @Mock lateinit var shellCommandHandler: ShellCommandHandler
+ @Mock lateinit var shellController: ShellController
+ @Mock lateinit var displayController: DisplayController
+ @Mock lateinit var displayLayout: DisplayLayout
+ @Mock lateinit var shellTaskOrganizer: ShellTaskOrganizer
+ @Mock lateinit var syncQueue: SyncTransactionQueue
+ @Mock lateinit var rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer
+ @Mock lateinit var transitions: Transitions
+ @Mock lateinit var keyguardManager: KeyguardManager
+ @Mock lateinit var mReturnToDragStartAnimator: ReturnToDragStartAnimator
+ @Mock lateinit var desktopMixedTransitionHandler: DesktopMixedTransitionHandler
+ @Mock lateinit var exitDesktopTransitionHandler: ExitDesktopTaskTransitionHandler
+ @Mock lateinit var enterDesktopTransitionHandler: EnterDesktopTaskTransitionHandler
+ @Mock lateinit var dragAndDropTransitionHandler: DesktopModeDragAndDropTransitionHandler
+ @Mock
+ lateinit var toggleResizeDesktopTaskTransitionHandler: ToggleResizeDesktopTaskTransitionHandler
+ @Mock lateinit var dragToDesktopTransitionHandler: DragToDesktopTransitionHandler
+ @Mock lateinit var mMockDesktopImmersiveController: DesktopImmersiveController
+ @Mock lateinit var splitScreenController: SplitScreenController
+ @Mock lateinit var recentsTransitionHandler: RecentsTransitionHandler
+ @Mock lateinit var dragAndDropController: DragAndDropController
+ @Mock lateinit var multiInstanceHelper: MultiInstanceHelper
+ @Mock lateinit var desktopModeVisualIndicator: DesktopModeVisualIndicator
+ @Mock lateinit var recentTasksController: RecentTasksController
+ @Mock private lateinit var mockInteractionJankMonitor: InteractionJankMonitor
+ @Mock private lateinit var mockSurface: SurfaceControl
+ @Mock private lateinit var taskbarDesktopTaskListener: TaskbarDesktopTaskListener
+ @Mock private lateinit var freeformTaskTransitionStarter: FreeformTaskTransitionStarter
+ @Mock private lateinit var mockHandler: Handler
+ @Mock private lateinit var desktopModeEventLogger: DesktopModeEventLogger
+ @Mock private lateinit var desktopModeUiEventLogger: DesktopModeUiEventLogger
+ @Mock lateinit var persistentRepository: DesktopPersistentRepository
+ @Mock lateinit var motionEvent: MotionEvent
+ @Mock lateinit var repositoryInitializer: DesktopRepositoryInitializer
+ @Mock private lateinit var mockToast: Toast
+ private lateinit var mockitoSession: StaticMockitoSession
+ @Mock private lateinit var desktopTilingDecorViewModel: DesktopTilingDecorViewModel
+ @Mock private lateinit var desktopWindowDecoration: DesktopModeWindowDecoration
+ @Mock private lateinit var resources: Resources
+ @Mock
+ lateinit var desktopModeEnterExitTransitionListener: DesktopModeEntryExitTransitionListener
+ @Mock private lateinit var userManager: UserManager
+ private lateinit var controller: DesktopTasksController
+ private lateinit var shellInit: ShellInit
+ private lateinit var taskRepository: DesktopRepository
+ private lateinit var userRepositories: DesktopUserRepositories
+ private lateinit var desktopTasksLimiter: DesktopTasksLimiter
+ private lateinit var recentsTransitionStateListener: RecentsTransitionStateListener
+ private lateinit var testScope: CoroutineScope
+
+ private val shellExecutor = TestShellExecutor()
+
+ // Mock running tasks are registered here so we can get the list from mock shell task organizer
+ private val runningTasks = mutableListOf<RunningTaskInfo>()
+
+ private val DISPLAY_DIMENSION_SHORT = 1600
+ private val DISPLAY_DIMENSION_LONG = 2560
+ private val DEFAULT_LANDSCAPE_BOUNDS = Rect(320, 75, 2240, 1275)
+ private val DEFAULT_PORTRAIT_BOUNDS = Rect(200, 165, 1400, 2085)
+ private val RESIZABLE_LANDSCAPE_BOUNDS = Rect(25, 435, 1575, 1635)
+ private val RESIZABLE_PORTRAIT_BOUNDS = Rect(680, 75, 1880, 1275)
+ private val UNRESIZABLE_LANDSCAPE_BOUNDS = Rect(25, 449, 1575, 1611)
+ private val UNRESIZABLE_PORTRAIT_BOUNDS = Rect(830, 75, 1730, 1275)
+
+ @Before
+ fun setUp() {
+ Dispatchers.setMain(StandardTestDispatcher())
+ mockitoSession =
+ mockitoSession()
+ .strictness(Strictness.LENIENT)
+ .spyStatic(DesktopModeStatus::class.java)
+ .spyStatic(Toast::class.java)
+ .startMocking()
+ doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
+
+ testScope = CoroutineScope(Dispatchers.Unconfined + SupervisorJob())
+ shellInit = spy(ShellInit(testExecutor))
+ userRepositories =
+ DesktopUserRepositories(
+ context,
+ shellInit,
+ shellController,
+ persistentRepository,
+ repositoryInitializer,
+ testScope,
+ userManager,
+ )
+ desktopTasksLimiter =
+ DesktopTasksLimiter(
+ transitions,
+ userRepositories,
+ shellTaskOrganizer,
+ MAX_TASK_LIMIT,
+ mockInteractionJankMonitor,
+ mContext,
+ mockHandler,
+ )
+
+ whenever(shellTaskOrganizer.getRunningTasks(anyInt())).thenAnswer { runningTasks }
+ whenever(transitions.startTransition(anyInt(), any(), isNull())).thenAnswer { Binder() }
+ whenever(enterDesktopTransitionHandler.moveToDesktop(any(), any())).thenAnswer { Binder() }
+ whenever(displayController.getDisplayLayout(anyInt())).thenReturn(displayLayout)
+ whenever(displayLayout.getStableBounds(any())).thenAnswer { i ->
+ (i.arguments.first() as Rect).set(STABLE_BOUNDS)
+ }
+ whenever(runBlocking { persistentRepository.readDesktop(any(), any()) })
+ .thenReturn(Desktop.getDefaultInstance())
+ doReturn(mockToast).`when` { Toast.makeText(any(), anyInt(), anyInt()) }
+
+ val tda = DisplayAreaInfo(MockToken().token(), DEFAULT_DISPLAY, 0)
+ tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FULLSCREEN
+ whenever(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)).thenReturn(tda)
+ whenever(
+ mMockDesktopImmersiveController.exitImmersiveIfApplicable(
+ any(),
+ any<RunningTaskInfo>(),
+ any(),
+ )
+ )
+ .thenReturn(ExitResult.NoExit)
+ whenever(
+ mMockDesktopImmersiveController.exitImmersiveIfApplicable(
+ any(),
+ anyInt(),
+ anyOrNull(),
+ any(),
+ )
+ )
+ .thenReturn(ExitResult.NoExit)
+
+ controller = createController()
+ controller.setSplitScreenController(splitScreenController)
+ controller.freeformTaskTransitionStarter = freeformTaskTransitionStarter
+ controller.desktopModeEnterExitTransitionListener = desktopModeEnterExitTransitionListener
+
+ shellInit.init()
+
+ val captor = ArgumentCaptor.forClass(RecentsTransitionStateListener::class.java)
+ verify(recentsTransitionHandler).addTransitionStateListener(captor.capture())
+ recentsTransitionStateListener = captor.value
+
+ controller.taskbarDesktopTaskListener = taskbarDesktopTaskListener
+
+ assumeTrue(ENABLE_SHELL_TRANSITIONS)
+
+ taskRepository = userRepositories.current
+ }
+
+ private fun createController(): DesktopTasksController {
+ return DesktopTasksController(
+ context,
+ shellInit,
+ shellCommandHandler,
+ shellController,
+ displayController,
+ shellTaskOrganizer,
+ syncQueue,
+ rootTaskDisplayAreaOrganizer,
+ dragAndDropController,
transitions,
+ keyguardManager,
+ mReturnToDragStartAnimator,
+ desktopMixedTransitionHandler,
+ enterDesktopTransitionHandler,
+ exitDesktopTransitionHandler,
+ dragAndDropTransitionHandler,
+ toggleResizeDesktopTaskTransitionHandler,
+ dragToDesktopTransitionHandler,
+ mMockDesktopImmersiveController,
userRepositories,
- shellTaskOrganizer,
- MAX_TASK_LIMIT,
+ recentsTransitionHandler,
+ multiInstanceHelper,
+ shellExecutor,
+ Optional.of(desktopTasksLimiter),
+ recentTasksController,
mockInteractionJankMonitor,
- mContext,
- mockHandler)
-
- whenever(shellTaskOrganizer.getRunningTasks(anyInt())).thenAnswer { runningTasks }
- whenever(transitions.startTransition(anyInt(), any(), isNull())).thenAnswer { Binder() }
- whenever(enterDesktopTransitionHandler.moveToDesktop(any(), any())).thenAnswer { Binder() }
- whenever(displayController.getDisplayLayout(anyInt())).thenReturn(displayLayout)
- whenever(displayLayout.getStableBounds(any())).thenAnswer { i ->
- (i.arguments.first() as Rect).set(STABLE_BOUNDS)
- }
- whenever(runBlocking { persistentRepository.readDesktop(any(), any()) }).thenReturn(
- Desktop.getDefaultInstance()
- )
- doReturn(mockToast).`when` { Toast.makeText(any(), anyInt(), anyInt()) }
-
- val tda = DisplayAreaInfo(MockToken().token(), DEFAULT_DISPLAY, 0)
- tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FULLSCREEN
- whenever(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)).thenReturn(tda)
- whenever(mMockDesktopImmersiveController
- .exitImmersiveIfApplicable(any(), any<RunningTaskInfo>(), any()))
- .thenReturn(ExitResult.NoExit)
- whenever(mMockDesktopImmersiveController
- .exitImmersiveIfApplicable(any(), anyInt(), anyOrNull(), any()))
- .thenReturn(ExitResult.NoExit)
-
- controller = createController()
- controller.setSplitScreenController(splitScreenController)
- controller.freeformTaskTransitionStarter = freeformTaskTransitionStarter
- controller.desktopModeEnterExitTransitionListener = desktopModeEnterExitTransitionListener
-
- shellInit.init()
-
- val captor = ArgumentCaptor.forClass(RecentsTransitionStateListener::class.java)
- verify(recentsTransitionHandler).addTransitionStateListener(captor.capture())
- recentsTransitionStateListener = captor.value
-
- controller.taskbarDesktopTaskListener = taskbarDesktopTaskListener
-
- assumeTrue(ENABLE_SHELL_TRANSITIONS)
-
- taskRepository = userRepositories.current
- }
-
- private fun createController(): DesktopTasksController {
- return DesktopTasksController(
- context,
- shellInit,
- shellCommandHandler,
- shellController,
- displayController,
- shellTaskOrganizer,
- syncQueue,
- rootTaskDisplayAreaOrganizer,
- dragAndDropController,
- transitions,
- keyguardManager,
- mReturnToDragStartAnimator,
- desktopMixedTransitionHandler,
- enterDesktopTransitionHandler,
- exitDesktopTransitionHandler,
- dragAndDropTransitionHandler,
- toggleResizeDesktopTaskTransitionHandler,
- dragToDesktopTransitionHandler,
- mMockDesktopImmersiveController,
- userRepositories,
- recentsTransitionHandler,
- multiInstanceHelper,
- shellExecutor,
- Optional.of(desktopTasksLimiter),
- recentTasksController,
- mockInteractionJankMonitor,
- mockHandler,
- desktopModeEventLogger,
- desktopModeUiEventLogger,
- desktopTilingDecorViewModel,
- )
- }
-
- @After
- fun tearDown() {
- mockitoSession.finishMocking()
-
- runningTasks.clear()
- testScope.cancel()
- }
-
- @Test
- fun instantiate_addInitCallback() {
- verify(shellInit).addInitCallback(any(), any<DesktopTasksController>())
- }
-
- @Test
- fun doesAnyTaskRequireTaskbarRounding_onlyFreeFormTaskIsRunning_returnFalse() {
- setUpFreeformTask()
-
- assertThat(controller.doesAnyTaskRequireTaskbarRounding(DEFAULT_DISPLAY)).isFalse()
- }
-
- @Test
- fun doesAnyTaskRequireTaskbarRounding_toggleResizeOfFreeFormTask_returnTrue() {
- val task1 = setUpFreeformTask()
-
- val argumentCaptor = ArgumentCaptor.forClass(Boolean::class.java)
- controller.toggleDesktopTaskSize(
- task1,
- ToggleTaskSizeInteraction(
- ToggleTaskSizeInteraction.Direction.MAXIMIZE,
- ToggleTaskSizeInteraction.Source.HEADER_BUTTON_TO_MAXIMIZE,
- InputMethod.TOUCH
- )
- )
+ mockHandler,
+ desktopModeEventLogger,
+ desktopModeUiEventLogger,
+ desktopTilingDecorViewModel,
+ )
+ }
- verify(taskbarDesktopTaskListener).onTaskbarCornerRoundingUpdate(argumentCaptor.capture())
- verify(desktopModeEventLogger, times(1)).logTaskResizingEnded(
- ResizeTrigger.MAXIMIZE_BUTTON,
- InputMethod.TOUCH,
- task1,
- STABLE_BOUNDS.width(),
- STABLE_BOUNDS.height(),
- displayController
- )
- assertThat(argumentCaptor.value).isTrue()
- }
-
- @Test
- fun doesAnyTaskRequireTaskbarRounding_fullScreenTaskIsRunning_returnTrue() {
- val stableBounds = Rect().apply { displayLayout.getStableBounds(this) }
- setUpFreeformTask(bounds = stableBounds, active = true)
- assertThat(controller.doesAnyTaskRequireTaskbarRounding(DEFAULT_DISPLAY)).isTrue()
- }
-
- @Test
- fun doesAnyTaskRequireTaskbarRounding_toggleResizeOfMaximizedTask_returnFalse() {
- val stableBounds = Rect().apply { displayLayout.getStableBounds(this) }
- val task1 = setUpFreeformTask(bounds = stableBounds, active = true)
-
- val argumentCaptor = ArgumentCaptor.forClass(Boolean::class.java)
- controller.toggleDesktopTaskSize(
- task1,
- ToggleTaskSizeInteraction(
- ToggleTaskSizeInteraction.Direction.RESTORE,
- ToggleTaskSizeInteraction.Source.HEADER_BUTTON_TO_RESTORE,
- InputMethod.TOUCH
- )
- )
+ @After
+ fun tearDown() {
+ mockitoSession.finishMocking()
- verify(taskbarDesktopTaskListener).onTaskbarCornerRoundingUpdate(argumentCaptor.capture())
- verify(desktopModeEventLogger, times(1)).logTaskResizingEnded(
- eq(ResizeTrigger.MAXIMIZE_BUTTON),
- eq(InputMethod.TOUCH),
- eq(task1),
- anyOrNull(),
- anyOrNull(),
- eq(displayController),
- anyOrNull()
- )
- assertThat(argumentCaptor.value).isFalse()
- }
-
- @Test
- fun doesAnyTaskRequireTaskbarRounding_splitScreenTaskIsRunning_returnTrue() {
- val stableBounds = Rect().apply { displayLayout.getStableBounds(this) }
- setUpFreeformTask(bounds = Rect(stableBounds.left, stableBounds.top, 500, stableBounds.bottom))
-
- assertThat(controller.doesAnyTaskRequireTaskbarRounding(DEFAULT_DISPLAY)).isTrue()
- }
-
-
- @Test
- fun instantiate_cannotEnterDesktopMode_doNotAddInitCallback() {
- whenever(DesktopModeStatus.canEnterDesktopMode(context)).thenReturn(false)
- clearInvocations(shellInit)
-
- createController()
-
- verify(shellInit, never()).addInitCallback(any(), any<DesktopTasksController>())
- }
-
- @Test
- @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- fun showDesktopApps_allAppsInvisible_bringsToFront_desktopWallpaperDisabled() {
- val homeTask = setUpHomeTask()
- val task1 = setUpFreeformTask()
- val task2 = setUpFreeformTask()
- markTaskHidden(task1)
- markTaskHidden(task2)
-
- controller.showDesktopApps(DEFAULT_DISPLAY, RemoteTransition(TestRemoteTransition()))
-
- val wct = getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java)
- assertThat(wct.hierarchyOps).hasSize(3)
- // Expect order to be from bottom: home, task1, task2
- wct.assertReorderAt(index = 0, homeTask)
- wct.assertReorderAt(index = 1, task1)
- wct.assertReorderAt(index = 2, task2)
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- fun showDesktopApps_allAppsInvisible_bringsToFront_desktopWallpaperEnabled() {
- val task1 = setUpFreeformTask()
- val task2 = setUpFreeformTask()
- markTaskHidden(task1)
- markTaskHidden(task2)
-
- controller.showDesktopApps(DEFAULT_DISPLAY, RemoteTransition(TestRemoteTransition()))
-
- val wct = getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java)
- assertThat(wct.hierarchyOps).hasSize(3)
- // Expect order to be from bottom: wallpaper intent, task1, task2
- wct.assertPendingIntentAt(index = 0, desktopWallpaperIntent)
- wct.assertReorderAt(index = 1, task1)
- wct.assertReorderAt(index = 2, task2)
- }
-
- @Test
- fun isDesktopModeShowing_noTasks_returnsFalse() {
- assertThat(controller.isDesktopModeShowing(displayId = 0)).isFalse()
- }
-
- @Test
- fun isDesktopModeShowing_noTasksVisible_returnsFalse() {
- val task1 = setUpFreeformTask()
- val task2 = setUpFreeformTask()
- markTaskHidden(task1)
- markTaskHidden(task2)
-
- assertThat(controller.isDesktopModeShowing(displayId = 0)).isFalse()
- }
-
- @Test
- fun isDesktopModeShowing_tasksActiveAndVisible_returnsTrue() {
- val task1 = setUpFreeformTask()
- val task2 = setUpFreeformTask()
- markTaskVisible(task1)
- markTaskHidden(task2)
-
- assertThat(controller.isDesktopModeShowing(displayId = 0)).isTrue()
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- fun showDesktopApps_onSecondaryDisplay_desktopWallpaperEnabled_shouldNotShowWallpaper() {
- val homeTask = setUpHomeTask(SECOND_DISPLAY)
- val task1 = setUpFreeformTask(SECOND_DISPLAY)
- val task2 = setUpFreeformTask(SECOND_DISPLAY)
- markTaskHidden(task1)
- markTaskHidden(task2)
-
- controller.showDesktopApps(SECOND_DISPLAY, RemoteTransition(TestRemoteTransition()))
-
- val wct = getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java)
- assertThat(wct.hierarchyOps).hasSize(3)
- // Expect order to be from bottom: home, task1, task2 (no wallpaper intent)
- wct.assertReorderAt(index = 0, homeTask)
- wct.assertReorderAt(index = 1, task1)
- wct.assertReorderAt(index = 2, task2)
- }
-
- @Test
- @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- fun showDesktopApps_appsAlreadyVisible_bringsToFront_desktopWallpaperDisabled() {
- val homeTask = setUpHomeTask()
- val task1 = setUpFreeformTask()
- val task2 = setUpFreeformTask()
- markTaskVisible(task1)
- markTaskVisible(task2)
-
- controller.showDesktopApps(DEFAULT_DISPLAY, RemoteTransition(TestRemoteTransition()))
-
- val wct = getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java)
- assertThat(wct.hierarchyOps).hasSize(3)
- // Expect order to be from bottom: home, task1, task2
- wct.assertReorderAt(index = 0, homeTask)
- wct.assertReorderAt(index = 1, task1)
- wct.assertReorderAt(index = 2, task2)
- }
-
- @Test
- @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- fun showDesktopApps_onSecondaryDisplay_desktopWallpaperDisabled_shouldNotMoveLauncher() {
- val homeTask = setUpHomeTask(SECOND_DISPLAY)
- val task1 = setUpFreeformTask(SECOND_DISPLAY)
- val task2 = setUpFreeformTask(SECOND_DISPLAY)
- markTaskHidden(task1)
- markTaskHidden(task2)
-
- controller.showDesktopApps(SECOND_DISPLAY, RemoteTransition(TestRemoteTransition()))
-
- val wct = getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java)
- assertThat(wct.hierarchyOps).hasSize(3)
- // Expect order to be from bottom: home, task1, task2
- wct.assertReorderAt(index = 0, homeTask)
- wct.assertReorderAt(index = 1, task1)
- wct.assertReorderAt(index = 2, task2)
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- fun showDesktopApps_appsAlreadyVisible_bringsToFront_desktopWallpaperEnabled() {
- val task1 = setUpFreeformTask()
- val task2 = setUpFreeformTask()
- markTaskVisible(task1)
- markTaskVisible(task2)
-
- controller.showDesktopApps(DEFAULT_DISPLAY, RemoteTransition(TestRemoteTransition()))
-
- val wct = getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java)
- assertThat(wct.hierarchyOps).hasSize(3)
- // Expect order to be from bottom: wallpaper intent, task1, task2
- wct.assertPendingIntentAt(index = 0, desktopWallpaperIntent)
- wct.assertReorderAt(index = 1, task1)
- wct.assertReorderAt(index = 2, task2)
- }
-
- @Test
- @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- fun showDesktopApps_someAppsInvisible_reordersAll_desktopWallpaperDisabled() {
- val homeTask = setUpHomeTask()
- val task1 = setUpFreeformTask()
- val task2 = setUpFreeformTask()
- markTaskHidden(task1)
- markTaskVisible(task2)
-
- controller.showDesktopApps(DEFAULT_DISPLAY, RemoteTransition(TestRemoteTransition()))
-
- val wct = getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java)
- assertThat(wct.hierarchyOps).hasSize(3)
- // Expect order to be from bottom: home, task1, task2
- wct.assertReorderAt(index = 0, homeTask)
- wct.assertReorderAt(index = 1, task1)
- wct.assertReorderAt(index = 2, task2)
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- fun showDesktopApps_someAppsInvisible_reordersAll_desktopWallpaperEnabled() {
- val task1 = setUpFreeformTask()
- val task2 = setUpFreeformTask()
- markTaskHidden(task1)
- markTaskVisible(task2)
-
- controller.showDesktopApps(DEFAULT_DISPLAY, RemoteTransition(TestRemoteTransition()))
-
- val wct = getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java)
- assertThat(wct.hierarchyOps).hasSize(3)
- // Expect order to be from bottom: wallpaper intent, task1, task2
- wct.assertPendingIntentAt(index = 0, desktopWallpaperIntent)
- wct.assertReorderAt(index = 1, task1)
- wct.assertReorderAt(index = 2, task2)
- }
-
- @Test
- @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- fun showDesktopApps_noActiveTasks_reorderHomeToTop_desktopWallpaperDisabled() {
- val homeTask = setUpHomeTask()
-
- controller.showDesktopApps(DEFAULT_DISPLAY, RemoteTransition(TestRemoteTransition()))
-
- val wct = getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java)
- assertThat(wct.hierarchyOps).hasSize(1)
- wct.assertReorderAt(index = 0, homeTask)
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- fun showDesktopApps_noActiveTasks_addDesktopWallpaper_desktopWallpaperEnabled() {
- controller.showDesktopApps(DEFAULT_DISPLAY, RemoteTransition(TestRemoteTransition()))
-
- val wct = getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java)
- wct.assertPendingIntentAt(index = 0, desktopWallpaperIntent)
- }
-
- @Test
- @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- fun showDesktopApps_twoDisplays_bringsToFrontOnlyOneDisplay_desktopWallpaperDisabled() {
- val homeTaskDefaultDisplay = setUpHomeTask(DEFAULT_DISPLAY)
- val taskDefaultDisplay = setUpFreeformTask(DEFAULT_DISPLAY)
- setUpHomeTask(SECOND_DISPLAY)
- val taskSecondDisplay = setUpFreeformTask(SECOND_DISPLAY)
- markTaskHidden(taskDefaultDisplay)
- markTaskHidden(taskSecondDisplay)
-
- controller.showDesktopApps(DEFAULT_DISPLAY, RemoteTransition(TestRemoteTransition()))
-
- val wct = getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java)
- assertThat(wct.hierarchyOps).hasSize(2)
- // Expect order to be from bottom: home, task
- wct.assertReorderAt(index = 0, homeTaskDefaultDisplay)
- wct.assertReorderAt(index = 1, taskDefaultDisplay)
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- fun showDesktopApps_twoDisplays_bringsToFrontOnlyOneDisplay_desktopWallpaperEnabled() {
- val homeTaskDefaultDisplay = setUpHomeTask(DEFAULT_DISPLAY)
- val taskDefaultDisplay = setUpFreeformTask(DEFAULT_DISPLAY)
- setUpHomeTask(SECOND_DISPLAY)
- val taskSecondDisplay = setUpFreeformTask(SECOND_DISPLAY)
- markTaskHidden(taskDefaultDisplay)
- markTaskHidden(taskSecondDisplay)
-
- controller.showDesktopApps(DEFAULT_DISPLAY, RemoteTransition(TestRemoteTransition()))
-
- val wct = getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java)
- assertThat(wct.hierarchyOps).hasSize(3)
- // Move home to front
- wct.assertReorderAt(index = 0, homeTaskDefaultDisplay)
- // Add desktop wallpaper activity
- wct.assertPendingIntentAt(index = 1, desktopWallpaperIntent)
- // Move freeform task to front
- wct.assertReorderAt(index = 2, taskDefaultDisplay)
- }
-
- @Test
- @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- fun showDesktopApps_desktopWallpaperDisabled_dontReorderMinimizedTask() {
- val homeTask = setUpHomeTask()
- val freeformTask = setUpFreeformTask()
- val minimizedTask = setUpFreeformTask()
-
- markTaskHidden(freeformTask)
- markTaskHidden(minimizedTask)
- taskRepository.minimizeTask(DEFAULT_DISPLAY, minimizedTask.taskId)
- controller.showDesktopApps(DEFAULT_DISPLAY, RemoteTransition(TestRemoteTransition()))
-
- val wct = getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java)
- assertThat(wct.hierarchyOps).hasSize(2)
- // Reorder home and freeform task to top, don't reorder the minimized task
- wct.assertReorderAt(index = 0, homeTask, toTop = true)
- wct.assertReorderAt(index = 1, freeformTask, toTop = true)
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- fun showDesktopApps_desktopWallpaperEnabled_dontReorderMinimizedTask() {
- val homeTask = setUpHomeTask()
- val freeformTask = setUpFreeformTask()
- val minimizedTask = setUpFreeformTask()
-
- markTaskHidden(freeformTask)
- markTaskHidden(minimizedTask)
- taskRepository.minimizeTask(DEFAULT_DISPLAY, minimizedTask.taskId)
- controller.showDesktopApps(DEFAULT_DISPLAY, RemoteTransition(TestRemoteTransition()))
-
- val wct = getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java)
- assertThat(wct.hierarchyOps).hasSize(3)
- // Move home to front
- wct.assertReorderAt(index = 0, homeTask, toTop = true)
- // Add desktop wallpaper activity
- wct.assertPendingIntentAt(index = 1, desktopWallpaperIntent)
- // Reorder freeform task to top, don't reorder the minimized task
- wct.assertReorderAt(index = 2, freeformTask, toTop = true)
- }
-
- @Test
- fun visibleTaskCount_noTasks_returnsZero() {
- assertThat(controller.visibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(0)
- }
-
- @Test
- fun visibleTaskCount_twoTasks_bothVisible_returnsTwo() {
- setUpHomeTask()
- setUpFreeformTask().also(::markTaskVisible)
- setUpFreeformTask().also(::markTaskVisible)
- assertThat(controller.visibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(2)
- }
-
- @Test
- fun visibleTaskCount_twoTasks_oneVisible_returnsOne() {
- setUpHomeTask()
- setUpFreeformTask().also(::markTaskVisible)
- setUpFreeformTask().also(::markTaskHidden)
- assertThat(controller.visibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(1)
- }
-
- @Test
- fun visibleTaskCount_twoTasksVisibleOnDifferentDisplays_returnsOne() {
- setUpHomeTask()
- setUpFreeformTask(DEFAULT_DISPLAY).also(::markTaskVisible)
- setUpFreeformTask(SECOND_DISPLAY).also(::markTaskVisible)
- assertThat(controller.visibleTaskCount(SECOND_DISPLAY)).isEqualTo(1)
- }
-
- @Test
- fun addMoveToDesktopChanges_gravityLeft_noBoundsApplied() {
- setUpLandscapeDisplay()
- val task = setUpFullscreenTask(gravity = Gravity.LEFT)
- val wct = WindowContainerTransaction()
- controller.addMoveToDesktopChanges(wct, task)
-
- val finalBounds = findBoundsChange(wct, task)
- assertThat(finalBounds).isEqualTo(Rect())
- }
-
- @Test
- fun addMoveToDesktopChanges_gravityRight_noBoundsApplied() {
- setUpLandscapeDisplay()
- val task = setUpFullscreenTask(gravity = Gravity.RIGHT)
- val wct = WindowContainerTransaction()
- controller.addMoveToDesktopChanges(wct, task)
-
- val finalBounds = findBoundsChange(wct, task)
- assertThat(finalBounds).isEqualTo(Rect())
- }
-
- @Test
- fun addMoveToDesktopChanges_gravityTop_noBoundsApplied() {
- setUpLandscapeDisplay()
- val task = setUpFullscreenTask(gravity = Gravity.TOP)
- val wct = WindowContainerTransaction()
- controller.addMoveToDesktopChanges(wct, task)
-
- val finalBounds = findBoundsChange(wct, task)
- assertThat(finalBounds).isEqualTo(Rect())
- }
-
- @Test
- fun addMoveToDesktopChanges_gravityBottom_noBoundsApplied() {
- setUpLandscapeDisplay()
- val task = setUpFullscreenTask(gravity = Gravity.BOTTOM)
- val wct = WindowContainerTransaction()
- controller.addMoveToDesktopChanges(wct, task)
-
- val finalBounds = findBoundsChange(wct, task)
- assertThat(finalBounds).isEqualTo(Rect())
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_CASCADING_WINDOWS)
- fun handleRequest_newFreeformTaskLaunch_cascadeApplied() {
- setUpLandscapeDisplay()
- val stableBounds = Rect()
- displayLayout.getStableBoundsForDesktopMode(stableBounds)
-
- setUpFreeformTask(bounds = DEFAULT_LANDSCAPE_BOUNDS)
- val freeformTask = setUpFreeformTask(bounds = DEFAULT_LANDSCAPE_BOUNDS, active = false)
-
- val wct = controller.handleRequest(Binder(), createTransition(freeformTask))
-
- assertNotNull(wct, "should handle request")
- val finalBounds = findBoundsChange(wct, freeformTask)
- assertThat(stableBounds.getDesktopTaskPosition(finalBounds!!))
- .isEqualTo(DesktopTaskPosition.BottomRight)
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_CASCADING_WINDOWS)
- fun handleRequest_freeformTaskAlreadyExistsInDesktopMode_cascadeNotApplied() {
- setUpLandscapeDisplay()
- val stableBounds = Rect()
- displayLayout.getStableBoundsForDesktopMode(stableBounds)
-
- setUpFreeformTask(bounds = DEFAULT_LANDSCAPE_BOUNDS)
- val freeformTask = setUpFreeformTask(bounds = DEFAULT_LANDSCAPE_BOUNDS)
-
- val wct = controller.handleRequest(Binder(), createTransition(freeformTask))
-
- assertNull(wct, "should not handle request")
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_CASCADING_WINDOWS)
- fun addMoveToDesktopChanges_positionBottomRight() {
- setUpLandscapeDisplay()
- val stableBounds = Rect()
- displayLayout.getStableBoundsForDesktopMode(stableBounds)
-
- setUpFreeformTask(bounds = DEFAULT_LANDSCAPE_BOUNDS)
-
- val task = setUpFullscreenTask()
- val wct = WindowContainerTransaction()
- controller.addMoveToDesktopChanges(wct, task)
-
- val finalBounds = findBoundsChange(wct, task)
- assertThat(stableBounds.getDesktopTaskPosition(finalBounds!!))
- .isEqualTo(DesktopTaskPosition.BottomRight)
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_CASCADING_WINDOWS)
- fun addMoveToDesktopChanges_positionTopLeft() {
- setUpLandscapeDisplay()
- val stableBounds = Rect()
- displayLayout.getStableBoundsForDesktopMode(stableBounds)
-
- addFreeformTaskAtPosition(DesktopTaskPosition.BottomRight, stableBounds)
-
- val task = setUpFullscreenTask()
- val wct = WindowContainerTransaction()
- controller.addMoveToDesktopChanges(wct, task)
-
- val finalBounds = findBoundsChange(wct, task)
- assertThat(stableBounds.getDesktopTaskPosition(finalBounds!!))
- .isEqualTo(DesktopTaskPosition.TopLeft)
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_CASCADING_WINDOWS)
- fun addMoveToDesktopChanges_positionBottomLeft() {
- setUpLandscapeDisplay()
- val stableBounds = Rect()
- displayLayout.getStableBoundsForDesktopMode(stableBounds)
-
- addFreeformTaskAtPosition(DesktopTaskPosition.TopLeft, stableBounds)
-
- val task = setUpFullscreenTask()
- val wct = WindowContainerTransaction()
- controller.addMoveToDesktopChanges(wct, task)
-
- val finalBounds = findBoundsChange(wct, task)
- assertThat(stableBounds.getDesktopTaskPosition(finalBounds!!))
- .isEqualTo(DesktopTaskPosition.BottomLeft)
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_CASCADING_WINDOWS)
- fun addMoveToDesktopChanges_positionTopRight() {
- setUpLandscapeDisplay()
- val stableBounds = Rect()
- displayLayout.getStableBoundsForDesktopMode(stableBounds)
-
- addFreeformTaskAtPosition(DesktopTaskPosition.BottomLeft, stableBounds)
-
- val task = setUpFullscreenTask()
- val wct = WindowContainerTransaction()
- controller.addMoveToDesktopChanges(wct, task)
-
- val finalBounds = findBoundsChange(wct, task)
- assertThat(stableBounds.getDesktopTaskPosition(finalBounds!!))
- .isEqualTo(DesktopTaskPosition.TopRight)
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_CASCADING_WINDOWS)
- fun addMoveToDesktopChanges_positionResetsToCenter() {
- setUpLandscapeDisplay()
- val stableBounds = Rect()
- displayLayout.getStableBoundsForDesktopMode(stableBounds)
-
- addFreeformTaskAtPosition(DesktopTaskPosition.TopRight, stableBounds)
-
- val task = setUpFullscreenTask()
- val wct = WindowContainerTransaction()
- controller.addMoveToDesktopChanges(wct, task)
-
- val finalBounds = findBoundsChange(wct, task)
- assertThat(stableBounds.getDesktopTaskPosition(finalBounds!!))
- .isEqualTo(DesktopTaskPosition.Center)
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_CASCADING_WINDOWS)
- fun addMoveToDesktopChanges_lastWindowSnapLeft_positionResetsToCenter() {
- setUpLandscapeDisplay()
- val stableBounds = Rect()
- displayLayout.getStableBoundsForDesktopMode(stableBounds)
-
- // Add freeform task with half display size snap bounds at left side.
- setUpFreeformTask(bounds = Rect(stableBounds.left, stableBounds.top, 500, stableBounds.bottom))
-
- val task = setUpFullscreenTask()
- val wct = WindowContainerTransaction()
- controller.addMoveToDesktopChanges(wct, task)
-
- val finalBounds = findBoundsChange(wct, task)
- assertThat(stableBounds.getDesktopTaskPosition(finalBounds!!))
- .isEqualTo(DesktopTaskPosition.Center)
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_CASCADING_WINDOWS)
- fun addMoveToDesktopChanges_lastWindowSnapRight_positionResetsToCenter() {
- setUpLandscapeDisplay()
- val stableBounds = Rect()
- displayLayout.getStableBoundsForDesktopMode(stableBounds)
-
- // Add freeform task with half display size snap bounds at right side.
- setUpFreeformTask(bounds = Rect(
- stableBounds.right - 500, stableBounds.top, stableBounds.right, stableBounds.bottom))
-
- val task = setUpFullscreenTask()
- val wct = WindowContainerTransaction()
- controller.addMoveToDesktopChanges(wct, task)
-
- val finalBounds = findBoundsChange(wct, task)
- assertThat(stableBounds.getDesktopTaskPosition(finalBounds!!))
- .isEqualTo(DesktopTaskPosition.Center)
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_CASCADING_WINDOWS)
- fun addMoveToDesktopChanges_lastWindowMaximised_positionResetsToCenter() {
- setUpLandscapeDisplay()
- val stableBounds = Rect()
- displayLayout.getStableBoundsForDesktopMode(stableBounds)
-
- // Add maximised freeform task.
- setUpFreeformTask(bounds = Rect(stableBounds))
-
- val task = setUpFullscreenTask()
- val wct = WindowContainerTransaction()
- controller.addMoveToDesktopChanges(wct, task)
-
- val finalBounds = findBoundsChange(wct, task)
- assertThat(stableBounds.getDesktopTaskPosition(finalBounds!!))
- .isEqualTo(DesktopTaskPosition.Center)
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_CASCADING_WINDOWS)
- fun addMoveToDesktopChanges_defaultToCenterIfFree() {
- setUpLandscapeDisplay()
- val stableBounds = Rect()
- displayLayout.getStableBoundsForDesktopMode(stableBounds)
-
- val minTouchTarget = context.resources.getDimensionPixelSize(
- R.dimen.freeform_required_visible_empty_space_in_header)
- addFreeformTaskAtPosition(DesktopTaskPosition.Center, stableBounds,
- Rect(0, 0, 1600, 1200), Point(0, minTouchTarget + 1))
-
- val task = setUpFullscreenTask()
- val wct = WindowContainerTransaction()
- controller.addMoveToDesktopChanges(wct, task)
-
- val finalBounds = findBoundsChange(wct, task)
- assertThat(stableBounds.getDesktopTaskPosition(finalBounds!!))
- .isEqualTo(DesktopTaskPosition.Center)
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
- fun addMoveToDesktopChanges_landscapeDevice_userFullscreenOverride_defaultPortraitBounds() {
- setUpLandscapeDisplay()
- val task = setUpFullscreenTask(enableUserFullscreenOverride = true)
- val wct = WindowContainerTransaction()
- controller.addMoveToDesktopChanges(wct, task)
-
- assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_LANDSCAPE_BOUNDS)
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
- fun addMoveToDesktopChanges_landscapeDevice_systemFullscreenOverride_defaultPortraitBounds() {
- setUpLandscapeDisplay()
- val task = setUpFullscreenTask(enableSystemFullscreenOverride = true)
- val wct = WindowContainerTransaction()
- controller.addMoveToDesktopChanges(wct, task)
-
- assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_LANDSCAPE_BOUNDS)
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
- fun addMoveToDesktopChanges_landscapeDevice_portraitResizableApp_aspectRatioOverridden() {
- setUpLandscapeDisplay()
- val task = setUpFullscreenTask(screenOrientation = SCREEN_ORIENTATION_PORTRAIT,
- shouldLetterbox = true, aspectRatioOverrideApplied = true)
- val wct = WindowContainerTransaction()
- controller.addMoveToDesktopChanges(wct, task)
-
- assertThat(findBoundsChange(wct, task)).isEqualTo(UNRESIZABLE_PORTRAIT_BOUNDS)
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
- fun addMoveToDesktopChanges_portraitDevice_userFullscreenOverride_defaultPortraitBounds() {
- setUpPortraitDisplay()
- val task = setUpFullscreenTask(enableUserFullscreenOverride = true)
- val wct = WindowContainerTransaction()
- controller.addMoveToDesktopChanges(wct, task)
-
- assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_PORTRAIT_BOUNDS)
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
- fun addMoveToDesktopChanges_portraitDevice_systemFullscreenOverride_defaultPortraitBounds() {
- setUpPortraitDisplay()
- val task = setUpFullscreenTask(enableSystemFullscreenOverride = true)
- val wct = WindowContainerTransaction()
- controller.addMoveToDesktopChanges(wct, task)
-
- assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_PORTRAIT_BOUNDS)
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
- fun addMoveToDesktopChanges_portraitDevice_landscapeResizableApp_aspectRatioOverridden() {
- setUpPortraitDisplay()
- val task = setUpFullscreenTask(screenOrientation = SCREEN_ORIENTATION_LANDSCAPE,
- deviceOrientation = ORIENTATION_PORTRAIT,
- shouldLetterbox = true, aspectRatioOverrideApplied = true)
- val wct = WindowContainerTransaction()
- controller.addMoveToDesktopChanges(wct, task)
-
- assertThat(findBoundsChange(wct, task)).isEqualTo(UNRESIZABLE_LANDSCAPE_BOUNDS)
- }
-
- @Test
- fun moveToDesktop_tdaFullscreen_windowingModeSetToFreeform() {
- val task = setUpFullscreenTask()
- val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!!
- tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FULLSCREEN
- controller.moveRunningTaskToDesktop(task, transitionSource = UNKNOWN)
- val wct = getLatestEnterDesktopWct()
- assertThat(wct.changes[task.token.asBinder()]?.windowingMode).isEqualTo(WINDOWING_MODE_FREEFORM)
- verify(desktopModeEnterExitTransitionListener).onEnterDesktopModeTransitionStarted(FREEFORM_ANIMATION_DURATION)
- }
-
- @Test
- fun moveRunningTaskToDesktop_tdaFreeform_windowingModeSetToUndefined() {
- val task = setUpFullscreenTask()
- val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!!
- tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FREEFORM
- controller.moveRunningTaskToDesktop(task, transitionSource = UNKNOWN)
- val wct = getLatestEnterDesktopWct()
- assertThat(wct.changes[task.token.asBinder()]?.windowingMode)
- .isEqualTo(WINDOWING_MODE_UNDEFINED)
- verify(desktopModeEnterExitTransitionListener).onEnterDesktopModeTransitionStarted(FREEFORM_ANIMATION_DURATION)
- }
-
- @Test
- fun moveTaskToDesktop_nonExistentTask_doesNothing() {
- controller.moveTaskToDesktop(999, transitionSource = UNKNOWN)
- verifyEnterDesktopWCTNotExecuted()
- verify(desktopModeEnterExitTransitionListener, times(0)).onEnterDesktopModeTransitionStarted(anyInt())
- }
-
- @Test
- @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- fun moveTaskToDesktop_desktopWallpaperDisabled_nonRunningTask_launchesInFreeform() {
- val task = createTaskInfo(1)
- whenever(shellTaskOrganizer.getRunningTaskInfo(anyInt())).thenReturn(null)
- whenever(recentTasksController.findTaskInBackground(anyInt())).thenReturn(task)
-
- controller.moveTaskToDesktop(task.taskId, transitionSource = UNKNOWN)
-
- with(getLatestEnterDesktopWct()) {
- assertLaunchTaskAt(0, task.taskId, WINDOWING_MODE_FREEFORM)
- }
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- fun moveTaskToDesktop_desktopWallpaperEnabled_nonRunningTask_launchesInFreeform() {
- val task = createTaskInfo(1)
- whenever(shellTaskOrganizer.getRunningTaskInfo(anyInt())).thenReturn(null)
- whenever(recentTasksController.findTaskInBackground(anyInt())).thenReturn(task)
-
- controller.moveTaskToDesktop(task.taskId, transitionSource = UNKNOWN)
-
- with(getLatestEnterDesktopWct()) {
- // Add desktop wallpaper activity
- assertPendingIntentAt(index = 0, desktopWallpaperIntent)
- // Launch task
- assertLaunchTaskAt(index = 1, task.taskId, WINDOWING_MODE_FREEFORM)
- }
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
- fun moveRunningTaskToDesktop_topActivityTranslucentWithoutDisplay_taskIsMovedToDesktop() {
- val task =
- setUpFullscreenTask().apply {
- isActivityStackTransparent = true
- isTopActivityNoDisplay = true
- numActivities = 1
- }
-
- controller.moveRunningTaskToDesktop(task, transitionSource = UNKNOWN)
- val wct = getLatestEnterDesktopWct()
- assertThat(wct.changes[task.token.asBinder()]?.windowingMode).isEqualTo(WINDOWING_MODE_FREEFORM)
- verify(desktopModeEnterExitTransitionListener).onEnterDesktopModeTransitionStarted(FREEFORM_ANIMATION_DURATION)
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
- fun moveRunningTaskToDesktop_topActivityTranslucentWithDisplay_doesNothing() {
- val task =
- setUpFullscreenTask().apply {
- isActivityStackTransparent = true
- isTopActivityNoDisplay = false
- numActivities = 1
- }
-
- controller.moveRunningTaskToDesktop(task, transitionSource = UNKNOWN)
- verifyEnterDesktopWCTNotExecuted()
- verify(desktopModeEnterExitTransitionListener, times(0)).onEnterDesktopModeTransitionStarted(
- FREEFORM_ANIMATION_DURATION
- )
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
- fun moveRunningTaskToDesktop_systemUIActivityWithDisplay_doesNothing() {
- // Set task as systemUI package
- val systemUIPackageName = context.resources.getString(
- com.android.internal.R.string.config_systemUi)
- val baseComponent = ComponentName(systemUIPackageName, /* class */ "")
- val task =
- setUpFullscreenTask().apply {
- baseActivity = baseComponent
- isTopActivityNoDisplay = false
- }
-
- controller.moveRunningTaskToDesktop(task, transitionSource = UNKNOWN)
- verifyEnterDesktopWCTNotExecuted()
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
- fun moveRunningTaskToDesktop_systemUIActivityWithoutDisplay_doesNothing() {
- // Set task as systemUI package
- val systemUIPackageName = context.resources.getString(
- com.android.internal.R.string.config_systemUi)
- val baseComponent = ComponentName(systemUIPackageName, /* class */ "")
- val task =
- setUpFullscreenTask().apply {
- baseActivity = baseComponent
- isTopActivityNoDisplay = true
- }
-
- controller.moveRunningTaskToDesktop(task, transitionSource = UNKNOWN)
-
- val wct = getLatestEnterDesktopWct()
- assertThat(wct.changes[task.token.asBinder()]?.windowingMode).isEqualTo(WINDOWING_MODE_FREEFORM)
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- fun moveBackgroundTaskToDesktop_remoteTransition_usesOneShotHandler() {
- val transitionHandlerArgCaptor = ArgumentCaptor.forClass(TransitionHandler::class.java)
- whenever(
- transitions.startTransition(anyInt(), any(), transitionHandlerArgCaptor.capture())
- ).thenReturn(Binder())
-
- val task = createTaskInfo(1)
- whenever(shellTaskOrganizer.getRunningTaskInfo(anyInt())).thenReturn(null)
- whenever(recentTasksController.findTaskInBackground(anyInt())).thenReturn(task)
- controller.moveTaskToDesktop(
- taskId = task.taskId,
- transitionSource = UNKNOWN,
- remoteTransition = RemoteTransition(spy(TestRemoteTransition())))
-
- verify(desktopModeEnterExitTransitionListener).onEnterDesktopModeTransitionStarted(FREEFORM_ANIMATION_DURATION)
- assertIs<OneShotRemoteHandler>(transitionHandlerArgCaptor.value)
- }
-
-
- @Test
- fun moveRunningTaskToDesktop_remoteTransition_usesOneShotHandler() {
- val transitionHandlerArgCaptor = ArgumentCaptor.forClass(TransitionHandler::class.java)
- whenever(
- transitions.startTransition(anyInt(), any(), transitionHandlerArgCaptor.capture())
- ).thenReturn(Binder())
-
- controller.moveRunningTaskToDesktop(
- task = setUpFullscreenTask(),
- transitionSource = UNKNOWN,
- remoteTransition = RemoteTransition(spy(TestRemoteTransition())))
-
- verify(desktopModeEnterExitTransitionListener).onEnterDesktopModeTransitionStarted(FREEFORM_ANIMATION_DURATION)
- assertIs<OneShotRemoteHandler>(transitionHandlerArgCaptor.value)
- }
-
- @Test
- @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- fun moveRunningTaskToDesktop_otherFreeformTasksBroughtToFront_desktopWallpaperDisabled() {
- val homeTask = setUpHomeTask()
- val freeformTask = setUpFreeformTask()
- val fullscreenTask = setUpFullscreenTask()
- markTaskHidden(freeformTask)
-
- controller.moveRunningTaskToDesktop(fullscreenTask, transitionSource = UNKNOWN)
-
- with(getLatestEnterDesktopWct()) {
- // Operations should include home task, freeform task
- assertThat(hierarchyOps).hasSize(3)
- assertReorderSequence(homeTask, freeformTask, fullscreenTask)
- assertThat(changes[fullscreenTask.token.asBinder()]?.windowingMode)
- .isEqualTo(WINDOWING_MODE_FREEFORM)
- }
- verify(desktopModeEnterExitTransitionListener).onEnterDesktopModeTransitionStarted(FREEFORM_ANIMATION_DURATION)
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- fun moveRunningTaskToDesktop_otherFreeformTasksBroughtToFront_desktopWallpaperEnabled() {
- val freeformTask = setUpFreeformTask()
- val fullscreenTask = setUpFullscreenTask()
- markTaskHidden(freeformTask)
-
- controller.moveRunningTaskToDesktop(fullscreenTask, transitionSource = UNKNOWN)
-
- with(getLatestEnterDesktopWct()) {
- // Operations should include wallpaper intent, freeform task, fullscreen task
- assertThat(hierarchyOps).hasSize(3)
- assertPendingIntentAt(index = 0, desktopWallpaperIntent)
- assertReorderAt(index = 1, freeformTask)
- assertReorderAt(index = 2, fullscreenTask)
- assertThat(changes[fullscreenTask.token.asBinder()]?.windowingMode)
- .isEqualTo(WINDOWING_MODE_FREEFORM)
- }
- verify(desktopModeEnterExitTransitionListener).onEnterDesktopModeTransitionStarted(FREEFORM_ANIMATION_DURATION)
- }
-
- @Test
- fun moveRunningTaskToDesktop_onlyFreeformTasksFromCurrentDisplayBroughtToFront() {
- setUpHomeTask(displayId = DEFAULT_DISPLAY)
- val freeformTaskDefault = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
- val fullscreenTaskDefault = setUpFullscreenTask(displayId = DEFAULT_DISPLAY)
- markTaskHidden(freeformTaskDefault)
-
- val homeTaskSecond = setUpHomeTask(displayId = SECOND_DISPLAY)
- val freeformTaskSecond = setUpFreeformTask(displayId = SECOND_DISPLAY)
- markTaskHidden(freeformTaskSecond)
-
- controller.moveRunningTaskToDesktop(fullscreenTaskDefault, transitionSource = UNKNOWN)
-
- with(getLatestEnterDesktopWct()) {
- // Check that hierarchy operations do not include tasks from second display
- assertThat(hierarchyOps.map { it.container }).doesNotContain(homeTaskSecond.token.asBinder())
- assertThat(hierarchyOps.map { it.container })
- .doesNotContain(freeformTaskSecond.token.asBinder())
- }
- verify(desktopModeEnterExitTransitionListener).onEnterDesktopModeTransitionStarted(FREEFORM_ANIMATION_DURATION)
- }
-
- @Test
- fun moveRunningTaskToDesktop_splitTaskExitsSplit() {
- val task = setUpSplitScreenTask()
- controller.moveRunningTaskToDesktop(task, transitionSource = UNKNOWN)
- val wct = getLatestEnterDesktopWct()
- assertThat(wct.changes[task.token.asBinder()]?.windowingMode).isEqualTo(WINDOWING_MODE_FREEFORM)
- verify(desktopModeEnterExitTransitionListener).onEnterDesktopModeTransitionStarted(FREEFORM_ANIMATION_DURATION)
- verify(splitScreenController)
- .prepareExitSplitScreen(any(), anyInt(), eq(SplitScreenController.EXIT_REASON_DESKTOP_MODE))
- }
-
- @Test
- fun moveRunningTaskToDesktop_fullscreenTaskDoesNotExitSplit() {
- val task = setUpFullscreenTask()
- controller.moveRunningTaskToDesktop(task, transitionSource = UNKNOWN)
- val wct = getLatestEnterDesktopWct()
- assertThat(wct.changes[task.token.asBinder()]?.windowingMode).isEqualTo(WINDOWING_MODE_FREEFORM)
- verify(desktopModeEnterExitTransitionListener).onEnterDesktopModeTransitionStarted(FREEFORM_ANIMATION_DURATION)
- verify(splitScreenController, never())
- .prepareExitSplitScreen(any(), anyInt(), eq(SplitScreenController.EXIT_REASON_DESKTOP_MODE))
- }
-
- @Test
- @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- fun moveRunningTaskToDesktop_desktopWallpaperDisabled_bringsTasksOver_dontShowBackTask() {
- val freeformTasks = (1..MAX_TASK_LIMIT).map { _ -> setUpFreeformTask() }
- val newTask = setUpFullscreenTask()
- val homeTask = setUpHomeTask()
-
- controller.moveRunningTaskToDesktop(newTask, transitionSource = UNKNOWN)
-
- val wct = getLatestEnterDesktopWct()
- verify(desktopModeEnterExitTransitionListener).onEnterDesktopModeTransitionStarted(FREEFORM_ANIMATION_DURATION)
- assertThat(wct.hierarchyOps.size).isEqualTo(MAX_TASK_LIMIT + 1) // visible tasks + home
- wct.assertReorderAt(0, homeTask)
- wct.assertReorderSequenceInRange(
- range = 1..<(MAX_TASK_LIMIT + 1),
- *freeformTasks.drop(1).toTypedArray(), // Skipping freeformTasks[0]
- newTask)
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- fun moveRunningTaskToDesktop_desktopWallpaperEnabled_bringsTasksOverLimit_dontShowBackTask() {
- val freeformTasks = (1..MAX_TASK_LIMIT).map { _ -> setUpFreeformTask() }
- val newTask = setUpFullscreenTask()
- val homeTask = setUpHomeTask()
-
- controller.moveRunningTaskToDesktop(newTask, transitionSource = UNKNOWN)
-
- val wct = getLatestEnterDesktopWct()
- verify(desktopModeEnterExitTransitionListener).onEnterDesktopModeTransitionStarted(FREEFORM_ANIMATION_DURATION)
- assertThat(wct.hierarchyOps.size).isEqualTo(MAX_TASK_LIMIT + 2) // tasks + home + wallpaper
- // Move home to front
- wct.assertReorderAt(0, homeTask)
- // Add desktop wallpaper activity
- wct.assertPendingIntentAt(1, desktopWallpaperIntent)
- // Bring freeform tasks to front
- wct.assertReorderSequenceInRange(
- range = 2..<(MAX_TASK_LIMIT + 2),
- *freeformTasks.drop(1).toTypedArray(), // Skipping freeformTasks[0]
- newTask)
- }
-
- @Test
- fun moveToFullscreen_tdaFullscreen_windowingModeSetToUndefined() {
- val task = setUpFreeformTask()
- val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!!
- tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FULLSCREEN
- controller.moveToFullscreen(task.taskId, transitionSource = UNKNOWN)
- val wct = getLatestExitDesktopWct()
- verify(desktopModeEnterExitTransitionListener, times(1)).onExitDesktopModeTransitionStarted(FULLSCREEN_ANIMATION_DURATION)
- assertThat(wct.changes[task.token.asBinder()]?.windowingMode)
- .isEqualTo(WINDOWING_MODE_UNDEFINED)
- }
-
- @Test
- fun moveToFullscreen_tdaFullscreen_windowingModeUndefined_removesWallpaperActivity() {
- val task = setUpFreeformTask()
- val wallpaperToken = MockToken().token()
-
- taskRepository.wallpaperActivityToken = wallpaperToken
- assertNotNull(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY))
- .configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FULLSCREEN
-
- controller.moveToFullscreen(task.taskId, transitionSource = UNKNOWN)
-
- val wct = getLatestExitDesktopWct()
- val taskChange = assertNotNull(wct.changes[task.token.asBinder()])
- verify(desktopModeEnterExitTransitionListener).onExitDesktopModeTransitionStarted(FULLSCREEN_ANIMATION_DURATION)
- assertThat(taskChange.windowingMode).isEqualTo(WINDOWING_MODE_UNDEFINED)
- // Removes wallpaper activity when leaving desktop
- wct.assertRemoveAt(index = 0, wallpaperToken)
- }
-
- @Test
- fun moveToFullscreen_tdaFreeform_windowingModeSetToFullscreen() {
- val task = setUpFreeformTask()
- val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!!
- tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FREEFORM
- controller.moveToFullscreen(task.taskId, transitionSource = UNKNOWN)
- val wct = getLatestExitDesktopWct()
- assertThat(wct.changes[task.token.asBinder()]?.windowingMode)
- .isEqualTo(WINDOWING_MODE_FULLSCREEN)
- verify(desktopModeEnterExitTransitionListener).onExitDesktopModeTransitionStarted(FULLSCREEN_ANIMATION_DURATION)
- }
-
- @Test
- fun moveToFullscreen_tdaFreeform_windowingModeFullscreen_removesWallpaperActivity() {
- val task = setUpFreeformTask()
- val wallpaperToken = MockToken().token()
-
- taskRepository.wallpaperActivityToken = wallpaperToken
- assertNotNull(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY))
- .configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FREEFORM
-
- controller.moveToFullscreen(task.taskId, transitionSource = UNKNOWN)
-
- val wct = getLatestExitDesktopWct()
- val taskChange = assertNotNull(wct.changes[task.token.asBinder()])
- assertThat(taskChange.windowingMode).isEqualTo(WINDOWING_MODE_FULLSCREEN)
- verify(desktopModeEnterExitTransitionListener).onExitDesktopModeTransitionStarted(FULLSCREEN_ANIMATION_DURATION)
- // Removes wallpaper activity when leaving desktop
- wct.assertRemoveAt(index = 0, wallpaperToken)
- }
-
- @Test
- fun moveToFullscreen_multipleVisibleNonMinimizedTasks_doesNotRemoveWallpaperActivity() {
- val task1 = setUpFreeformTask()
- // Setup task2
- setUpFreeformTask()
- val wallpaperToken = MockToken().token()
-
- taskRepository.wallpaperActivityToken = wallpaperToken
- assertNotNull(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY))
- .configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FULLSCREEN
-
- controller.moveToFullscreen(task1.taskId, transitionSource = UNKNOWN)
-
- val wct = getLatestExitDesktopWct()
- val task1Change = assertNotNull(wct.changes[task1.token.asBinder()])
- assertThat(task1Change.windowingMode).isEqualTo(WINDOWING_MODE_UNDEFINED)
- verify(desktopModeEnterExitTransitionListener).onExitDesktopModeTransitionStarted(FULLSCREEN_ANIMATION_DURATION)
- // Does not remove wallpaper activity, as desktop still has a visible desktop task
- assertThat(wct.hierarchyOps).isEmpty()
- }
-
- @Test
- fun moveToFullscreen_nonExistentTask_doesNothing() {
- controller.moveToFullscreen(999, transitionSource = UNKNOWN)
- verifyExitDesktopWCTNotExecuted()
- }
-
- @Test
- fun moveToFullscreen_secondDisplayTaskHasFreeform_secondDisplayNotAffected() {
- val taskDefaultDisplay = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
- val taskSecondDisplay = setUpFreeformTask(displayId = SECOND_DISPLAY)
- controller.moveToFullscreen(taskDefaultDisplay.taskId, transitionSource = UNKNOWN)
-
- with(getLatestExitDesktopWct()) {
- assertThat(changes.keys).contains(taskDefaultDisplay.token.asBinder())
- assertThat(changes.keys).doesNotContain(taskSecondDisplay.token.asBinder())
- }
- verify(desktopModeEnterExitTransitionListener).onExitDesktopModeTransitionStarted(FULLSCREEN_ANIMATION_DURATION)
- }
-
- @Test
- fun moveTaskToFront_postsWctWithReorderOp() {
- val task1 = setUpFreeformTask()
- setUpFreeformTask()
-
- controller.moveTaskToFront(task1, remoteTransition = null)
-
- val wct = getLatestDesktopMixedTaskWct(type = TRANSIT_TO_FRONT)
- assertThat(wct.hierarchyOps).hasSize(1)
- wct.assertReorderAt(index = 0, task1)
- }
-
- @Test
- fun moveTaskToFront_bringsTasksOverLimit_minimizesBackTask() {
- setUpHomeTask()
- val freeformTasks = (1..MAX_TASK_LIMIT + 1).map { _ -> setUpFreeformTask() }
- whenever(desktopMixedTransitionHandler.startLaunchTransition(
- eq(TRANSIT_TO_FRONT),
- any(),
- eq(freeformTasks[0].taskId),
- anyOrNull(),
- anyOrNull(),
- )).thenReturn(Binder())
-
- controller.moveTaskToFront(freeformTasks[0], remoteTransition = null)
-
- val wct = getLatestDesktopMixedTaskWct(type = TRANSIT_TO_FRONT)
- assertThat(wct.hierarchyOps.size).isEqualTo(2) // move-to-front + minimize
- wct.assertReorderAt(0, freeformTasks[0], toTop = true)
- wct.assertReorderAt(1, freeformTasks[1], toTop = false)
- }
-
- @Test
- fun moveTaskToFront_remoteTransition_usesOneshotHandler() {
- setUpHomeTask()
- val freeformTasks = List(MAX_TASK_LIMIT) { setUpFreeformTask() }
- val transitionHandlerArgCaptor = ArgumentCaptor.forClass(TransitionHandler::class.java)
- whenever(
- transitions.startTransition(anyInt(), any(), transitionHandlerArgCaptor.capture())
- ).thenReturn(Binder())
-
- controller.moveTaskToFront(freeformTasks[0], RemoteTransition(TestRemoteTransition()))
-
- assertIs<OneShotRemoteHandler>(transitionHandlerArgCaptor.value)
- }
-
- @Test
- fun moveTaskToFront_bringsTasksOverLimit_remoteTransition_usesWindowLimitHandler() {
- setUpHomeTask()
- val freeformTasks = List(MAX_TASK_LIMIT + 1) { setUpFreeformTask() }
- val transitionHandlerArgCaptor = ArgumentCaptor.forClass(TransitionHandler::class.java)
- whenever(
- transitions.startTransition(anyInt(), any(), transitionHandlerArgCaptor.capture())
- ).thenReturn(Binder())
-
- controller.moveTaskToFront(freeformTasks[0], RemoteTransition(TestRemoteTransition()))
-
- assertThat(transitionHandlerArgCaptor.value)
- .isInstanceOf(DesktopWindowLimitRemoteHandler::class.java)
- }
-
- @Test
- fun moveTaskToFront_backgroundTask_launchesTask() {
- val task = createTaskInfo(1)
- whenever(shellTaskOrganizer.getRunningTaskInfo(anyInt())).thenReturn(null)
-
- controller.moveTaskToFront(task.taskId, remoteTransition = null)
-
- val wct = getLatestDesktopMixedTaskWct(type = TRANSIT_OPEN)
- assertThat(wct.hierarchyOps).hasSize(1)
- wct.assertLaunchTaskAt(0, task.taskId, WINDOWING_MODE_FREEFORM)
- }
-
- @Test
- fun moveTaskToFront_backgroundTaskBringsTasksOverLimit_minimizesBackTask() {
- val freeformTasks = (1..MAX_TASK_LIMIT).map { _ -> setUpFreeformTask() }
- val task = createTaskInfo(1001)
- whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(null)
- whenever(desktopMixedTransitionHandler
- .startLaunchTransition(eq(TRANSIT_OPEN), any(), eq(task.taskId), anyOrNull(), anyOrNull()))
- .thenReturn(Binder())
-
- controller.moveTaskToFront(task.taskId, remoteTransition = null)
-
- val wct = getLatestDesktopMixedTaskWct(type = TRANSIT_OPEN)
- assertThat(wct.hierarchyOps.size).isEqualTo(2) // launch + minimize
- wct.assertLaunchTaskAt(0, task.taskId, WINDOWING_MODE_FREEFORM)
- wct.assertReorderAt(1, freeformTasks[0], toTop = false)
- }
-
- @Test
- fun moveToNextDisplay_noOtherDisplays() {
- whenever(rootTaskDisplayAreaOrganizer.displayIds).thenReturn(intArrayOf(DEFAULT_DISPLAY))
- val task = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
- controller.moveToNextDisplay(task.taskId)
- verifyWCTNotExecuted()
- }
-
- @Test
- fun moveToNextDisplay_moveFromFirstToSecondDisplay() {
- // Set up two display ids
- whenever(rootTaskDisplayAreaOrganizer.displayIds)
- .thenReturn(intArrayOf(DEFAULT_DISPLAY, SECOND_DISPLAY))
- // Create a mock for the target display area: second display
- val secondDisplayArea = DisplayAreaInfo(MockToken().token(), SECOND_DISPLAY, 0)
- whenever(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(SECOND_DISPLAY))
- .thenReturn(secondDisplayArea)
-
- val task = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
- controller.moveToNextDisplay(task.taskId)
- with(getLatestWct(type = TRANSIT_CHANGE)) {
- assertThat(hierarchyOps).hasSize(1)
- assertThat(hierarchyOps[0].container).isEqualTo(task.token.asBinder())
- assertThat(hierarchyOps[0].isReparent).isTrue()
- assertThat(hierarchyOps[0].newParent).isEqualTo(secondDisplayArea.token.asBinder())
- assertThat(hierarchyOps[0].toTop).isTrue()
- }
- }
-
- @Test
- fun moveToNextDisplay_moveFromSecondToFirstDisplay() {
- // Set up two display ids
- whenever(rootTaskDisplayAreaOrganizer.displayIds)
- .thenReturn(intArrayOf(DEFAULT_DISPLAY, SECOND_DISPLAY))
- // Create a mock for the target display area: default display
- val defaultDisplayArea = DisplayAreaInfo(MockToken().token(), DEFAULT_DISPLAY, 0)
- whenever(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY))
- .thenReturn(defaultDisplayArea)
-
- val task = setUpFreeformTask(displayId = SECOND_DISPLAY)
- controller.moveToNextDisplay(task.taskId)
-
- with(getLatestWct(type = TRANSIT_CHANGE)) {
- assertThat(hierarchyOps).hasSize(1)
- assertThat(hierarchyOps[0].container).isEqualTo(task.token.asBinder())
- assertThat(hierarchyOps[0].isReparent).isTrue()
- assertThat(hierarchyOps[0].newParent).isEqualTo(defaultDisplayArea.token.asBinder())
- assertThat(hierarchyOps[0].toTop).isTrue()
- }
- }
-
- @Test
- @EnableFlags(FLAG_ENABLE_PER_DISPLAY_DESKTOP_WALLPAPER_ACTIVITY)
- fun moveToNextDisplay_removeWallpaper() {
- // Set up two display ids
- whenever(rootTaskDisplayAreaOrganizer.displayIds)
- .thenReturn(intArrayOf(DEFAULT_DISPLAY, SECOND_DISPLAY))
- // Create a mock for the target display area: second display
- val secondDisplayArea = DisplayAreaInfo(MockToken().token(), SECOND_DISPLAY, 0)
- whenever(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(SECOND_DISPLAY))
- .thenReturn(secondDisplayArea)
- // Add a task and a wallpaper
- val task = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
- val wallpaperToken = MockToken().token()
- taskRepository.wallpaperActivityToken = wallpaperToken
-
- controller.moveToNextDisplay(task.taskId)
-
- with(getLatestWct(type = TRANSIT_CHANGE)) {
- val wallpaperChange = hierarchyOps.find { op -> op.container == wallpaperToken.asBinder() }
- assertThat(wallpaperChange).isNotNull()
- assertThat(wallpaperChange!!.type).isEqualTo(HIERARCHY_OP_TYPE_REMOVE_TASK)
- }
- }
-
- @Test
- fun getTaskWindowingMode() {
- val fullscreenTask = setUpFullscreenTask()
- val freeformTask = setUpFreeformTask()
-
- assertThat(controller.getTaskWindowingMode(fullscreenTask.taskId))
- .isEqualTo(WINDOWING_MODE_FULLSCREEN)
- assertThat(controller.getTaskWindowingMode(freeformTask.taskId))
- .isEqualTo(WINDOWING_MODE_FREEFORM)
- assertThat(controller.getTaskWindowingMode(999)).isEqualTo(WINDOWING_MODE_UNDEFINED)
- }
-
- @Test
- fun onDesktopWindowClose_noActiveTasks() {
- val task = setUpFreeformTask(active = false)
- val wct = WindowContainerTransaction()
- controller.onDesktopWindowClose(wct, displayId = DEFAULT_DISPLAY, task)
- // Doesn't modify transaction
- assertThat(wct.hierarchyOps).isEmpty()
- }
-
- @Test
- fun onDesktopWindowClose_singleActiveTask_noWallpaperActivityToken() {
- val task = setUpFreeformTask()
- val wct = WindowContainerTransaction()
- controller.onDesktopWindowClose(wct, displayId = DEFAULT_DISPLAY, task)
- // Doesn't modify transaction
- assertThat(wct.hierarchyOps).isEmpty()
- }
-
- @Test
- fun onDesktopWindowClose_singleActiveTask_hasWallpaperActivityToken() {
- val task = setUpFreeformTask()
- val wallpaperToken = MockToken().token()
- taskRepository.wallpaperActivityToken = wallpaperToken
-
- val wct = WindowContainerTransaction()
- controller.onDesktopWindowClose(wct, displayId = DEFAULT_DISPLAY, task)
- // Adds remove wallpaper operation
- wct.assertRemoveAt(index = 0, wallpaperToken)
- }
-
- @Test
- fun onDesktopWindowClose_singleActiveTask_isClosing() {
- val task = setUpFreeformTask()
- val wallpaperToken = MockToken().token()
- taskRepository.wallpaperActivityToken = wallpaperToken
- taskRepository.addClosingTask(DEFAULT_DISPLAY, task.taskId)
-
- val wct = WindowContainerTransaction()
- controller.onDesktopWindowClose(wct, displayId = DEFAULT_DISPLAY, task)
- // Doesn't modify transaction
- assertThat(wct.hierarchyOps).isEmpty()
- }
-
- @Test
- fun onDesktopWindowClose_singleActiveTask_isMinimized() {
- val task = setUpFreeformTask()
- val wallpaperToken = MockToken().token()
- taskRepository.wallpaperActivityToken = wallpaperToken
- taskRepository.minimizeTask(DEFAULT_DISPLAY, task.taskId)
-
- val wct = WindowContainerTransaction()
- controller.onDesktopWindowClose(wct, displayId = DEFAULT_DISPLAY, task)
- // Doesn't modify transaction
- assertThat(wct.hierarchyOps).isEmpty()
- }
-
- @Test
- fun onDesktopWindowClose_multipleActiveTasks() {
- val task1 = setUpFreeformTask()
- setUpFreeformTask()
- val wallpaperToken = MockToken().token()
- taskRepository.wallpaperActivityToken = wallpaperToken
-
- val wct = WindowContainerTransaction()
- controller.onDesktopWindowClose(wct, displayId = DEFAULT_DISPLAY, task1)
- // Doesn't modify transaction
- assertThat(wct.hierarchyOps).isEmpty()
- }
-
- @Test
- fun onDesktopWindowClose_multipleActiveTasks_isOnlyNonClosingTask() {
- val task1 = setUpFreeformTask()
- val task2 = setUpFreeformTask()
- val wallpaperToken = MockToken().token()
- taskRepository.wallpaperActivityToken = wallpaperToken
- taskRepository.addClosingTask(DEFAULT_DISPLAY, task2.taskId)
-
- val wct = WindowContainerTransaction()
- controller.onDesktopWindowClose(wct, displayId = DEFAULT_DISPLAY, task1)
- // Adds remove wallpaper operation
- wct.assertRemoveAt(index = 0, wallpaperToken)
- }
-
- @Test
- fun onDesktopWindowClose_multipleActiveTasks_hasMinimized() {
- val task1 = setUpFreeformTask()
- val task2 = setUpFreeformTask()
- val wallpaperToken = MockToken().token()
- taskRepository.wallpaperActivityToken = wallpaperToken
- taskRepository.minimizeTask(DEFAULT_DISPLAY, task2.taskId)
-
- val wct = WindowContainerTransaction()
- controller.onDesktopWindowClose(wct, displayId = DEFAULT_DISPLAY, task1)
- // Adds remove wallpaper operation
- wct.assertRemoveAt(index = 0, wallpaperToken)
- }
-
- @Test
- fun onDesktopWindowMinimize_noActiveTask_doesntRemoveWallpaper() {
- val task = setUpFreeformTask(active = false)
- val transition = Binder()
- whenever(freeformTaskTransitionStarter.startMinimizedModeTransition(any()))
- .thenReturn(transition)
- val wallpaperToken = MockToken().token()
- taskRepository.wallpaperActivityToken = wallpaperToken
-
- controller.minimizeTask(task)
-
- val captor = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
- verify(freeformTaskTransitionStarter).startMinimizedModeTransition(captor.capture())
- captor.value.hierarchyOps.none { hop ->
- hop.type == HIERARCHY_OP_TYPE_REMOVE_TASK && hop.container == wallpaperToken.asBinder()
- }
- }
-
- @Test
- fun onDesktopWindowMinimize_pipTask_autoEnterEnabled_startPipTransition() {
- val task = setUpPipTask(autoEnterEnabled = true)
- val handler = mock(TransitionHandler::class.java)
- whenever(freeformTaskTransitionStarter.startPipTransition(any()))
- .thenReturn(Binder())
- whenever(transitions.dispatchRequest(any(), any(), anyOrNull()))
- .thenReturn(android.util.Pair(handler, WindowContainerTransaction())
+ runningTasks.clear()
+ testScope.cancel()
+ }
+
+ @Test
+ fun instantiate_addInitCallback() {
+ verify(shellInit).addInitCallback(any(), any<DesktopTasksController>())
+ }
+
+ @Test
+ fun doesAnyTaskRequireTaskbarRounding_onlyFreeFormTaskIsRunning_returnFalse() {
+ setUpFreeformTask()
+
+ assertThat(controller.doesAnyTaskRequireTaskbarRounding(DEFAULT_DISPLAY)).isFalse()
+ }
+
+ @Test
+ fun doesAnyTaskRequireTaskbarRounding_toggleResizeOfFreeFormTask_returnTrue() {
+ val task1 = setUpFreeformTask()
+
+ val argumentCaptor = ArgumentCaptor.forClass(Boolean::class.java)
+ controller.toggleDesktopTaskSize(
+ task1,
+ ToggleTaskSizeInteraction(
+ ToggleTaskSizeInteraction.Direction.MAXIMIZE,
+ ToggleTaskSizeInteraction.Source.HEADER_BUTTON_TO_MAXIMIZE,
+ InputMethod.TOUCH,
+ ),
+ )
+
+ verify(taskbarDesktopTaskListener).onTaskbarCornerRoundingUpdate(argumentCaptor.capture())
+ verify(desktopModeEventLogger, times(1))
+ .logTaskResizingEnded(
+ ResizeTrigger.MAXIMIZE_BUTTON,
+ InputMethod.TOUCH,
+ task1,
+ STABLE_BOUNDS.width(),
+ STABLE_BOUNDS.height(),
+ displayController,
+ )
+ assertThat(argumentCaptor.value).isTrue()
+ }
+
+ @Test
+ fun doesAnyTaskRequireTaskbarRounding_fullScreenTaskIsRunning_returnTrue() {
+ val stableBounds = Rect().apply { displayLayout.getStableBounds(this) }
+ setUpFreeformTask(bounds = stableBounds, active = true)
+ assertThat(controller.doesAnyTaskRequireTaskbarRounding(DEFAULT_DISPLAY)).isTrue()
+ }
+
+ @Test
+ fun doesAnyTaskRequireTaskbarRounding_toggleResizeOfMaximizedTask_returnFalse() {
+ val stableBounds = Rect().apply { displayLayout.getStableBounds(this) }
+ val task1 = setUpFreeformTask(bounds = stableBounds, active = true)
+
+ val argumentCaptor = ArgumentCaptor.forClass(Boolean::class.java)
+ controller.toggleDesktopTaskSize(
+ task1,
+ ToggleTaskSizeInteraction(
+ ToggleTaskSizeInteraction.Direction.RESTORE,
+ ToggleTaskSizeInteraction.Source.HEADER_BUTTON_TO_RESTORE,
+ InputMethod.TOUCH,
+ ),
+ )
+
+ verify(taskbarDesktopTaskListener).onTaskbarCornerRoundingUpdate(argumentCaptor.capture())
+ verify(desktopModeEventLogger, times(1))
+ .logTaskResizingEnded(
+ eq(ResizeTrigger.MAXIMIZE_BUTTON),
+ eq(InputMethod.TOUCH),
+ eq(task1),
+ anyOrNull(),
+ anyOrNull(),
+ eq(displayController),
+ anyOrNull(),
+ )
+ assertThat(argumentCaptor.value).isFalse()
+ }
+
+ @Test
+ fun doesAnyTaskRequireTaskbarRounding_splitScreenTaskIsRunning_returnTrue() {
+ val stableBounds = Rect().apply { displayLayout.getStableBounds(this) }
+ setUpFreeformTask(
+ bounds = Rect(stableBounds.left, stableBounds.top, 500, stableBounds.bottom)
+ )
+
+ assertThat(controller.doesAnyTaskRequireTaskbarRounding(DEFAULT_DISPLAY)).isTrue()
+ }
+
+ @Test
+ fun instantiate_cannotEnterDesktopMode_doNotAddInitCallback() {
+ whenever(DesktopModeStatus.canEnterDesktopMode(context)).thenReturn(false)
+ clearInvocations(shellInit)
+
+ createController()
+
+ verify(shellInit, never()).addInitCallback(any(), any<DesktopTasksController>())
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ fun showDesktopApps_allAppsInvisible_bringsToFront_desktopWallpaperDisabled() {
+ val homeTask = setUpHomeTask()
+ val task1 = setUpFreeformTask()
+ val task2 = setUpFreeformTask()
+ markTaskHidden(task1)
+ markTaskHidden(task2)
+
+ controller.showDesktopApps(DEFAULT_DISPLAY, RemoteTransition(TestRemoteTransition()))
+
+ val wct =
+ getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java)
+ assertThat(wct.hierarchyOps).hasSize(3)
+ // Expect order to be from bottom: home, task1, task2
+ wct.assertReorderAt(index = 0, homeTask)
+ wct.assertReorderAt(index = 1, task1)
+ wct.assertReorderAt(index = 2, task2)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ fun showDesktopApps_allAppsInvisible_bringsToFront_desktopWallpaperEnabled() {
+ val task1 = setUpFreeformTask()
+ val task2 = setUpFreeformTask()
+ markTaskHidden(task1)
+ markTaskHidden(task2)
+
+ controller.showDesktopApps(DEFAULT_DISPLAY, RemoteTransition(TestRemoteTransition()))
+
+ val wct =
+ getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java)
+ assertThat(wct.hierarchyOps).hasSize(3)
+ // Expect order to be from bottom: wallpaper intent, task1, task2
+ wct.assertPendingIntentAt(index = 0, desktopWallpaperIntent)
+ wct.assertReorderAt(index = 1, task1)
+ wct.assertReorderAt(index = 2, task2)
+ }
+
+ @Test
+ fun isDesktopModeShowing_noTasks_returnsFalse() {
+ assertThat(controller.isDesktopModeShowing(displayId = 0)).isFalse()
+ }
+
+ @Test
+ fun isDesktopModeShowing_noTasksVisible_returnsFalse() {
+ val task1 = setUpFreeformTask()
+ val task2 = setUpFreeformTask()
+ markTaskHidden(task1)
+ markTaskHidden(task2)
+
+ assertThat(controller.isDesktopModeShowing(displayId = 0)).isFalse()
+ }
+
+ @Test
+ fun isDesktopModeShowing_tasksActiveAndVisible_returnsTrue() {
+ val task1 = setUpFreeformTask()
+ val task2 = setUpFreeformTask()
+ markTaskVisible(task1)
+ markTaskHidden(task2)
+
+ assertThat(controller.isDesktopModeShowing(displayId = 0)).isTrue()
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ fun showDesktopApps_onSecondaryDisplay_desktopWallpaperEnabled_shouldNotShowWallpaper() {
+ val homeTask = setUpHomeTask(SECOND_DISPLAY)
+ val task1 = setUpFreeformTask(SECOND_DISPLAY)
+ val task2 = setUpFreeformTask(SECOND_DISPLAY)
+ markTaskHidden(task1)
+ markTaskHidden(task2)
+
+ controller.showDesktopApps(SECOND_DISPLAY, RemoteTransition(TestRemoteTransition()))
+
+ val wct =
+ getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java)
+ assertThat(wct.hierarchyOps).hasSize(3)
+ // Expect order to be from bottom: home, task1, task2 (no wallpaper intent)
+ wct.assertReorderAt(index = 0, homeTask)
+ wct.assertReorderAt(index = 1, task1)
+ wct.assertReorderAt(index = 2, task2)
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ fun showDesktopApps_appsAlreadyVisible_bringsToFront_desktopWallpaperDisabled() {
+ val homeTask = setUpHomeTask()
+ val task1 = setUpFreeformTask()
+ val task2 = setUpFreeformTask()
+ markTaskVisible(task1)
+ markTaskVisible(task2)
+
+ controller.showDesktopApps(DEFAULT_DISPLAY, RemoteTransition(TestRemoteTransition()))
+
+ val wct =
+ getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java)
+ assertThat(wct.hierarchyOps).hasSize(3)
+ // Expect order to be from bottom: home, task1, task2
+ wct.assertReorderAt(index = 0, homeTask)
+ wct.assertReorderAt(index = 1, task1)
+ wct.assertReorderAt(index = 2, task2)
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ fun showDesktopApps_onSecondaryDisplay_desktopWallpaperDisabled_shouldNotMoveLauncher() {
+ val homeTask = setUpHomeTask(SECOND_DISPLAY)
+ val task1 = setUpFreeformTask(SECOND_DISPLAY)
+ val task2 = setUpFreeformTask(SECOND_DISPLAY)
+ markTaskHidden(task1)
+ markTaskHidden(task2)
+
+ controller.showDesktopApps(SECOND_DISPLAY, RemoteTransition(TestRemoteTransition()))
+
+ val wct =
+ getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java)
+ assertThat(wct.hierarchyOps).hasSize(3)
+ // Expect order to be from bottom: home, task1, task2
+ wct.assertReorderAt(index = 0, homeTask)
+ wct.assertReorderAt(index = 1, task1)
+ wct.assertReorderAt(index = 2, task2)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ fun showDesktopApps_appsAlreadyVisible_bringsToFront_desktopWallpaperEnabled() {
+ val task1 = setUpFreeformTask()
+ val task2 = setUpFreeformTask()
+ markTaskVisible(task1)
+ markTaskVisible(task2)
+
+ controller.showDesktopApps(DEFAULT_DISPLAY, RemoteTransition(TestRemoteTransition()))
+
+ val wct =
+ getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java)
+ assertThat(wct.hierarchyOps).hasSize(3)
+ // Expect order to be from bottom: wallpaper intent, task1, task2
+ wct.assertPendingIntentAt(index = 0, desktopWallpaperIntent)
+ wct.assertReorderAt(index = 1, task1)
+ wct.assertReorderAt(index = 2, task2)
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ fun showDesktopApps_someAppsInvisible_reordersAll_desktopWallpaperDisabled() {
+ val homeTask = setUpHomeTask()
+ val task1 = setUpFreeformTask()
+ val task2 = setUpFreeformTask()
+ markTaskHidden(task1)
+ markTaskVisible(task2)
+
+ controller.showDesktopApps(DEFAULT_DISPLAY, RemoteTransition(TestRemoteTransition()))
+
+ val wct =
+ getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java)
+ assertThat(wct.hierarchyOps).hasSize(3)
+ // Expect order to be from bottom: home, task1, task2
+ wct.assertReorderAt(index = 0, homeTask)
+ wct.assertReorderAt(index = 1, task1)
+ wct.assertReorderAt(index = 2, task2)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ fun showDesktopApps_someAppsInvisible_reordersAll_desktopWallpaperEnabled() {
+ val task1 = setUpFreeformTask()
+ val task2 = setUpFreeformTask()
+ markTaskHidden(task1)
+ markTaskVisible(task2)
+
+ controller.showDesktopApps(DEFAULT_DISPLAY, RemoteTransition(TestRemoteTransition()))
+
+ val wct =
+ getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java)
+ assertThat(wct.hierarchyOps).hasSize(3)
+ // Expect order to be from bottom: wallpaper intent, task1, task2
+ wct.assertPendingIntentAt(index = 0, desktopWallpaperIntent)
+ wct.assertReorderAt(index = 1, task1)
+ wct.assertReorderAt(index = 2, task2)
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ fun showDesktopApps_noActiveTasks_reorderHomeToTop_desktopWallpaperDisabled() {
+ val homeTask = setUpHomeTask()
+
+ controller.showDesktopApps(DEFAULT_DISPLAY, RemoteTransition(TestRemoteTransition()))
+
+ val wct =
+ getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java)
+ assertThat(wct.hierarchyOps).hasSize(1)
+ wct.assertReorderAt(index = 0, homeTask)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ fun showDesktopApps_noActiveTasks_addDesktopWallpaper_desktopWallpaperEnabled() {
+ controller.showDesktopApps(DEFAULT_DISPLAY, RemoteTransition(TestRemoteTransition()))
+
+ val wct =
+ getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java)
+ wct.assertPendingIntentAt(index = 0, desktopWallpaperIntent)
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ fun showDesktopApps_twoDisplays_bringsToFrontOnlyOneDisplay_desktopWallpaperDisabled() {
+ val homeTaskDefaultDisplay = setUpHomeTask(DEFAULT_DISPLAY)
+ val taskDefaultDisplay = setUpFreeformTask(DEFAULT_DISPLAY)
+ setUpHomeTask(SECOND_DISPLAY)
+ val taskSecondDisplay = setUpFreeformTask(SECOND_DISPLAY)
+ markTaskHidden(taskDefaultDisplay)
+ markTaskHidden(taskSecondDisplay)
+
+ controller.showDesktopApps(DEFAULT_DISPLAY, RemoteTransition(TestRemoteTransition()))
+
+ val wct =
+ getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java)
+ assertThat(wct.hierarchyOps).hasSize(2)
+ // Expect order to be from bottom: home, task
+ wct.assertReorderAt(index = 0, homeTaskDefaultDisplay)
+ wct.assertReorderAt(index = 1, taskDefaultDisplay)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ fun showDesktopApps_twoDisplays_bringsToFrontOnlyOneDisplay_desktopWallpaperEnabled() {
+ val homeTaskDefaultDisplay = setUpHomeTask(DEFAULT_DISPLAY)
+ val taskDefaultDisplay = setUpFreeformTask(DEFAULT_DISPLAY)
+ setUpHomeTask(SECOND_DISPLAY)
+ val taskSecondDisplay = setUpFreeformTask(SECOND_DISPLAY)
+ markTaskHidden(taskDefaultDisplay)
+ markTaskHidden(taskSecondDisplay)
+
+ controller.showDesktopApps(DEFAULT_DISPLAY, RemoteTransition(TestRemoteTransition()))
+
+ val wct =
+ getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java)
+ assertThat(wct.hierarchyOps).hasSize(3)
+ // Move home to front
+ wct.assertReorderAt(index = 0, homeTaskDefaultDisplay)
+ // Add desktop wallpaper activity
+ wct.assertPendingIntentAt(index = 1, desktopWallpaperIntent)
+ // Move freeform task to front
+ wct.assertReorderAt(index = 2, taskDefaultDisplay)
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ fun showDesktopApps_desktopWallpaperDisabled_dontReorderMinimizedTask() {
+ val homeTask = setUpHomeTask()
+ val freeformTask = setUpFreeformTask()
+ val minimizedTask = setUpFreeformTask()
+
+ markTaskHidden(freeformTask)
+ markTaskHidden(minimizedTask)
+ taskRepository.minimizeTask(DEFAULT_DISPLAY, minimizedTask.taskId)
+ controller.showDesktopApps(DEFAULT_DISPLAY, RemoteTransition(TestRemoteTransition()))
+
+ val wct =
+ getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java)
+ assertThat(wct.hierarchyOps).hasSize(2)
+ // Reorder home and freeform task to top, don't reorder the minimized task
+ wct.assertReorderAt(index = 0, homeTask, toTop = true)
+ wct.assertReorderAt(index = 1, freeformTask, toTop = true)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ fun showDesktopApps_desktopWallpaperEnabled_dontReorderMinimizedTask() {
+ val homeTask = setUpHomeTask()
+ val freeformTask = setUpFreeformTask()
+ val minimizedTask = setUpFreeformTask()
+
+ markTaskHidden(freeformTask)
+ markTaskHidden(minimizedTask)
+ taskRepository.minimizeTask(DEFAULT_DISPLAY, minimizedTask.taskId)
+ controller.showDesktopApps(DEFAULT_DISPLAY, RemoteTransition(TestRemoteTransition()))
+
+ val wct =
+ getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java)
+ assertThat(wct.hierarchyOps).hasSize(3)
+ // Move home to front
+ wct.assertReorderAt(index = 0, homeTask, toTop = true)
+ // Add desktop wallpaper activity
+ wct.assertPendingIntentAt(index = 1, desktopWallpaperIntent)
+ // Reorder freeform task to top, don't reorder the minimized task
+ wct.assertReorderAt(index = 2, freeformTask, toTop = true)
+ }
+
+ @Test
+ fun visibleTaskCount_noTasks_returnsZero() {
+ assertThat(controller.visibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(0)
+ }
+
+ @Test
+ fun visibleTaskCount_twoTasks_bothVisible_returnsTwo() {
+ setUpHomeTask()
+ setUpFreeformTask().also(::markTaskVisible)
+ setUpFreeformTask().also(::markTaskVisible)
+ assertThat(controller.visibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(2)
+ }
+
+ @Test
+ fun visibleTaskCount_twoTasks_oneVisible_returnsOne() {
+ setUpHomeTask()
+ setUpFreeformTask().also(::markTaskVisible)
+ setUpFreeformTask().also(::markTaskHidden)
+ assertThat(controller.visibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(1)
+ }
+
+ @Test
+ fun visibleTaskCount_twoTasksVisibleOnDifferentDisplays_returnsOne() {
+ setUpHomeTask()
+ setUpFreeformTask(DEFAULT_DISPLAY).also(::markTaskVisible)
+ setUpFreeformTask(SECOND_DISPLAY).also(::markTaskVisible)
+ assertThat(controller.visibleTaskCount(SECOND_DISPLAY)).isEqualTo(1)
+ }
+
+ @Test
+ fun addMoveToDesktopChanges_gravityLeft_noBoundsApplied() {
+ setUpLandscapeDisplay()
+ val task = setUpFullscreenTask(gravity = Gravity.LEFT)
+ val wct = WindowContainerTransaction()
+ controller.addMoveToDesktopChanges(wct, task)
+
+ val finalBounds = findBoundsChange(wct, task)
+ assertThat(finalBounds).isEqualTo(Rect())
+ }
+
+ @Test
+ fun addMoveToDesktopChanges_gravityRight_noBoundsApplied() {
+ setUpLandscapeDisplay()
+ val task = setUpFullscreenTask(gravity = Gravity.RIGHT)
+ val wct = WindowContainerTransaction()
+ controller.addMoveToDesktopChanges(wct, task)
+
+ val finalBounds = findBoundsChange(wct, task)
+ assertThat(finalBounds).isEqualTo(Rect())
+ }
+
+ @Test
+ fun addMoveToDesktopChanges_gravityTop_noBoundsApplied() {
+ setUpLandscapeDisplay()
+ val task = setUpFullscreenTask(gravity = Gravity.TOP)
+ val wct = WindowContainerTransaction()
+ controller.addMoveToDesktopChanges(wct, task)
+
+ val finalBounds = findBoundsChange(wct, task)
+ assertThat(finalBounds).isEqualTo(Rect())
+ }
+
+ @Test
+ fun addMoveToDesktopChanges_gravityBottom_noBoundsApplied() {
+ setUpLandscapeDisplay()
+ val task = setUpFullscreenTask(gravity = Gravity.BOTTOM)
+ val wct = WindowContainerTransaction()
+ controller.addMoveToDesktopChanges(wct, task)
+
+ val finalBounds = findBoundsChange(wct, task)
+ assertThat(finalBounds).isEqualTo(Rect())
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_CASCADING_WINDOWS)
+ fun handleRequest_newFreeformTaskLaunch_cascadeApplied() {
+ setUpLandscapeDisplay()
+ val stableBounds = Rect()
+ displayLayout.getStableBoundsForDesktopMode(stableBounds)
+
+ setUpFreeformTask(bounds = DEFAULT_LANDSCAPE_BOUNDS)
+ val freeformTask = setUpFreeformTask(bounds = DEFAULT_LANDSCAPE_BOUNDS, active = false)
+
+ val wct = controller.handleRequest(Binder(), createTransition(freeformTask))
+
+ assertNotNull(wct, "should handle request")
+ val finalBounds = findBoundsChange(wct, freeformTask)
+ assertThat(stableBounds.getDesktopTaskPosition(finalBounds!!))
+ .isEqualTo(DesktopTaskPosition.BottomRight)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_CASCADING_WINDOWS)
+ fun handleRequest_freeformTaskAlreadyExistsInDesktopMode_cascadeNotApplied() {
+ setUpLandscapeDisplay()
+ val stableBounds = Rect()
+ displayLayout.getStableBoundsForDesktopMode(stableBounds)
+
+ setUpFreeformTask(bounds = DEFAULT_LANDSCAPE_BOUNDS)
+ val freeformTask = setUpFreeformTask(bounds = DEFAULT_LANDSCAPE_BOUNDS)
+
+ val wct = controller.handleRequest(Binder(), createTransition(freeformTask))
+
+ assertNull(wct, "should not handle request")
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_CASCADING_WINDOWS)
+ fun addMoveToDesktopChanges_positionBottomRight() {
+ setUpLandscapeDisplay()
+ val stableBounds = Rect()
+ displayLayout.getStableBoundsForDesktopMode(stableBounds)
+
+ setUpFreeformTask(bounds = DEFAULT_LANDSCAPE_BOUNDS)
+
+ val task = setUpFullscreenTask()
+ val wct = WindowContainerTransaction()
+ controller.addMoveToDesktopChanges(wct, task)
+
+ val finalBounds = findBoundsChange(wct, task)
+ assertThat(stableBounds.getDesktopTaskPosition(finalBounds!!))
+ .isEqualTo(DesktopTaskPosition.BottomRight)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_CASCADING_WINDOWS)
+ fun addMoveToDesktopChanges_positionTopLeft() {
+ setUpLandscapeDisplay()
+ val stableBounds = Rect()
+ displayLayout.getStableBoundsForDesktopMode(stableBounds)
+
+ addFreeformTaskAtPosition(DesktopTaskPosition.BottomRight, stableBounds)
+
+ val task = setUpFullscreenTask()
+ val wct = WindowContainerTransaction()
+ controller.addMoveToDesktopChanges(wct, task)
+
+ val finalBounds = findBoundsChange(wct, task)
+ assertThat(stableBounds.getDesktopTaskPosition(finalBounds!!))
+ .isEqualTo(DesktopTaskPosition.TopLeft)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_CASCADING_WINDOWS)
+ fun addMoveToDesktopChanges_positionBottomLeft() {
+ setUpLandscapeDisplay()
+ val stableBounds = Rect()
+ displayLayout.getStableBoundsForDesktopMode(stableBounds)
+
+ addFreeformTaskAtPosition(DesktopTaskPosition.TopLeft, stableBounds)
+
+ val task = setUpFullscreenTask()
+ val wct = WindowContainerTransaction()
+ controller.addMoveToDesktopChanges(wct, task)
+
+ val finalBounds = findBoundsChange(wct, task)
+ assertThat(stableBounds.getDesktopTaskPosition(finalBounds!!))
+ .isEqualTo(DesktopTaskPosition.BottomLeft)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_CASCADING_WINDOWS)
+ fun addMoveToDesktopChanges_positionTopRight() {
+ setUpLandscapeDisplay()
+ val stableBounds = Rect()
+ displayLayout.getStableBoundsForDesktopMode(stableBounds)
+
+ addFreeformTaskAtPosition(DesktopTaskPosition.BottomLeft, stableBounds)
+
+ val task = setUpFullscreenTask()
+ val wct = WindowContainerTransaction()
+ controller.addMoveToDesktopChanges(wct, task)
+
+ val finalBounds = findBoundsChange(wct, task)
+ assertThat(stableBounds.getDesktopTaskPosition(finalBounds!!))
+ .isEqualTo(DesktopTaskPosition.TopRight)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_CASCADING_WINDOWS)
+ fun addMoveToDesktopChanges_positionResetsToCenter() {
+ setUpLandscapeDisplay()
+ val stableBounds = Rect()
+ displayLayout.getStableBoundsForDesktopMode(stableBounds)
+
+ addFreeformTaskAtPosition(DesktopTaskPosition.TopRight, stableBounds)
+
+ val task = setUpFullscreenTask()
+ val wct = WindowContainerTransaction()
+ controller.addMoveToDesktopChanges(wct, task)
+
+ val finalBounds = findBoundsChange(wct, task)
+ assertThat(stableBounds.getDesktopTaskPosition(finalBounds!!))
+ .isEqualTo(DesktopTaskPosition.Center)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_CASCADING_WINDOWS)
+ fun addMoveToDesktopChanges_lastWindowSnapLeft_positionResetsToCenter() {
+ setUpLandscapeDisplay()
+ val stableBounds = Rect()
+ displayLayout.getStableBoundsForDesktopMode(stableBounds)
+
+ // Add freeform task with half display size snap bounds at left side.
+ setUpFreeformTask(
+ bounds = Rect(stableBounds.left, stableBounds.top, 500, stableBounds.bottom)
+ )
+
+ val task = setUpFullscreenTask()
+ val wct = WindowContainerTransaction()
+ controller.addMoveToDesktopChanges(wct, task)
+
+ val finalBounds = findBoundsChange(wct, task)
+ assertThat(stableBounds.getDesktopTaskPosition(finalBounds!!))
+ .isEqualTo(DesktopTaskPosition.Center)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_CASCADING_WINDOWS)
+ fun addMoveToDesktopChanges_lastWindowSnapRight_positionResetsToCenter() {
+ setUpLandscapeDisplay()
+ val stableBounds = Rect()
+ displayLayout.getStableBoundsForDesktopMode(stableBounds)
+
+ // Add freeform task with half display size snap bounds at right side.
+ setUpFreeformTask(
+ bounds =
+ Rect(
+ stableBounds.right - 500,
+ stableBounds.top,
+ stableBounds.right,
+ stableBounds.bottom,
+ )
+ )
+
+ val task = setUpFullscreenTask()
+ val wct = WindowContainerTransaction()
+ controller.addMoveToDesktopChanges(wct, task)
+
+ val finalBounds = findBoundsChange(wct, task)
+ assertThat(stableBounds.getDesktopTaskPosition(finalBounds!!))
+ .isEqualTo(DesktopTaskPosition.Center)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_CASCADING_WINDOWS)
+ fun addMoveToDesktopChanges_lastWindowMaximised_positionResetsToCenter() {
+ setUpLandscapeDisplay()
+ val stableBounds = Rect()
+ displayLayout.getStableBoundsForDesktopMode(stableBounds)
+
+ // Add maximised freeform task.
+ setUpFreeformTask(bounds = Rect(stableBounds))
+
+ val task = setUpFullscreenTask()
+ val wct = WindowContainerTransaction()
+ controller.addMoveToDesktopChanges(wct, task)
+
+ val finalBounds = findBoundsChange(wct, task)
+ assertThat(stableBounds.getDesktopTaskPosition(finalBounds!!))
+ .isEqualTo(DesktopTaskPosition.Center)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_CASCADING_WINDOWS)
+ fun addMoveToDesktopChanges_defaultToCenterIfFree() {
+ setUpLandscapeDisplay()
+ val stableBounds = Rect()
+ displayLayout.getStableBoundsForDesktopMode(stableBounds)
+
+ val minTouchTarget =
+ context.resources.getDimensionPixelSize(
+ R.dimen.freeform_required_visible_empty_space_in_header
+ )
+ addFreeformTaskAtPosition(
+ DesktopTaskPosition.Center,
+ stableBounds,
+ Rect(0, 0, 1600, 1200),
+ Point(0, minTouchTarget + 1),
+ )
+
+ val task = setUpFullscreenTask()
+ val wct = WindowContainerTransaction()
+ controller.addMoveToDesktopChanges(wct, task)
+
+ val finalBounds = findBoundsChange(wct, task)
+ assertThat(stableBounds.getDesktopTaskPosition(finalBounds!!))
+ .isEqualTo(DesktopTaskPosition.Center)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+ fun addMoveToDesktopChanges_landscapeDevice_userFullscreenOverride_defaultPortraitBounds() {
+ setUpLandscapeDisplay()
+ val task = setUpFullscreenTask(enableUserFullscreenOverride = true)
+ val wct = WindowContainerTransaction()
+ controller.addMoveToDesktopChanges(wct, task)
+
+ assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_LANDSCAPE_BOUNDS)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+ fun addMoveToDesktopChanges_landscapeDevice_systemFullscreenOverride_defaultPortraitBounds() {
+ setUpLandscapeDisplay()
+ val task = setUpFullscreenTask(enableSystemFullscreenOverride = true)
+ val wct = WindowContainerTransaction()
+ controller.addMoveToDesktopChanges(wct, task)
+
+ assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_LANDSCAPE_BOUNDS)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+ fun addMoveToDesktopChanges_landscapeDevice_portraitResizableApp_aspectRatioOverridden() {
+ setUpLandscapeDisplay()
+ val task =
+ setUpFullscreenTask(
+ screenOrientation = SCREEN_ORIENTATION_PORTRAIT,
+ shouldLetterbox = true,
+ aspectRatioOverrideApplied = true,
+ )
+ val wct = WindowContainerTransaction()
+ controller.addMoveToDesktopChanges(wct, task)
+
+ assertThat(findBoundsChange(wct, task)).isEqualTo(UNRESIZABLE_PORTRAIT_BOUNDS)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+ fun addMoveToDesktopChanges_portraitDevice_userFullscreenOverride_defaultPortraitBounds() {
+ setUpPortraitDisplay()
+ val task = setUpFullscreenTask(enableUserFullscreenOverride = true)
+ val wct = WindowContainerTransaction()
+ controller.addMoveToDesktopChanges(wct, task)
+
+ assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_PORTRAIT_BOUNDS)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+ fun addMoveToDesktopChanges_portraitDevice_systemFullscreenOverride_defaultPortraitBounds() {
+ setUpPortraitDisplay()
+ val task = setUpFullscreenTask(enableSystemFullscreenOverride = true)
+ val wct = WindowContainerTransaction()
+ controller.addMoveToDesktopChanges(wct, task)
+
+ assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_PORTRAIT_BOUNDS)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+ fun addMoveToDesktopChanges_portraitDevice_landscapeResizableApp_aspectRatioOverridden() {
+ setUpPortraitDisplay()
+ val task =
+ setUpFullscreenTask(
+ screenOrientation = SCREEN_ORIENTATION_LANDSCAPE,
+ deviceOrientation = ORIENTATION_PORTRAIT,
+ shouldLetterbox = true,
+ aspectRatioOverrideApplied = true,
+ )
+ val wct = WindowContainerTransaction()
+ controller.addMoveToDesktopChanges(wct, task)
+
+ assertThat(findBoundsChange(wct, task)).isEqualTo(UNRESIZABLE_LANDSCAPE_BOUNDS)
+ }
+
+ @Test
+ fun moveToDesktop_tdaFullscreen_windowingModeSetToFreeform() {
+ val task = setUpFullscreenTask()
+ val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!!
+ tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FULLSCREEN
+ controller.moveRunningTaskToDesktop(task, transitionSource = UNKNOWN)
+ val wct = getLatestEnterDesktopWct()
+ assertThat(wct.changes[task.token.asBinder()]?.windowingMode)
+ .isEqualTo(WINDOWING_MODE_FREEFORM)
+ verify(desktopModeEnterExitTransitionListener)
+ .onEnterDesktopModeTransitionStarted(FREEFORM_ANIMATION_DURATION)
+ }
+
+ @Test
+ fun moveRunningTaskToDesktop_tdaFreeform_windowingModeSetToUndefined() {
+ val task = setUpFullscreenTask()
+ val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!!
+ tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FREEFORM
+ controller.moveRunningTaskToDesktop(task, transitionSource = UNKNOWN)
+ val wct = getLatestEnterDesktopWct()
+ assertThat(wct.changes[task.token.asBinder()]?.windowingMode)
+ .isEqualTo(WINDOWING_MODE_UNDEFINED)
+ verify(desktopModeEnterExitTransitionListener)
+ .onEnterDesktopModeTransitionStarted(FREEFORM_ANIMATION_DURATION)
+ }
+
+ @Test
+ fun moveTaskToDesktop_nonExistentTask_doesNothing() {
+ controller.moveTaskToDesktop(999, transitionSource = UNKNOWN)
+ verifyEnterDesktopWCTNotExecuted()
+ verify(desktopModeEnterExitTransitionListener, times(0))
+ .onEnterDesktopModeTransitionStarted(anyInt())
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ fun moveTaskToDesktop_desktopWallpaperDisabled_nonRunningTask_launchesInFreeform() {
+ val task = createTaskInfo(1)
+ whenever(shellTaskOrganizer.getRunningTaskInfo(anyInt())).thenReturn(null)
+ whenever(recentTasksController.findTaskInBackground(anyInt())).thenReturn(task)
+
+ controller.moveTaskToDesktop(task.taskId, transitionSource = UNKNOWN)
+
+ with(getLatestEnterDesktopWct()) {
+ assertLaunchTaskAt(0, task.taskId, WINDOWING_MODE_FREEFORM)
+ }
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ fun moveTaskToDesktop_desktopWallpaperEnabled_nonRunningTask_launchesInFreeform() {
+ val task = createTaskInfo(1)
+ whenever(shellTaskOrganizer.getRunningTaskInfo(anyInt())).thenReturn(null)
+ whenever(recentTasksController.findTaskInBackground(anyInt())).thenReturn(task)
+
+ controller.moveTaskToDesktop(task.taskId, transitionSource = UNKNOWN)
+
+ with(getLatestEnterDesktopWct()) {
+ // Add desktop wallpaper activity
+ assertPendingIntentAt(index = 0, desktopWallpaperIntent)
+ // Launch task
+ assertLaunchTaskAt(index = 1, task.taskId, WINDOWING_MODE_FREEFORM)
+ }
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
+ fun moveRunningTaskToDesktop_topActivityTranslucentWithoutDisplay_taskIsMovedToDesktop() {
+ val task =
+ setUpFullscreenTask().apply {
+ isActivityStackTransparent = true
+ isTopActivityNoDisplay = true
+ numActivities = 1
+ }
+
+ controller.moveRunningTaskToDesktop(task, transitionSource = UNKNOWN)
+ val wct = getLatestEnterDesktopWct()
+ assertThat(wct.changes[task.token.asBinder()]?.windowingMode)
+ .isEqualTo(WINDOWING_MODE_FREEFORM)
+ verify(desktopModeEnterExitTransitionListener)
+ .onEnterDesktopModeTransitionStarted(FREEFORM_ANIMATION_DURATION)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
+ fun moveRunningTaskToDesktop_topActivityTranslucentWithDisplay_doesNothing() {
+ val task =
+ setUpFullscreenTask().apply {
+ isActivityStackTransparent = true
+ isTopActivityNoDisplay = false
+ numActivities = 1
+ }
+
+ controller.moveRunningTaskToDesktop(task, transitionSource = UNKNOWN)
+ verifyEnterDesktopWCTNotExecuted()
+ verify(desktopModeEnterExitTransitionListener, times(0))
+ .onEnterDesktopModeTransitionStarted(FREEFORM_ANIMATION_DURATION)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
+ fun moveRunningTaskToDesktop_systemUIActivityWithDisplay_doesNothing() {
+ // Set task as systemUI package
+ val systemUIPackageName =
+ context.resources.getString(com.android.internal.R.string.config_systemUi)
+ val baseComponent = ComponentName(systemUIPackageName, /* class */ "")
+ val task =
+ setUpFullscreenTask().apply {
+ baseActivity = baseComponent
+ isTopActivityNoDisplay = false
+ }
+
+ controller.moveRunningTaskToDesktop(task, transitionSource = UNKNOWN)
+ verifyEnterDesktopWCTNotExecuted()
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
+ fun moveRunningTaskToDesktop_systemUIActivityWithoutDisplay_doesNothing() {
+ // Set task as systemUI package
+ val systemUIPackageName =
+ context.resources.getString(com.android.internal.R.string.config_systemUi)
+ val baseComponent = ComponentName(systemUIPackageName, /* class */ "")
+ val task =
+ setUpFullscreenTask().apply {
+ baseActivity = baseComponent
+ isTopActivityNoDisplay = true
+ }
+
+ controller.moveRunningTaskToDesktop(task, transitionSource = UNKNOWN)
+
+ val wct = getLatestEnterDesktopWct()
+ assertThat(wct.changes[task.token.asBinder()]?.windowingMode)
+ .isEqualTo(WINDOWING_MODE_FREEFORM)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ fun moveBackgroundTaskToDesktop_remoteTransition_usesOneShotHandler() {
+ val transitionHandlerArgCaptor = ArgumentCaptor.forClass(TransitionHandler::class.java)
+ whenever(transitions.startTransition(anyInt(), any(), transitionHandlerArgCaptor.capture()))
+ .thenReturn(Binder())
+
+ val task = createTaskInfo(1)
+ whenever(shellTaskOrganizer.getRunningTaskInfo(anyInt())).thenReturn(null)
+ whenever(recentTasksController.findTaskInBackground(anyInt())).thenReturn(task)
+ controller.moveTaskToDesktop(
+ taskId = task.taskId,
+ transitionSource = UNKNOWN,
+ remoteTransition = RemoteTransition(spy(TestRemoteTransition())),
+ )
+
+ verify(desktopModeEnterExitTransitionListener)
+ .onEnterDesktopModeTransitionStarted(FREEFORM_ANIMATION_DURATION)
+ assertIs<OneShotRemoteHandler>(transitionHandlerArgCaptor.value)
+ }
+
+ @Test
+ fun moveRunningTaskToDesktop_remoteTransition_usesOneShotHandler() {
+ val transitionHandlerArgCaptor = ArgumentCaptor.forClass(TransitionHandler::class.java)
+ whenever(transitions.startTransition(anyInt(), any(), transitionHandlerArgCaptor.capture()))
+ .thenReturn(Binder())
+
+ controller.moveRunningTaskToDesktop(
+ task = setUpFullscreenTask(),
+ transitionSource = UNKNOWN,
+ remoteTransition = RemoteTransition(spy(TestRemoteTransition())),
+ )
+
+ verify(desktopModeEnterExitTransitionListener)
+ .onEnterDesktopModeTransitionStarted(FREEFORM_ANIMATION_DURATION)
+ assertIs<OneShotRemoteHandler>(transitionHandlerArgCaptor.value)
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ fun moveRunningTaskToDesktop_otherFreeformTasksBroughtToFront_desktopWallpaperDisabled() {
+ val homeTask = setUpHomeTask()
+ val freeformTask = setUpFreeformTask()
+ val fullscreenTask = setUpFullscreenTask()
+ markTaskHidden(freeformTask)
+
+ controller.moveRunningTaskToDesktop(fullscreenTask, transitionSource = UNKNOWN)
+
+ with(getLatestEnterDesktopWct()) {
+ // Operations should include home task, freeform task
+ assertThat(hierarchyOps).hasSize(3)
+ assertReorderSequence(homeTask, freeformTask, fullscreenTask)
+ assertThat(changes[fullscreenTask.token.asBinder()]?.windowingMode)
+ .isEqualTo(WINDOWING_MODE_FREEFORM)
+ }
+ verify(desktopModeEnterExitTransitionListener)
+ .onEnterDesktopModeTransitionStarted(FREEFORM_ANIMATION_DURATION)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ fun moveRunningTaskToDesktop_otherFreeformTasksBroughtToFront_desktopWallpaperEnabled() {
+ val freeformTask = setUpFreeformTask()
+ val fullscreenTask = setUpFullscreenTask()
+ markTaskHidden(freeformTask)
+
+ controller.moveRunningTaskToDesktop(fullscreenTask, transitionSource = UNKNOWN)
+
+ with(getLatestEnterDesktopWct()) {
+ // Operations should include wallpaper intent, freeform task, fullscreen task
+ assertThat(hierarchyOps).hasSize(3)
+ assertPendingIntentAt(index = 0, desktopWallpaperIntent)
+ assertReorderAt(index = 1, freeformTask)
+ assertReorderAt(index = 2, fullscreenTask)
+ assertThat(changes[fullscreenTask.token.asBinder()]?.windowingMode)
+ .isEqualTo(WINDOWING_MODE_FREEFORM)
+ }
+ verify(desktopModeEnterExitTransitionListener)
+ .onEnterDesktopModeTransitionStarted(FREEFORM_ANIMATION_DURATION)
+ }
+
+ @Test
+ fun moveRunningTaskToDesktop_onlyFreeformTasksFromCurrentDisplayBroughtToFront() {
+ setUpHomeTask(displayId = DEFAULT_DISPLAY)
+ val freeformTaskDefault = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
+ val fullscreenTaskDefault = setUpFullscreenTask(displayId = DEFAULT_DISPLAY)
+ markTaskHidden(freeformTaskDefault)
+
+ val homeTaskSecond = setUpHomeTask(displayId = SECOND_DISPLAY)
+ val freeformTaskSecond = setUpFreeformTask(displayId = SECOND_DISPLAY)
+ markTaskHidden(freeformTaskSecond)
+
+ controller.moveRunningTaskToDesktop(fullscreenTaskDefault, transitionSource = UNKNOWN)
+
+ with(getLatestEnterDesktopWct()) {
+ // Check that hierarchy operations do not include tasks from second display
+ assertThat(hierarchyOps.map { it.container })
+ .doesNotContain(homeTaskSecond.token.asBinder())
+ assertThat(hierarchyOps.map { it.container })
+ .doesNotContain(freeformTaskSecond.token.asBinder())
+ }
+ verify(desktopModeEnterExitTransitionListener)
+ .onEnterDesktopModeTransitionStarted(FREEFORM_ANIMATION_DURATION)
+ }
+
+ @Test
+ fun moveRunningTaskToDesktop_splitTaskExitsSplit() {
+ val task = setUpSplitScreenTask()
+ controller.moveRunningTaskToDesktop(task, transitionSource = UNKNOWN)
+ val wct = getLatestEnterDesktopWct()
+ assertThat(wct.changes[task.token.asBinder()]?.windowingMode)
+ .isEqualTo(WINDOWING_MODE_FREEFORM)
+ verify(desktopModeEnterExitTransitionListener)
+ .onEnterDesktopModeTransitionStarted(FREEFORM_ANIMATION_DURATION)
+ verify(splitScreenController)
+ .prepareExitSplitScreen(
+ any(),
+ anyInt(),
+ eq(SplitScreenController.EXIT_REASON_DESKTOP_MODE),
+ )
+ }
+
+ @Test
+ fun moveRunningTaskToDesktop_fullscreenTaskDoesNotExitSplit() {
+ val task = setUpFullscreenTask()
+ controller.moveRunningTaskToDesktop(task, transitionSource = UNKNOWN)
+ val wct = getLatestEnterDesktopWct()
+ assertThat(wct.changes[task.token.asBinder()]?.windowingMode)
+ .isEqualTo(WINDOWING_MODE_FREEFORM)
+ verify(desktopModeEnterExitTransitionListener)
+ .onEnterDesktopModeTransitionStarted(FREEFORM_ANIMATION_DURATION)
+ verify(splitScreenController, never())
+ .prepareExitSplitScreen(
+ any(),
+ anyInt(),
+ eq(SplitScreenController.EXIT_REASON_DESKTOP_MODE),
+ )
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ fun moveRunningTaskToDesktop_desktopWallpaperDisabled_bringsTasksOver_dontShowBackTask() {
+ val freeformTasks = (1..MAX_TASK_LIMIT).map { _ -> setUpFreeformTask() }
+ val newTask = setUpFullscreenTask()
+ val homeTask = setUpHomeTask()
+
+ controller.moveRunningTaskToDesktop(newTask, transitionSource = UNKNOWN)
+
+ val wct = getLatestEnterDesktopWct()
+ verify(desktopModeEnterExitTransitionListener)
+ .onEnterDesktopModeTransitionStarted(FREEFORM_ANIMATION_DURATION)
+ assertThat(wct.hierarchyOps.size).isEqualTo(MAX_TASK_LIMIT + 1) // visible tasks + home
+ wct.assertReorderAt(0, homeTask)
+ wct.assertReorderSequenceInRange(
+ range = 1..<(MAX_TASK_LIMIT + 1),
+ *freeformTasks.drop(1).toTypedArray(), // Skipping freeformTasks[0]
+ newTask,
+ )
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ fun moveRunningTaskToDesktop_desktopWallpaperEnabled_bringsTasksOverLimit_dontShowBackTask() {
+ val freeformTasks = (1..MAX_TASK_LIMIT).map { _ -> setUpFreeformTask() }
+ val newTask = setUpFullscreenTask()
+ val homeTask = setUpHomeTask()
+
+ controller.moveRunningTaskToDesktop(newTask, transitionSource = UNKNOWN)
+
+ val wct = getLatestEnterDesktopWct()
+ verify(desktopModeEnterExitTransitionListener)
+ .onEnterDesktopModeTransitionStarted(FREEFORM_ANIMATION_DURATION)
+ assertThat(wct.hierarchyOps.size).isEqualTo(MAX_TASK_LIMIT + 2) // tasks + home + wallpaper
+ // Move home to front
+ wct.assertReorderAt(0, homeTask)
+ // Add desktop wallpaper activity
+ wct.assertPendingIntentAt(1, desktopWallpaperIntent)
+ // Bring freeform tasks to front
+ wct.assertReorderSequenceInRange(
+ range = 2..<(MAX_TASK_LIMIT + 2),
+ *freeformTasks.drop(1).toTypedArray(), // Skipping freeformTasks[0]
+ newTask,
+ )
+ }
+
+ @Test
+ fun moveToFullscreen_tdaFullscreen_windowingModeSetToUndefined() {
+ val task = setUpFreeformTask()
+ val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!!
+ tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FULLSCREEN
+ controller.moveToFullscreen(task.taskId, transitionSource = UNKNOWN)
+ val wct = getLatestExitDesktopWct()
+ verify(desktopModeEnterExitTransitionListener, times(1))
+ .onExitDesktopModeTransitionStarted(FULLSCREEN_ANIMATION_DURATION)
+ assertThat(wct.changes[task.token.asBinder()]?.windowingMode)
+ .isEqualTo(WINDOWING_MODE_UNDEFINED)
+ }
+
+ @Test
+ fun moveToFullscreen_tdaFullscreen_windowingModeUndefined_removesWallpaperActivity() {
+ val task = setUpFreeformTask()
+ val wallpaperToken = MockToken().token()
+
+ taskRepository.wallpaperActivityToken = wallpaperToken
+ assertNotNull(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY))
+ .configuration
+ .windowConfiguration
+ .windowingMode = WINDOWING_MODE_FULLSCREEN
+
+ controller.moveToFullscreen(task.taskId, transitionSource = UNKNOWN)
+
+ val wct = getLatestExitDesktopWct()
+ val taskChange = assertNotNull(wct.changes[task.token.asBinder()])
+ verify(desktopModeEnterExitTransitionListener)
+ .onExitDesktopModeTransitionStarted(FULLSCREEN_ANIMATION_DURATION)
+ assertThat(taskChange.windowingMode).isEqualTo(WINDOWING_MODE_UNDEFINED)
+ // Removes wallpaper activity when leaving desktop
+ wct.assertRemoveAt(index = 0, wallpaperToken)
+ }
+
+ @Test
+ fun moveToFullscreen_tdaFreeform_windowingModeSetToFullscreen() {
+ val task = setUpFreeformTask()
+ val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!!
+ tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FREEFORM
+ controller.moveToFullscreen(task.taskId, transitionSource = UNKNOWN)
+ val wct = getLatestExitDesktopWct()
+ assertThat(wct.changes[task.token.asBinder()]?.windowingMode)
+ .isEqualTo(WINDOWING_MODE_FULLSCREEN)
+ verify(desktopModeEnterExitTransitionListener)
+ .onExitDesktopModeTransitionStarted(FULLSCREEN_ANIMATION_DURATION)
+ }
+
+ @Test
+ fun moveToFullscreen_tdaFreeform_windowingModeFullscreen_removesWallpaperActivity() {
+ val task = setUpFreeformTask()
+ val wallpaperToken = MockToken().token()
+
+ taskRepository.wallpaperActivityToken = wallpaperToken
+ assertNotNull(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY))
+ .configuration
+ .windowConfiguration
+ .windowingMode = WINDOWING_MODE_FREEFORM
+
+ controller.moveToFullscreen(task.taskId, transitionSource = UNKNOWN)
+
+ val wct = getLatestExitDesktopWct()
+ val taskChange = assertNotNull(wct.changes[task.token.asBinder()])
+ assertThat(taskChange.windowingMode).isEqualTo(WINDOWING_MODE_FULLSCREEN)
+ verify(desktopModeEnterExitTransitionListener)
+ .onExitDesktopModeTransitionStarted(FULLSCREEN_ANIMATION_DURATION)
+ // Removes wallpaper activity when leaving desktop
+ wct.assertRemoveAt(index = 0, wallpaperToken)
+ }
+
+ @Test
+ fun moveToFullscreen_multipleVisibleNonMinimizedTasks_doesNotRemoveWallpaperActivity() {
+ val task1 = setUpFreeformTask()
+ // Setup task2
+ setUpFreeformTask()
+ val wallpaperToken = MockToken().token()
+
+ taskRepository.wallpaperActivityToken = wallpaperToken
+ assertNotNull(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY))
+ .configuration
+ .windowConfiguration
+ .windowingMode = WINDOWING_MODE_FULLSCREEN
+
+ controller.moveToFullscreen(task1.taskId, transitionSource = UNKNOWN)
+
+ val wct = getLatestExitDesktopWct()
+ val task1Change = assertNotNull(wct.changes[task1.token.asBinder()])
+ assertThat(task1Change.windowingMode).isEqualTo(WINDOWING_MODE_UNDEFINED)
+ verify(desktopModeEnterExitTransitionListener)
+ .onExitDesktopModeTransitionStarted(FULLSCREEN_ANIMATION_DURATION)
+ // Does not remove wallpaper activity, as desktop still has a visible desktop task
+ assertThat(wct.hierarchyOps).isEmpty()
+ }
+
+ @Test
+ fun moveToFullscreen_nonExistentTask_doesNothing() {
+ controller.moveToFullscreen(999, transitionSource = UNKNOWN)
+ verifyExitDesktopWCTNotExecuted()
+ }
+
+ @Test
+ fun moveToFullscreen_secondDisplayTaskHasFreeform_secondDisplayNotAffected() {
+ val taskDefaultDisplay = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
+ val taskSecondDisplay = setUpFreeformTask(displayId = SECOND_DISPLAY)
+ controller.moveToFullscreen(taskDefaultDisplay.taskId, transitionSource = UNKNOWN)
+
+ with(getLatestExitDesktopWct()) {
+ assertThat(changes.keys).contains(taskDefaultDisplay.token.asBinder())
+ assertThat(changes.keys).doesNotContain(taskSecondDisplay.token.asBinder())
+ }
+ verify(desktopModeEnterExitTransitionListener)
+ .onExitDesktopModeTransitionStarted(FULLSCREEN_ANIMATION_DURATION)
+ }
+
+ @Test
+ fun moveTaskToFront_postsWctWithReorderOp() {
+ val task1 = setUpFreeformTask()
+ setUpFreeformTask()
+
+ controller.moveTaskToFront(task1, remoteTransition = null)
+
+ val wct = getLatestDesktopMixedTaskWct(type = TRANSIT_TO_FRONT)
+ assertThat(wct.hierarchyOps).hasSize(1)
+ wct.assertReorderAt(index = 0, task1)
+ }
+
+ @Test
+ fun moveTaskToFront_bringsTasksOverLimit_minimizesBackTask() {
+ setUpHomeTask()
+ val freeformTasks = (1..MAX_TASK_LIMIT + 1).map { _ -> setUpFreeformTask() }
+ whenever(
+ desktopMixedTransitionHandler.startLaunchTransition(
+ eq(TRANSIT_TO_FRONT),
+ any(),
+ eq(freeformTasks[0].taskId),
+ anyOrNull(),
+ anyOrNull(),
+ )
+ )
+ .thenReturn(Binder())
+
+ controller.moveTaskToFront(freeformTasks[0], remoteTransition = null)
+
+ val wct = getLatestDesktopMixedTaskWct(type = TRANSIT_TO_FRONT)
+ assertThat(wct.hierarchyOps.size).isEqualTo(2) // move-to-front + minimize
+ wct.assertReorderAt(0, freeformTasks[0], toTop = true)
+ wct.assertReorderAt(1, freeformTasks[1], toTop = false)
+ }
+
+ @Test
+ fun moveTaskToFront_remoteTransition_usesOneshotHandler() {
+ setUpHomeTask()
+ val freeformTasks = List(MAX_TASK_LIMIT) { setUpFreeformTask() }
+ val transitionHandlerArgCaptor = ArgumentCaptor.forClass(TransitionHandler::class.java)
+ whenever(transitions.startTransition(anyInt(), any(), transitionHandlerArgCaptor.capture()))
+ .thenReturn(Binder())
+
+ controller.moveTaskToFront(freeformTasks[0], RemoteTransition(TestRemoteTransition()))
+
+ assertIs<OneShotRemoteHandler>(transitionHandlerArgCaptor.value)
+ }
+
+ @Test
+ fun moveTaskToFront_bringsTasksOverLimit_remoteTransition_usesWindowLimitHandler() {
+ setUpHomeTask()
+ val freeformTasks = List(MAX_TASK_LIMIT + 1) { setUpFreeformTask() }
+ val transitionHandlerArgCaptor = ArgumentCaptor.forClass(TransitionHandler::class.java)
+ whenever(transitions.startTransition(anyInt(), any(), transitionHandlerArgCaptor.capture()))
+ .thenReturn(Binder())
+
+ controller.moveTaskToFront(freeformTasks[0], RemoteTransition(TestRemoteTransition()))
+
+ assertThat(transitionHandlerArgCaptor.value)
+ .isInstanceOf(DesktopWindowLimitRemoteHandler::class.java)
+ }
+
+ @Test
+ fun moveTaskToFront_backgroundTask_launchesTask() {
+ val task = createTaskInfo(1)
+ whenever(shellTaskOrganizer.getRunningTaskInfo(anyInt())).thenReturn(null)
+
+ controller.moveTaskToFront(task.taskId, remoteTransition = null)
+
+ val wct = getLatestDesktopMixedTaskWct(type = TRANSIT_OPEN)
+ assertThat(wct.hierarchyOps).hasSize(1)
+ wct.assertLaunchTaskAt(0, task.taskId, WINDOWING_MODE_FREEFORM)
+ }
+
+ @Test
+ fun moveTaskToFront_backgroundTaskBringsTasksOverLimit_minimizesBackTask() {
+ val freeformTasks = (1..MAX_TASK_LIMIT).map { _ -> setUpFreeformTask() }
+ val task = createTaskInfo(1001)
+ whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(null)
+ whenever(
+ desktopMixedTransitionHandler.startLaunchTransition(
+ eq(TRANSIT_OPEN),
+ any(),
+ eq(task.taskId),
+ anyOrNull(),
+ anyOrNull(),
+ )
+ )
+ .thenReturn(Binder())
+
+ controller.moveTaskToFront(task.taskId, remoteTransition = null)
+
+ val wct = getLatestDesktopMixedTaskWct(type = TRANSIT_OPEN)
+ assertThat(wct.hierarchyOps.size).isEqualTo(2) // launch + minimize
+ wct.assertLaunchTaskAt(0, task.taskId, WINDOWING_MODE_FREEFORM)
+ wct.assertReorderAt(1, freeformTasks[0], toTop = false)
+ }
+
+ @Test
+ fun moveToNextDisplay_noOtherDisplays() {
+ whenever(rootTaskDisplayAreaOrganizer.displayIds).thenReturn(intArrayOf(DEFAULT_DISPLAY))
+ val task = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
+ controller.moveToNextDisplay(task.taskId)
+ verifyWCTNotExecuted()
+ }
+
+ @Test
+ fun moveToNextDisplay_moveFromFirstToSecondDisplay() {
+ // Set up two display ids
+ whenever(rootTaskDisplayAreaOrganizer.displayIds)
+ .thenReturn(intArrayOf(DEFAULT_DISPLAY, SECOND_DISPLAY))
+ // Create a mock for the target display area: second display
+ val secondDisplayArea = DisplayAreaInfo(MockToken().token(), SECOND_DISPLAY, 0)
+ whenever(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(SECOND_DISPLAY))
+ .thenReturn(secondDisplayArea)
+
+ val task = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
+ controller.moveToNextDisplay(task.taskId)
+ with(getLatestWct(type = TRANSIT_CHANGE)) {
+ assertThat(hierarchyOps).hasSize(1)
+ assertThat(hierarchyOps[0].container).isEqualTo(task.token.asBinder())
+ assertThat(hierarchyOps[0].isReparent).isTrue()
+ assertThat(hierarchyOps[0].newParent).isEqualTo(secondDisplayArea.token.asBinder())
+ assertThat(hierarchyOps[0].toTop).isTrue()
+ }
+ }
+
+ @Test
+ fun moveToNextDisplay_moveFromSecondToFirstDisplay() {
+ // Set up two display ids
+ whenever(rootTaskDisplayAreaOrganizer.displayIds)
+ .thenReturn(intArrayOf(DEFAULT_DISPLAY, SECOND_DISPLAY))
+ // Create a mock for the target display area: default display
+ val defaultDisplayArea = DisplayAreaInfo(MockToken().token(), DEFAULT_DISPLAY, 0)
+ whenever(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY))
+ .thenReturn(defaultDisplayArea)
+
+ val task = setUpFreeformTask(displayId = SECOND_DISPLAY)
+ controller.moveToNextDisplay(task.taskId)
+
+ with(getLatestWct(type = TRANSIT_CHANGE)) {
+ assertThat(hierarchyOps).hasSize(1)
+ assertThat(hierarchyOps[0].container).isEqualTo(task.token.asBinder())
+ assertThat(hierarchyOps[0].isReparent).isTrue()
+ assertThat(hierarchyOps[0].newParent).isEqualTo(defaultDisplayArea.token.asBinder())
+ assertThat(hierarchyOps[0].toTop).isTrue()
+ }
+ }
+
+ @Test
+ @EnableFlags(FLAG_ENABLE_PER_DISPLAY_DESKTOP_WALLPAPER_ACTIVITY)
+ fun moveToNextDisplay_removeWallpaper() {
+ // Set up two display ids
+ whenever(rootTaskDisplayAreaOrganizer.displayIds)
+ .thenReturn(intArrayOf(DEFAULT_DISPLAY, SECOND_DISPLAY))
+ // Create a mock for the target display area: second display
+ val secondDisplayArea = DisplayAreaInfo(MockToken().token(), SECOND_DISPLAY, 0)
+ whenever(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(SECOND_DISPLAY))
+ .thenReturn(secondDisplayArea)
+ // Add a task and a wallpaper
+ val task = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
+ val wallpaperToken = MockToken().token()
+ taskRepository.wallpaperActivityToken = wallpaperToken
+
+ controller.moveToNextDisplay(task.taskId)
+
+ with(getLatestWct(type = TRANSIT_CHANGE)) {
+ val wallpaperChange =
+ hierarchyOps.find { op -> op.container == wallpaperToken.asBinder() }
+ assertThat(wallpaperChange).isNotNull()
+ assertThat(wallpaperChange!!.type).isEqualTo(HIERARCHY_OP_TYPE_REMOVE_TASK)
+ }
+ }
+
+ @Test
+ fun getTaskWindowingMode() {
+ val fullscreenTask = setUpFullscreenTask()
+ val freeformTask = setUpFreeformTask()
+
+ assertThat(controller.getTaskWindowingMode(fullscreenTask.taskId))
+ .isEqualTo(WINDOWING_MODE_FULLSCREEN)
+ assertThat(controller.getTaskWindowingMode(freeformTask.taskId))
+ .isEqualTo(WINDOWING_MODE_FREEFORM)
+ assertThat(controller.getTaskWindowingMode(999)).isEqualTo(WINDOWING_MODE_UNDEFINED)
+ }
+
+ @Test
+ fun onDesktopWindowClose_noActiveTasks() {
+ val task = setUpFreeformTask(active = false)
+ val wct = WindowContainerTransaction()
+ controller.onDesktopWindowClose(wct, displayId = DEFAULT_DISPLAY, task)
+ // Doesn't modify transaction
+ assertThat(wct.hierarchyOps).isEmpty()
+ }
+
+ @Test
+ fun onDesktopWindowClose_singleActiveTask_noWallpaperActivityToken() {
+ val task = setUpFreeformTask()
+ val wct = WindowContainerTransaction()
+ controller.onDesktopWindowClose(wct, displayId = DEFAULT_DISPLAY, task)
+ // Doesn't modify transaction
+ assertThat(wct.hierarchyOps).isEmpty()
+ }
+
+ @Test
+ fun onDesktopWindowClose_singleActiveTask_hasWallpaperActivityToken() {
+ val task = setUpFreeformTask()
+ val wallpaperToken = MockToken().token()
+ taskRepository.wallpaperActivityToken = wallpaperToken
+
+ val wct = WindowContainerTransaction()
+ controller.onDesktopWindowClose(wct, displayId = DEFAULT_DISPLAY, task)
+ // Adds remove wallpaper operation
+ wct.assertRemoveAt(index = 0, wallpaperToken)
+ }
+
+ @Test
+ fun onDesktopWindowClose_singleActiveTask_isClosing() {
+ val task = setUpFreeformTask()
+ val wallpaperToken = MockToken().token()
+ taskRepository.wallpaperActivityToken = wallpaperToken
+ taskRepository.addClosingTask(DEFAULT_DISPLAY, task.taskId)
+
+ val wct = WindowContainerTransaction()
+ controller.onDesktopWindowClose(wct, displayId = DEFAULT_DISPLAY, task)
+ // Doesn't modify transaction
+ assertThat(wct.hierarchyOps).isEmpty()
+ }
+
+ @Test
+ fun onDesktopWindowClose_singleActiveTask_isMinimized() {
+ val task = setUpFreeformTask()
+ val wallpaperToken = MockToken().token()
+ taskRepository.wallpaperActivityToken = wallpaperToken
+ taskRepository.minimizeTask(DEFAULT_DISPLAY, task.taskId)
+
+ val wct = WindowContainerTransaction()
+ controller.onDesktopWindowClose(wct, displayId = DEFAULT_DISPLAY, task)
+ // Doesn't modify transaction
+ assertThat(wct.hierarchyOps).isEmpty()
+ }
+
+ @Test
+ fun onDesktopWindowClose_multipleActiveTasks() {
+ val task1 = setUpFreeformTask()
+ setUpFreeformTask()
+ val wallpaperToken = MockToken().token()
+ taskRepository.wallpaperActivityToken = wallpaperToken
+
+ val wct = WindowContainerTransaction()
+ controller.onDesktopWindowClose(wct, displayId = DEFAULT_DISPLAY, task1)
+ // Doesn't modify transaction
+ assertThat(wct.hierarchyOps).isEmpty()
+ }
+
+ @Test
+ fun onDesktopWindowClose_multipleActiveTasks_isOnlyNonClosingTask() {
+ val task1 = setUpFreeformTask()
+ val task2 = setUpFreeformTask()
+ val wallpaperToken = MockToken().token()
+ taskRepository.wallpaperActivityToken = wallpaperToken
+ taskRepository.addClosingTask(DEFAULT_DISPLAY, task2.taskId)
+
+ val wct = WindowContainerTransaction()
+ controller.onDesktopWindowClose(wct, displayId = DEFAULT_DISPLAY, task1)
+ // Adds remove wallpaper operation
+ wct.assertRemoveAt(index = 0, wallpaperToken)
+ }
+
+ @Test
+ fun onDesktopWindowClose_multipleActiveTasks_hasMinimized() {
+ val task1 = setUpFreeformTask()
+ val task2 = setUpFreeformTask()
+ val wallpaperToken = MockToken().token()
+ taskRepository.wallpaperActivityToken = wallpaperToken
+ taskRepository.minimizeTask(DEFAULT_DISPLAY, task2.taskId)
+
+ val wct = WindowContainerTransaction()
+ controller.onDesktopWindowClose(wct, displayId = DEFAULT_DISPLAY, task1)
+ // Adds remove wallpaper operation
+ wct.assertRemoveAt(index = 0, wallpaperToken)
+ }
+
+ @Test
+ fun onDesktopWindowMinimize_noActiveTask_doesntRemoveWallpaper() {
+ val task = setUpFreeformTask(active = false)
+ val transition = Binder()
+ whenever(freeformTaskTransitionStarter.startMinimizedModeTransition(any()))
+ .thenReturn(transition)
+ val wallpaperToken = MockToken().token()
+ taskRepository.wallpaperActivityToken = wallpaperToken
+
+ controller.minimizeTask(task)
+
+ val captor = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
+ verify(freeformTaskTransitionStarter).startMinimizedModeTransition(captor.capture())
+ captor.value.hierarchyOps.none { hop ->
+ hop.type == HIERARCHY_OP_TYPE_REMOVE_TASK && hop.container == wallpaperToken.asBinder()
+ }
+ }
+
+ @Test
+ fun onDesktopWindowMinimize_pipTask_autoEnterEnabled_startPipTransition() {
+ val task = setUpPipTask(autoEnterEnabled = true)
+ val handler = mock(TransitionHandler::class.java)
+ whenever(freeformTaskTransitionStarter.startPipTransition(any())).thenReturn(Binder())
+ whenever(transitions.dispatchRequest(any(), any(), anyOrNull()))
+ .thenReturn(android.util.Pair(handler, WindowContainerTransaction()))
+
+ controller.minimizeTask(task)
+
+ verify(freeformTaskTransitionStarter).startPipTransition(any())
+ verify(freeformTaskTransitionStarter, never()).startMinimizedModeTransition(any())
+ }
+
+ @Test
+ fun onDesktopWindowMinimize_pipTask_autoEnterDisabled_startMinimizeTransition() {
+ val task = setUpPipTask(autoEnterEnabled = false)
+ whenever(freeformTaskTransitionStarter.startMinimizedModeTransition(any()))
+ .thenReturn(Binder())
+
+ controller.minimizeTask(task)
+
+ verify(freeformTaskTransitionStarter).startMinimizedModeTransition(any())
+ verify(freeformTaskTransitionStarter, never()).startPipTransition(any())
+ }
+
+ @Test
+ fun onDesktopWindowMinimize_singleActiveTask_noWallpaperActivityToken_doesntRemoveWallpaper() {
+ val task = setUpFreeformTask(active = true)
+ val transition = Binder()
+ whenever(freeformTaskTransitionStarter.startMinimizedModeTransition(any()))
+ .thenReturn(transition)
+
+ controller.minimizeTask(task)
+
+ val captor = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
+ verify(freeformTaskTransitionStarter).startMinimizedModeTransition(captor.capture())
+ captor.value.hierarchyOps.none { hop -> hop.type == HIERARCHY_OP_TYPE_REMOVE_TASK }
+ }
+
+ @Test
+ fun onTaskMinimize_singleActiveTask_hasWallpaperActivityToken_removesWallpaper() {
+ val task = setUpFreeformTask()
+ val transition = Binder()
+ whenever(freeformTaskTransitionStarter.startMinimizedModeTransition(any()))
+ .thenReturn(transition)
+ val wallpaperToken = MockToken().token()
+ taskRepository.wallpaperActivityToken = wallpaperToken
+
+ // The only active task is being minimized.
+ controller.minimizeTask(task)
+
+ val captor = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
+ verify(freeformTaskTransitionStarter).startMinimizedModeTransition(captor.capture())
+ // Adds remove wallpaper operation
+ captor.value.assertRemoveAt(index = 0, wallpaperToken)
+ }
+
+ @Test
+ fun onDesktopWindowMinimize_singleActiveTask_alreadyMinimized_doesntRemoveWallpaper() {
+ val task = setUpFreeformTask()
+ val transition = Binder()
+ whenever(freeformTaskTransitionStarter.startMinimizedModeTransition(any()))
+ .thenReturn(transition)
+ val wallpaperToken = MockToken().token()
+ taskRepository.wallpaperActivityToken = wallpaperToken
+ taskRepository.minimizeTask(DEFAULT_DISPLAY, task.taskId)
+
+ // The only active task is already minimized.
+ controller.minimizeTask(task)
+
+ val captor = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
+ verify(freeformTaskTransitionStarter).startMinimizedModeTransition(captor.capture())
+ captor.value.hierarchyOps.none { hop ->
+ hop.type == HIERARCHY_OP_TYPE_REMOVE_TASK && hop.container == wallpaperToken.asBinder()
+ }
+ }
+
+ @Test
+ fun onDesktopWindowMinimize_multipleActiveTasks_doesntRemoveWallpaper() {
+ val task1 = setUpFreeformTask(active = true)
+ setUpFreeformTask(active = true)
+ val transition = Binder()
+ whenever(freeformTaskTransitionStarter.startMinimizedModeTransition(any()))
+ .thenReturn(transition)
+ val wallpaperToken = MockToken().token()
+ taskRepository.wallpaperActivityToken = wallpaperToken
+
+ controller.minimizeTask(task1)
+
+ val captor = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
+ verify(freeformTaskTransitionStarter).startMinimizedModeTransition(captor.capture())
+ captor.value.hierarchyOps.none { hop ->
+ hop.type == HIERARCHY_OP_TYPE_REMOVE_TASK && hop.container == wallpaperToken.asBinder()
+ }
+ }
+
+ @Test
+ fun onDesktopWindowMinimize_multipleActiveTasks_minimizesTheOnlyVisibleTask_removesWallpaper() {
+ val task1 = setUpFreeformTask(active = true)
+ val task2 = setUpFreeformTask(active = true)
+ val transition = Binder()
+ whenever(freeformTaskTransitionStarter.startMinimizedModeTransition(any()))
+ .thenReturn(transition)
+ val wallpaperToken = MockToken().token()
+ taskRepository.wallpaperActivityToken = wallpaperToken
+ taskRepository.minimizeTask(DEFAULT_DISPLAY, task2.taskId)
+
+ // task1 is the only visible task as task2 is minimized.
+ controller.minimizeTask(task1)
+ // Adds remove wallpaper operation
+ val captor = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
+ verify(freeformTaskTransitionStarter).startMinimizedModeTransition(captor.capture())
+ // Adds remove wallpaper operation
+ captor.value.assertRemoveAt(index = 0, wallpaperToken)
+ }
+
+ @Test
+ fun onDesktopWindowMinimize_triesToExitImmersive() {
+ val task = setUpFreeformTask()
+ val transition = Binder()
+ whenever(freeformTaskTransitionStarter.startMinimizedModeTransition(any()))
+ .thenReturn(transition)
+
+ controller.minimizeTask(task)
+
+ verify(mMockDesktopImmersiveController).exitImmersiveIfApplicable(any(), eq(task), any())
+ }
+
+ @Test
+ fun onDesktopWindowMinimize_invokesImmersiveTransitionStartCallback() {
+ val task = setUpFreeformTask()
+ val transition = Binder()
+ val runOnTransit = RunOnStartTransitionCallback()
+ whenever(freeformTaskTransitionStarter.startMinimizedModeTransition(any()))
+ .thenReturn(transition)
+ whenever(mMockDesktopImmersiveController.exitImmersiveIfApplicable(any(), eq(task), any()))
+ .thenReturn(
+ ExitResult.Exit(exitingTask = task.taskId, runOnTransitionStart = runOnTransit)
+ )
+
+ controller.minimizeTask(task)
+
+ assertThat(runOnTransit.invocations).isEqualTo(1)
+ assertThat(runOnTransit.lastInvoked).isEqualTo(transition)
+ }
+
+ @Test
+ fun handleRequest_fullscreenTask_freeformVisible_returnSwitchToFreeformWCT() {
+ val homeTask = setUpHomeTask()
+ val freeformTask = setUpFreeformTask()
+ markTaskVisible(freeformTask)
+ val fullscreenTask = createFullscreenTask()
+
+ val wct = controller.handleRequest(Binder(), createTransition(fullscreenTask))
+
+ assertNotNull(wct, "should handle request")
+ assertThat(wct.changes[fullscreenTask.token.asBinder()]?.windowingMode)
+ .isEqualTo(WINDOWING_MODE_FREEFORM)
+
+ assertThat(wct.hierarchyOps).hasSize(1)
+ }
+
+ @Test
+ fun handleRequest_fullscreenTaskWithTaskOnHome_freeformVisible_returnSwitchToFreeformWCT() {
+ val homeTask = setUpHomeTask()
+ val freeformTask = setUpFreeformTask()
+ markTaskVisible(freeformTask)
+ val fullscreenTask = createFullscreenTask()
+ fullscreenTask.baseIntent.setFlags(Intent.FLAG_ACTIVITY_TASK_ON_HOME)
+
+ val wct = controller.handleRequest(Binder(), createTransition(fullscreenTask))
+
+ assertNotNull(wct, "should handle request")
+ assertThat(wct.changes[fullscreenTask.token.asBinder()]?.windowingMode)
+ .isEqualTo(WINDOWING_MODE_FREEFORM)
+
+ // There are 5 hops that are happening in this case:
+ // 1. Moving the fullscreen task to top as we add moveToDesktop() changes
+ // 2. Bringing home task to front
+ // 3. Pending intent for the wallpaper
+ // 4. Bringing the existing freeform task to top
+ // 5. Bringing the fullscreen task back at the top
+ assertThat(wct.hierarchyOps).hasSize(5)
+ wct.assertReorderAt(1, homeTask, toTop = true)
+ wct.assertReorderAt(4, fullscreenTask, toTop = true)
+ }
+
+ @Test
+ fun handleRequest_fullscreenTaskToFreeform_underTaskLimit_dontMinimize() {
+ val freeformTask = setUpFreeformTask()
+ markTaskVisible(freeformTask)
+ val fullscreenTask = createFullscreenTask()
+
+ val wct = controller.handleRequest(Binder(), createTransition(fullscreenTask))
+
+ // Make sure we only reorder the new task to top (we don't reorder the old task to bottom)
+ assertThat(wct?.hierarchyOps?.size).isEqualTo(1)
+ wct!!.assertReorderAt(0, fullscreenTask, toTop = true)
+ }
+
+ @Test
+ fun handleRequest_fullscreenTaskToFreeform_bringsTasksOverLimit_otherTaskIsMinimized() {
+ val freeformTasks = (1..MAX_TASK_LIMIT).map { _ -> setUpFreeformTask() }
+ freeformTasks.forEach { markTaskVisible(it) }
+ val fullscreenTask = createFullscreenTask()
+
+ val wct = controller.handleRequest(Binder(), createTransition(fullscreenTask))
+
+ // Make sure we reorder the new task to top, and the back task to the bottom
+ assertThat(wct!!.hierarchyOps.size).isEqualTo(2)
+ wct.assertReorderAt(0, fullscreenTask, toTop = true)
+ wct.assertReorderAt(1, freeformTasks[0], toTop = false)
+ }
+
+ @Test
+ fun handleRequest_fullscreenTaskWithTaskOnHome_bringsTasksOverLimit_otherTaskIsMinimized() {
+ val freeformTasks = (1..MAX_TASK_LIMIT).map { _ -> setUpFreeformTask() }
+ freeformTasks.forEach { markTaskVisible(it) }
+ val fullscreenTask = createFullscreenTask()
+ fullscreenTask.baseIntent.setFlags(Intent.FLAG_ACTIVITY_TASK_ON_HOME)
+
+ val wct = controller.handleRequest(Binder(), createTransition(fullscreenTask))
+
+ // Make sure we reorder the new task to top, and the back task to the bottom
+ assertThat(wct!!.hierarchyOps.size).isEqualTo(9)
+ wct.assertReorderAt(0, fullscreenTask, toTop = true)
+ wct.assertReorderAt(8, freeformTasks[0], toTop = false)
+ }
+
+ @Test
+ fun handleRequest_fullscreenTaskWithTaskOnHome_beyondLimit_existingAndNewTasksAreMinimized() {
+ val minimizedTask = setUpFreeformTask()
+ taskRepository.minimizeTask(displayId = DEFAULT_DISPLAY, taskId = minimizedTask.taskId)
+ val freeformTasks = (1..MAX_TASK_LIMIT).map { _ -> setUpFreeformTask() }
+ freeformTasks.forEach { markTaskVisible(it) }
+ val homeTask = setUpHomeTask()
+ val fullscreenTask = createFullscreenTask()
+ fullscreenTask.baseIntent.setFlags(Intent.FLAG_ACTIVITY_TASK_ON_HOME)
+
+ val wct = controller.handleRequest(Binder(), createTransition(fullscreenTask))
+
+ assertThat(wct!!.hierarchyOps.size).isEqualTo(10)
+ wct.assertReorderAt(0, fullscreenTask, toTop = true)
+ // Make sure we reorder the home task to the top, desktop tasks to top of them and minimized
+ // task is under the home task.
+ wct.assertReorderAt(1, homeTask, toTop = true)
+ wct.assertReorderAt(9, freeformTasks[0], toTop = false)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ fun handleRequest_fullscreenTask_noTasks_enforceDesktop_freeformDisplay_returnFreeformWCT() {
+ whenever(DesktopModeStatus.enterDesktopByDefaultOnFreeformDisplay(context)).thenReturn(true)
+ val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!!
+ tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FREEFORM
+
+ val fullscreenTask = createFullscreenTask()
+ val wct = controller.handleRequest(Binder(), createTransition(fullscreenTask))
+
+ assertNotNull(wct, "should handle request")
+ assertThat(wct.changes[fullscreenTask.token.asBinder()]?.windowingMode)
+ .isEqualTo(WINDOWING_MODE_UNDEFINED)
+ assertThat(wct.hierarchyOps).hasSize(3)
+ // There are 3 hops that are happening in this case:
+ // 1. Moving the fullscreen task to top as we add moveToDesktop() changes
+ // 2. Pending intent for the wallpaper
+ // 3. Bringing the fullscreen task back at the top
+ wct.assertPendingIntentAt(1, desktopWallpaperIntent)
+ wct.assertReorderAt(2, fullscreenTask, toTop = true)
+ }
+
+ @Test
+ fun handleRequest_fullscreenTask_noTasks_enforceDesktop_fullscreenDisplay_returnNull() {
+ whenever(DesktopModeStatus.enterDesktopByDefaultOnFreeformDisplay(context)).thenReturn(true)
+ val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!!
+ tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FULLSCREEN
+
+ val fullscreenTask = createFullscreenTask()
+ val wct = controller.handleRequest(Binder(), createTransition(fullscreenTask))
+
+ assertThat(wct).isNull()
+ }
+
+ @Test
+ fun handleRequest_fullscreenTask_freeformNotVisible_returnNull() {
+ val freeformTask = setUpFreeformTask()
+ markTaskHidden(freeformTask)
+ val fullscreenTask = createFullscreenTask()
+ assertThat(controller.handleRequest(Binder(), createTransition(fullscreenTask))).isNull()
+ }
+
+ @Test
+ fun handleRequest_fullscreenTask_noOtherTasks_returnNull() {
+ val fullscreenTask = createFullscreenTask()
+ assertThat(controller.handleRequest(Binder(), createTransition(fullscreenTask))).isNull()
+ }
+
+ @Test
+ fun handleRequest_fullscreenTask_freeformTaskOnOtherDisplay_returnNull() {
+ val fullscreenTaskDefaultDisplay = createFullscreenTask(displayId = DEFAULT_DISPLAY)
+ createFreeformTask(displayId = SECOND_DISPLAY)
+
+ val result =
+ controller.handleRequest(Binder(), createTransition(fullscreenTaskDefaultDisplay))
+ assertThat(result).isNull()
+ }
+
+ @Test
+ fun handleRequest_freeformTask_freeformVisible_aboveTaskLimit_minimize() {
+ val freeformTasks = (1..MAX_TASK_LIMIT).map { _ -> setUpFreeformTask() }
+ freeformTasks.forEach { markTaskVisible(it) }
+ val newFreeformTask = createFreeformTask()
+
+ val wct =
+ controller.handleRequest(Binder(), createTransition(newFreeformTask, TRANSIT_OPEN))
+
+ assertThat(wct?.hierarchyOps?.size).isEqualTo(1)
+ wct!!.assertReorderAt(0, freeformTasks[0], toTop = false) // Reorder to the bottom
+ }
+
+ @Test
+ fun handleRequest_freeformTask_relaunchActiveTask_taskBecomesUndefined() {
+ val freeformTask = setUpFreeformTask()
+ markTaskHidden(freeformTask)
+
+ val wct = controller.handleRequest(Binder(), createTransition(freeformTask))
+
+ // Should become undefined as the TDA is set to fullscreen. It will inherit from the TDA.
+ assertNotNull(wct, "should handle request")
+ assertThat(wct.changes[freeformTask.token.asBinder()]?.windowingMode)
+ .isEqualTo(WINDOWING_MODE_UNDEFINED)
+ }
+
+ @Test
+ fun handleRequest_freeformTask_relaunchTask_enforceDesktop_freeformDisplay_noWinModeChange() {
+ whenever(DesktopModeStatus.enterDesktopByDefaultOnFreeformDisplay(context)).thenReturn(true)
+ val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!!
+ tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FREEFORM
+
+ val freeformTask = setUpFreeformTask()
+ markTaskHidden(freeformTask)
+ val wct = controller.handleRequest(Binder(), createTransition(freeformTask))
+
+ assertNotNull(wct, "should handle request")
+ assertFalse(wct.anyWindowingModeChange(freeformTask.token))
+ }
+
+ @Test
+ fun handleRequest_freeformTask_relaunchTask_enforceDesktop_fullscreenDisplay_becomesUndefined() {
+ whenever(DesktopModeStatus.enterDesktopByDefaultOnFreeformDisplay(context)).thenReturn(true)
+ val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!!
+ tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FULLSCREEN
+
+ val freeformTask = setUpFreeformTask()
+ markTaskHidden(freeformTask)
+ val wct = controller.handleRequest(Binder(), createTransition(freeformTask))
+
+ assertNotNull(wct, "should handle request")
+ assertThat(wct.changes[freeformTask.token.asBinder()]?.windowingMode)
+ .isEqualTo(WINDOWING_MODE_UNDEFINED)
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ fun handleRequest_freeformTask_desktopWallpaperDisabled_freeformNotVisible_reorderedToTop() {
+ val freeformTask1 = setUpFreeformTask()
+ val freeformTask2 = createFreeformTask()
+
+ markTaskHidden(freeformTask1)
+ val result =
+ controller.handleRequest(
+ Binder(),
+ createTransition(freeformTask2, type = TRANSIT_TO_FRONT),
+ )
+
+ assertNotNull(result, "Should handle request")
+ assertThat(result.hierarchyOps?.size).isEqualTo(2)
+ result.assertReorderAt(1, freeformTask2, toTop = true)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ fun handleRequest_freeformTask_desktopWallpaperEnabled_freeformNotVisible_reorderedToTop() {
+ val freeformTask1 = setUpFreeformTask()
+ val freeformTask2 = createFreeformTask()
+
+ markTaskHidden(freeformTask1)
+ val result =
+ controller.handleRequest(
+ Binder(),
+ createTransition(freeformTask2, type = TRANSIT_TO_FRONT),
+ )
+
+ assertNotNull(result, "Should handle request")
+ assertThat(result.hierarchyOps?.size).isEqualTo(3)
+ // Add desktop wallpaper activity
+ result.assertPendingIntentAt(0, desktopWallpaperIntent)
+ // Bring active desktop tasks to front
+ result.assertReorderAt(1, freeformTask1, toTop = true)
+ // Bring new task to front
+ result.assertReorderAt(2, freeformTask2, toTop = true)
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ fun handleRequest_freeformTask_desktopWallpaperDisabled_noOtherTasks_reorderedToTop() {
+ val task = createFreeformTask()
+ val result = controller.handleRequest(Binder(), createTransition(task))
+
+ assertNotNull(result, "Should handle request")
+ assertThat(result.hierarchyOps?.size).isEqualTo(1)
+ result.assertReorderAt(0, task, toTop = true)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ fun handleRequest_freeformTask_desktopWallpaperEnabled_noOtherTasks_reorderedToTop() {
+ val task = createFreeformTask()
+ val result = controller.handleRequest(Binder(), createTransition(task))
+
+ assertNotNull(result, "Should handle request")
+ assertThat(result.hierarchyOps?.size).isEqualTo(2)
+ // Add desktop wallpaper activity
+ result.assertPendingIntentAt(0, desktopWallpaperIntent)
+ // Bring new task to front
+ result.assertReorderAt(1, task, toTop = true)
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ fun handleRequest_freeformTask_dskWallpaperDisabled_freeformOnOtherDisplayOnly_reorderedToTop() {
+ val taskDefaultDisplay = createFreeformTask(displayId = DEFAULT_DISPLAY)
+ // Second display task
+ createFreeformTask(displayId = SECOND_DISPLAY)
+
+ val result = controller.handleRequest(Binder(), createTransition(taskDefaultDisplay))
+
+ assertNotNull(result, "Should handle request")
+ assertThat(result.hierarchyOps?.size).isEqualTo(1)
+ result.assertReorderAt(0, taskDefaultDisplay, toTop = true)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ fun handleRequest_freeformTask_dskWallpaperEnabled_freeformOnOtherDisplayOnly_reorderedToTop() {
+ val taskDefaultDisplay = createFreeformTask(displayId = DEFAULT_DISPLAY)
+ // Second display task
+ createFreeformTask(displayId = SECOND_DISPLAY)
+
+ val result = controller.handleRequest(Binder(), createTransition(taskDefaultDisplay))
+
+ assertNotNull(result, "Should handle request")
+ assertThat(result.hierarchyOps?.size).isEqualTo(2)
+ // Add desktop wallpaper activity
+ result.assertPendingIntentAt(0, desktopWallpaperIntent)
+ // Bring new task to front
+ result.assertReorderAt(1, taskDefaultDisplay, toTop = true)
+ }
+
+ @Test
+ fun handleRequest_freeformTask_alreadyInDesktop_noOverrideDensity_noConfigDensityChange() {
+ whenever(DesktopModeStatus.useDesktopOverrideDensity()).thenReturn(false)
+
+ val freeformTask1 = setUpFreeformTask()
+ markTaskVisible(freeformTask1)
+
+ val freeformTask2 = createFreeformTask()
+ val result =
+ controller.handleRequest(
+ freeformTask2.token.asBinder(),
+ createTransition(freeformTask2),
+ )
+ assertFalse(result.anyDensityConfigChange(freeformTask2.token))
+ }
+
+ @Test
+ fun handleRequest_freeformTask_alreadyInDesktop_overrideDensity_hasConfigDensityChange() {
+ whenever(DesktopModeStatus.useDesktopOverrideDensity()).thenReturn(true)
+
+ val freeformTask1 = setUpFreeformTask()
+ markTaskVisible(freeformTask1)
+
+ val freeformTask2 = createFreeformTask()
+ val result =
+ controller.handleRequest(
+ freeformTask2.token.asBinder(),
+ createTransition(freeformTask2),
+ )
+ assertTrue(result.anyDensityConfigChange(freeformTask2.token))
+ }
+
+ @Test
+ fun handleRequest_freeformTask_keyguardLocked_returnNull() {
+ whenever(keyguardManager.isKeyguardLocked).thenReturn(true)
+ val freeformTask = createFreeformTask(displayId = DEFAULT_DISPLAY)
+
+ val result = controller.handleRequest(Binder(), createTransition(freeformTask))
+
+ assertNull(result, "Should NOT handle request")
+ }
+
+ @Test
+ fun handleRequest_notOpenOrToFrontTransition_returnNull() {
+ val task =
+ TestRunningTaskInfoBuilder()
+ .setActivityType(ACTIVITY_TYPE_STANDARD)
+ .setWindowingMode(WINDOWING_MODE_FULLSCREEN)
+ .build()
+ val transition = createTransition(task = task, type = TRANSIT_CLOSE)
+ val result = controller.handleRequest(Binder(), transition)
+ assertThat(result).isNull()
+ }
+
+ @Test
+ fun handleRequest_noTriggerTask_returnNull() {
+ assertThat(controller.handleRequest(Binder(), createTransition(task = null))).isNull()
+ }
+
+ @Test
+ fun handleRequest_triggerTaskNotStandard_returnNull() {
+ val task = TestRunningTaskInfoBuilder().setActivityType(ACTIVITY_TYPE_HOME).build()
+ assertThat(controller.handleRequest(Binder(), createTransition(task))).isNull()
+ }
+
+ @Test
+ fun handleRequest_triggerTaskNotFullscreenOrFreeform_returnNull() {
+ val task =
+ TestRunningTaskInfoBuilder()
+ .setActivityType(ACTIVITY_TYPE_STANDARD)
+ .setWindowingMode(WINDOWING_MODE_MULTI_WINDOW)
+ .build()
+ assertThat(controller.handleRequest(Binder(), createTransition(task))).isNull()
+ }
+
+ @Test
+ fun handleRequest_recentsAnimationRunning_returnNull() {
+ // Set up a visible freeform task so a fullscreen task should be converted to freeform
+ val freeformTask = setUpFreeformTask()
+ markTaskVisible(freeformTask)
+
+ // Mark recents animation running
+ recentsTransitionStateListener.onTransitionStateChanged(TRANSITION_STATE_ANIMATING)
+
+ // Open a fullscreen task, check that it does not result in a WCT with changes to it
+ val fullscreenTask = createFullscreenTask()
+ assertThat(controller.handleRequest(Binder(), createTransition(fullscreenTask))).isNull()
+ }
+
+ @Test
+ fun handleRequest_recentsAnimationRunning_relaunchActiveTask_taskBecomesUndefined() {
+ // Set up a visible freeform task
+ val freeformTask = setUpFreeformTask()
+ markTaskVisible(freeformTask)
+
+ // Mark recents animation running
+ recentsTransitionStateListener.onTransitionStateChanged(TRANSITION_STATE_ANIMATING)
+
+ // Should become undefined as the TDA is set to fullscreen. It will inherit from the TDA.
+ val result = controller.handleRequest(Binder(), createTransition(freeformTask))
+ assertThat(result?.changes?.get(freeformTask.token.asBinder())?.windowingMode)
+ .isEqualTo(WINDOWING_MODE_UNDEFINED)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
+ fun handleRequest_topActivityTransparentWithoutDisplay_returnSwitchToFreeformWCT() {
+ val freeformTask = setUpFreeformTask()
+ markTaskVisible(freeformTask)
+
+ val task =
+ setUpFullscreenTask().apply {
+ isActivityStackTransparent = true
+ isTopActivityNoDisplay = true
+ numActivities = 1
+ }
+
+ val result = controller.handleRequest(Binder(), createTransition(task))
+ assertThat(result?.changes?.get(task.token.asBinder())?.windowingMode)
+ .isEqualTo(WINDOWING_MODE_FREEFORM)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
+ fun handleRequest_topActivityTransparentWithDisplay_returnSwitchToFullscreenWCT() {
+ val freeformTask = setUpFreeformTask()
+ markTaskVisible(freeformTask)
+
+ val task =
+ setUpFreeformTask().apply {
+ isActivityStackTransparent = true
+ isTopActivityNoDisplay = false
+ numActivities = 1
+ }
+
+ val result = controller.handleRequest(Binder(), createTransition(task))
+ assertThat(result?.changes?.get(task.token.asBinder())?.windowingMode)
+ .isEqualTo(WINDOWING_MODE_UNDEFINED) // inherited FULLSCREEN
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
+ fun handleRequest_systemUIActivityWithDisplay_returnSwitchToFullscreenWCT() {
+ val freeformTask = setUpFreeformTask()
+ markTaskVisible(freeformTask)
+
+ // Set task as systemUI package
+ val systemUIPackageName =
+ context.resources.getString(com.android.internal.R.string.config_systemUi)
+ val baseComponent = ComponentName(systemUIPackageName, /* class */ "")
+ val task =
+ setUpFreeformTask().apply {
+ baseActivity = baseComponent
+ isTopActivityNoDisplay = false
+ }
+
+ val result = controller.handleRequest(Binder(), createTransition(task))
+ assertThat(result?.changes?.get(task.token.asBinder())?.windowingMode)
+ .isEqualTo(WINDOWING_MODE_UNDEFINED) // inherited FULLSCREEN
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
+ fun handleRequest_systemUIActivityWithoutDisplay_returnSwitchToFreeformWCT() {
+ val freeformTask = setUpFreeformTask()
+ markTaskVisible(freeformTask)
+
+ // Set task as systemUI package
+ val systemUIPackageName =
+ context.resources.getString(com.android.internal.R.string.config_systemUi)
+ val baseComponent = ComponentName(systemUIPackageName, /* class */ "")
+ val task =
+ setUpFullscreenTask().apply {
+ baseActivity = baseComponent
+ isTopActivityNoDisplay = true
+ }
+
+ val result = controller.handleRequest(Binder(), createTransition(task))
+ assertThat(result?.changes?.get(task.token.asBinder())?.windowingMode)
+ .isEqualTo(WINDOWING_MODE_FREEFORM)
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ fun handleRequest_backTransition_singleTaskNoToken_noWallpaper_doesNotHandle() {
+ val task = setUpFreeformTask()
+
+ val result =
+ controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_TO_BACK))
+
+ assertNull(result, "Should not handle request")
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ fun handleRequest_backTransition_singleTaskNoToken_withWallpaper_removesTask() {
+ val task = setUpFreeformTask()
+
+ val result =
+ controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_TO_BACK))
+
+ assertNull(result, "Should not handle request")
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ fun handleRequest_backTransition_singleTaskNoToken_withWallpaper_notInDesktop_doesNotHandle() {
+ val task = setUpFreeformTask()
+ markTaskHidden(task)
+
+ val result =
+ controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_TO_BACK))
+
+ assertNull(result, "Should not handle request")
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ fun handleRequest_backTransition_singleTaskNoToken_doesNotHandle() {
+ val task = setUpFreeformTask()
+
+ val result =
+ controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_TO_BACK))
+
+ assertNull(result, "Should not handle request")
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ fun handleRequest_backTransition_singleTaskWithToken_noWallpaper_doesNotHandle() {
+ val task = setUpFreeformTask()
+
+ taskRepository.wallpaperActivityToken = MockToken().token()
+ val result =
+ controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_TO_BACK))
+
+ assertNull(result, "Should not handle request")
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ fun handleRequest_backTransition_singleTaskWithToken_removesWallpaper() {
+ val task = setUpFreeformTask()
+ val wallpaperToken = MockToken().token()
+
+ taskRepository.wallpaperActivityToken = wallpaperToken
+ val result =
+ controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_TO_BACK))
+
+ // Should create remove wallpaper transaction
+ assertNotNull(result, "Should handle request").assertRemoveAt(index = 0, wallpaperToken)
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ fun handleRequest_backTransition_multipleTasks_noWallpaper_doesNotHandle() {
+ val task1 = setUpFreeformTask()
+ setUpFreeformTask()
+
+ taskRepository.wallpaperActivityToken = MockToken().token()
+ val result =
+ controller.handleRequest(Binder(), createTransition(task1, type = TRANSIT_TO_BACK))
+
+ assertNull(result, "Should not handle request")
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ fun handleRequest_backTransition_multipleTasks_doesNotHandle() {
+ val task1 = setUpFreeformTask()
+ setUpFreeformTask()
+
+ taskRepository.wallpaperActivityToken = MockToken().token()
+ val result =
+ controller.handleRequest(Binder(), createTransition(task1, type = TRANSIT_TO_BACK))
+
+ assertNull(result, "Should not handle request")
+ }
+
+ @Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION,
)
+ fun handleRequest_backTransition_multipleTasksSingleNonClosing_removesWallpaperAndTask() {
+ val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
+ val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
+ val wallpaperToken = MockToken().token()
+
+ taskRepository.wallpaperActivityToken = wallpaperToken
+ taskRepository.addClosingTask(displayId = DEFAULT_DISPLAY, taskId = task2.taskId)
+ val result =
+ controller.handleRequest(Binder(), createTransition(task1, type = TRANSIT_TO_BACK))
+
+ // Should create remove wallpaper transaction
+ assertNotNull(result, "Should handle request").assertRemoveAt(index = 0, wallpaperToken)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ fun handleRequest_backTransition_multipleTasksSingleNonMinimized_removesWallpaperAndTask() {
+ val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
+ val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
+ val wallpaperToken = MockToken().token()
+
+ taskRepository.wallpaperActivityToken = wallpaperToken
+ taskRepository.minimizeTask(displayId = DEFAULT_DISPLAY, taskId = task2.taskId)
+ val result =
+ controller.handleRequest(Binder(), createTransition(task1, type = TRANSIT_TO_BACK))
+
+ // Should create remove wallpaper transaction
+ assertNotNull(result, "Should handle request").assertRemoveAt(index = 0, wallpaperToken)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ fun handleRequest_backTransition_nonMinimizadTask_withWallpaper_removesWallpaper() {
+ val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
+ val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
+ val wallpaperToken = MockToken().token()
+
+ taskRepository.wallpaperActivityToken = wallpaperToken
+ taskRepository.minimizeTask(displayId = DEFAULT_DISPLAY, taskId = task2.taskId)
+ // Task is being minimized so mark it as not visible.
+ taskRepository.updateTask(displayId = DEFAULT_DISPLAY, task2.taskId, isVisible = false)
+ val result =
+ controller.handleRequest(Binder(), createTransition(task2, type = TRANSIT_TO_BACK))
+
+ assertNull(result, "Should not handle request")
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ fun handleRequest_closeTransition_singleTaskNoToken_noWallpaper_doesNotHandle() {
+ val task = setUpFreeformTask()
+
+ val result =
+ controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_CLOSE))
+
+ assertNull(result, "Should not handle request")
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ fun handleRequest_closeTransition_singleTaskNoToken_doesNotHandle() {
+ val task = setUpFreeformTask()
+
+ val result =
+ controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_CLOSE))
+
+ assertNull(result, "Should not handle request")
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ fun handleRequest_closeTransition_singleTaskWithToken_noWallpaper_doesNotHandle() {
+ val task = setUpFreeformTask()
+
+ taskRepository.wallpaperActivityToken = MockToken().token()
+ val result =
+ controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_CLOSE))
+
+ assertNull(result, "Should not handle request")
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ fun handleRequest_closeTransition_singleTaskWithToken_withWallpaper_removesWallpaper() {
+ val task = setUpFreeformTask()
+ val wallpaperToken = MockToken().token()
+
+ taskRepository.wallpaperActivityToken = wallpaperToken
+ val result =
+ controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_CLOSE))
+
+ // Should create remove wallpaper transaction
+ assertNotNull(result, "Should handle request").assertRemoveAt(index = 0, wallpaperToken)
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ fun handleRequest_closeTransition_multipleTasks_noWallpaper_doesNotHandle() {
+ val task1 = setUpFreeformTask()
+ setUpFreeformTask()
+
+ taskRepository.wallpaperActivityToken = MockToken().token()
+ val result =
+ controller.handleRequest(Binder(), createTransition(task1, type = TRANSIT_CLOSE))
+
+ assertNull(result, "Should not handle request")
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ fun handleRequest_closeTransition_multipleTasksFlagEnabled_doesNotHandle() {
+ val task1 = setUpFreeformTask()
+ setUpFreeformTask()
+
+ taskRepository.wallpaperActivityToken = MockToken().token()
+ val result =
+ controller.handleRequest(Binder(), createTransition(task1, type = TRANSIT_CLOSE))
+
+ assertNull(result, "Should not handle request")
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ fun handleRequest_closeTransition_multipleTasksSingleNonClosing_removesWallpaper() {
+ val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
+ val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
+ val wallpaperToken = MockToken().token()
+
+ taskRepository.wallpaperActivityToken = wallpaperToken
+ taskRepository.addClosingTask(displayId = DEFAULT_DISPLAY, taskId = task2.taskId)
+ val result =
+ controller.handleRequest(Binder(), createTransition(task1, type = TRANSIT_CLOSE))
+
+ // Should create remove wallpaper transaction
+ assertNotNull(result, "Should handle request").assertRemoveAt(index = 0, wallpaperToken)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ fun handleRequest_closeTransition_multipleTasksSingleNonMinimized_removesWallpaper() {
+ val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
+ val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
+ val wallpaperToken = MockToken().token()
+
+ taskRepository.wallpaperActivityToken = wallpaperToken
+ taskRepository.minimizeTask(displayId = DEFAULT_DISPLAY, taskId = task2.taskId)
+ val result =
+ controller.handleRequest(Binder(), createTransition(task1, type = TRANSIT_CLOSE))
+
+ // Should create remove wallpaper transaction
+ assertNotNull(result, "Should handle request").assertRemoveAt(index = 0, wallpaperToken)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ fun handleRequest_closeTransition_minimizadTask_withWallpaper_removesWallpaper() {
+ val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
+ val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
+ val wallpaperToken = MockToken().token()
+
+ taskRepository.wallpaperActivityToken = wallpaperToken
+ taskRepository.minimizeTask(displayId = DEFAULT_DISPLAY, taskId = task2.taskId)
+ // Task is being minimized so mark it as not visible.
+ taskRepository.updateTask(displayId = DEFAULT_DISPLAY, task2.taskId, isVisible = false)
+ val result =
+ controller.handleRequest(Binder(), createTransition(task2, type = TRANSIT_TO_BACK))
+
+ assertNull(result, "Should not handle request")
+ }
- controller.minimizeTask(task)
-
- verify(freeformTaskTransitionStarter).startPipTransition(any())
- verify(freeformTaskTransitionStarter, never()).startMinimizedModeTransition(any())
- }
-
- @Test
- fun onDesktopWindowMinimize_pipTask_autoEnterDisabled_startMinimizeTransition() {
- val task = setUpPipTask(autoEnterEnabled = false)
- whenever(freeformTaskTransitionStarter.startMinimizedModeTransition(any()))
- .thenReturn(Binder())
-
- controller.minimizeTask(task)
-
- verify(freeformTaskTransitionStarter).startMinimizedModeTransition(any())
- verify(freeformTaskTransitionStarter, never()).startPipTransition(any())
- }
-
- @Test
- fun onDesktopWindowMinimize_singleActiveTask_noWallpaperActivityToken_doesntRemoveWallpaper() {
- val task = setUpFreeformTask(active = true)
- val transition = Binder()
- whenever(freeformTaskTransitionStarter.startMinimizedModeTransition(any()))
- .thenReturn(transition)
-
- controller.minimizeTask(task)
-
- val captor = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
- verify(freeformTaskTransitionStarter).startMinimizedModeTransition(captor.capture())
- captor.value.hierarchyOps.none { hop ->
- hop.type == HIERARCHY_OP_TYPE_REMOVE_TASK
- }
- }
-
- @Test
- fun onTaskMinimize_singleActiveTask_hasWallpaperActivityToken_removesWallpaper() {
- val task = setUpFreeformTask()
- val transition = Binder()
- whenever(freeformTaskTransitionStarter.startMinimizedModeTransition(any()))
- .thenReturn(transition)
- val wallpaperToken = MockToken().token()
- taskRepository.wallpaperActivityToken = wallpaperToken
-
- // The only active task is being minimized.
- controller.minimizeTask(task)
-
- val captor = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
- verify(freeformTaskTransitionStarter).startMinimizedModeTransition(captor.capture())
- // Adds remove wallpaper operation
- captor.value.assertRemoveAt(index = 0, wallpaperToken)
- }
-
- @Test
- fun onDesktopWindowMinimize_singleActiveTask_alreadyMinimized_doesntRemoveWallpaper() {
- val task = setUpFreeformTask()
- val transition = Binder()
- whenever(freeformTaskTransitionStarter.startMinimizedModeTransition(any()))
- .thenReturn(transition)
- val wallpaperToken = MockToken().token()
- taskRepository.wallpaperActivityToken = wallpaperToken
- taskRepository.minimizeTask(DEFAULT_DISPLAY, task.taskId)
-
- // The only active task is already minimized.
- controller.minimizeTask(task)
-
- val captor = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
- verify(freeformTaskTransitionStarter).startMinimizedModeTransition(captor.capture())
- captor.value.hierarchyOps.none { hop ->
- hop.type == HIERARCHY_OP_TYPE_REMOVE_TASK && hop.container == wallpaperToken.asBinder()
- }
- }
-
- @Test
- fun onDesktopWindowMinimize_multipleActiveTasks_doesntRemoveWallpaper() {
- val task1 = setUpFreeformTask(active = true)
- setUpFreeformTask(active = true)
- val transition = Binder()
- whenever(freeformTaskTransitionStarter.startMinimizedModeTransition(any()))
- .thenReturn(transition)
- val wallpaperToken = MockToken().token()
- taskRepository.wallpaperActivityToken = wallpaperToken
-
- controller.minimizeTask(task1)
-
- val captor = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
- verify(freeformTaskTransitionStarter).startMinimizedModeTransition(captor.capture())
- captor.value.hierarchyOps.none { hop ->
- hop.type == HIERARCHY_OP_TYPE_REMOVE_TASK && hop.container == wallpaperToken.asBinder()
- }
- }
-
- @Test
- fun onDesktopWindowMinimize_multipleActiveTasks_minimizesTheOnlyVisibleTask_removesWallpaper() {
- val task1 = setUpFreeformTask(active = true)
- val task2 = setUpFreeformTask(active = true)
- val transition = Binder()
- whenever(freeformTaskTransitionStarter.startMinimizedModeTransition(any()))
- .thenReturn(transition)
- val wallpaperToken = MockToken().token()
- taskRepository.wallpaperActivityToken = wallpaperToken
- taskRepository.minimizeTask(DEFAULT_DISPLAY, task2.taskId)
-
- // task1 is the only visible task as task2 is minimized.
- controller.minimizeTask(task1)
- // Adds remove wallpaper operation
- val captor = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
- verify(freeformTaskTransitionStarter).startMinimizedModeTransition(captor.capture())
- // Adds remove wallpaper operation
- captor.value.assertRemoveAt(index = 0, wallpaperToken)
- }
-
- @Test
- fun onDesktopWindowMinimize_triesToExitImmersive() {
- val task = setUpFreeformTask()
- val transition = Binder()
- whenever(freeformTaskTransitionStarter.startMinimizedModeTransition(any()))
- .thenReturn(transition)
-
- controller.minimizeTask(task)
-
- verify(mMockDesktopImmersiveController).exitImmersiveIfApplicable(any(), eq(task), any())
- }
-
- @Test
- fun onDesktopWindowMinimize_invokesImmersiveTransitionStartCallback() {
- val task = setUpFreeformTask()
- val transition = Binder()
- val runOnTransit = RunOnStartTransitionCallback()
- whenever(freeformTaskTransitionStarter.startMinimizedModeTransition(any()))
- .thenReturn(transition)
- whenever(mMockDesktopImmersiveController.exitImmersiveIfApplicable(any(), eq(task), any()))
- .thenReturn(
- ExitResult.Exit(
- exitingTask = task.taskId,
- runOnTransitionStart = runOnTransit,
- ))
-
- controller.minimizeTask(task)
-
- assertThat(runOnTransit.invocations).isEqualTo(1)
- assertThat(runOnTransit.lastInvoked).isEqualTo(transition)
- }
-
- @Test
- fun handleRequest_fullscreenTask_freeformVisible_returnSwitchToFreeformWCT() {
- val homeTask = setUpHomeTask()
- val freeformTask = setUpFreeformTask()
- markTaskVisible(freeformTask)
- val fullscreenTask = createFullscreenTask()
-
- val wct = controller.handleRequest(Binder(), createTransition(fullscreenTask))
-
- assertNotNull(wct, "should handle request")
- assertThat(wct.changes[fullscreenTask.token.asBinder()]?.windowingMode)
- .isEqualTo(WINDOWING_MODE_FREEFORM)
-
- assertThat(wct.hierarchyOps).hasSize(1)
- }
-
- @Test
- fun handleRequest_fullscreenTaskWithTaskOnHome_freeformVisible_returnSwitchToFreeformWCT() {
- val homeTask = setUpHomeTask()
- val freeformTask = setUpFreeformTask()
- markTaskVisible(freeformTask)
- val fullscreenTask = createFullscreenTask()
- fullscreenTask.baseIntent.setFlags(Intent.FLAG_ACTIVITY_TASK_ON_HOME)
-
- val wct = controller.handleRequest(Binder(), createTransition(fullscreenTask))
-
- assertNotNull(wct, "should handle request")
- assertThat(wct.changes[fullscreenTask.token.asBinder()]?.windowingMode)
- .isEqualTo(WINDOWING_MODE_FREEFORM)
-
- // There are 5 hops that are happening in this case:
- // 1. Moving the fullscreen task to top as we add moveToDesktop() changes
- // 2. Bringing home task to front
- // 3. Pending intent for the wallpaper
- // 4. Bringing the existing freeform task to top
- // 5. Bringing the fullscreen task back at the top
- assertThat(wct.hierarchyOps).hasSize(5)
- wct.assertReorderAt(1, homeTask, toTop = true)
- wct.assertReorderAt(4, fullscreenTask, toTop = true)
- }
-
- @Test
- fun handleRequest_fullscreenTaskToFreeform_underTaskLimit_dontMinimize() {
- val freeformTask = setUpFreeformTask()
- markTaskVisible(freeformTask)
- val fullscreenTask = createFullscreenTask()
-
- val wct = controller.handleRequest(Binder(), createTransition(fullscreenTask))
-
- // Make sure we only reorder the new task to top (we don't reorder the old task to bottom)
- assertThat(wct?.hierarchyOps?.size).isEqualTo(1)
- wct!!.assertReorderAt(0, fullscreenTask, toTop = true)
- }
-
- @Test
- fun handleRequest_fullscreenTaskToFreeform_bringsTasksOverLimit_otherTaskIsMinimized() {
- val freeformTasks = (1..MAX_TASK_LIMIT).map { _ -> setUpFreeformTask() }
- freeformTasks.forEach { markTaskVisible(it) }
- val fullscreenTask = createFullscreenTask()
-
- val wct = controller.handleRequest(Binder(), createTransition(fullscreenTask))
-
- // Make sure we reorder the new task to top, and the back task to the bottom
- assertThat(wct!!.hierarchyOps.size).isEqualTo(2)
- wct.assertReorderAt(0, fullscreenTask, toTop = true)
- wct.assertReorderAt(1, freeformTasks[0], toTop = false)
- }
-
- @Test
- fun handleRequest_fullscreenTaskWithTaskOnHome_bringsTasksOverLimit_otherTaskIsMinimized() {
- val freeformTasks = (1..MAX_TASK_LIMIT).map { _ -> setUpFreeformTask() }
- freeformTasks.forEach { markTaskVisible(it) }
- val fullscreenTask = createFullscreenTask()
- fullscreenTask.baseIntent.setFlags(Intent.FLAG_ACTIVITY_TASK_ON_HOME)
-
- val wct = controller.handleRequest(Binder(), createTransition(fullscreenTask))
-
- // Make sure we reorder the new task to top, and the back task to the bottom
- assertThat(wct!!.hierarchyOps.size).isEqualTo(9)
- wct.assertReorderAt(0, fullscreenTask, toTop = true)
- wct.assertReorderAt(8, freeformTasks[0], toTop = false)
- }
-
- @Test
- fun handleRequest_fullscreenTaskWithTaskOnHome_beyondLimit_existingAndNewTasksAreMinimized() {
- val minimizedTask = setUpFreeformTask()
- taskRepository.minimizeTask(displayId = DEFAULT_DISPLAY, taskId = minimizedTask.taskId)
- val freeformTasks = (1..MAX_TASK_LIMIT).map { _ -> setUpFreeformTask() }
- freeformTasks.forEach { markTaskVisible(it) }
- val homeTask = setUpHomeTask()
- val fullscreenTask = createFullscreenTask()
- fullscreenTask.baseIntent.setFlags(Intent.FLAG_ACTIVITY_TASK_ON_HOME)
-
- val wct = controller.handleRequest(Binder(), createTransition(fullscreenTask))
-
- assertThat(wct!!.hierarchyOps.size).isEqualTo(10)
- wct.assertReorderAt(0, fullscreenTask, toTop = true)
- // Make sure we reorder the home task to the top, desktop tasks to top of them and minimized
- // task is under the home task.
- wct.assertReorderAt(1, homeTask, toTop = true)
- wct.assertReorderAt(9, freeformTasks[0], toTop = false)
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- fun handleRequest_fullscreenTask_noTasks_enforceDesktop_freeformDisplay_returnFreeformWCT() {
- whenever(DesktopModeStatus.enterDesktopByDefaultOnFreeformDisplay(context)).thenReturn(true)
- val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!!
- tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FREEFORM
-
- val fullscreenTask = createFullscreenTask()
- val wct = controller.handleRequest(Binder(), createTransition(fullscreenTask))
-
- assertNotNull(wct, "should handle request")
- assertThat(wct.changes[fullscreenTask.token.asBinder()]?.windowingMode)
- .isEqualTo(WINDOWING_MODE_UNDEFINED)
- assertThat(wct.hierarchyOps).hasSize(3)
- // There are 3 hops that are happening in this case:
- // 1. Moving the fullscreen task to top as we add moveToDesktop() changes
- // 2. Pending intent for the wallpaper
- // 3. Bringing the fullscreen task back at the top
- wct.assertPendingIntentAt(1, desktopWallpaperIntent)
- wct.assertReorderAt(2, fullscreenTask, toTop = true)
- }
-
- @Test
- fun handleRequest_fullscreenTask_noTasks_enforceDesktop_fullscreenDisplay_returnNull() {
- whenever(DesktopModeStatus.enterDesktopByDefaultOnFreeformDisplay(context)).thenReturn(true)
- val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!!
- tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FULLSCREEN
-
- val fullscreenTask = createFullscreenTask()
- val wct = controller.handleRequest(Binder(), createTransition(fullscreenTask))
-
- assertThat(wct).isNull()
- }
-
- @Test
- fun handleRequest_fullscreenTask_freeformNotVisible_returnNull() {
- val freeformTask = setUpFreeformTask()
- markTaskHidden(freeformTask)
- val fullscreenTask = createFullscreenTask()
- assertThat(controller.handleRequest(Binder(), createTransition(fullscreenTask))).isNull()
- }
-
- @Test
- fun handleRequest_fullscreenTask_noOtherTasks_returnNull() {
- val fullscreenTask = createFullscreenTask()
- assertThat(controller.handleRequest(Binder(), createTransition(fullscreenTask))).isNull()
- }
-
- @Test
- fun handleRequest_fullscreenTask_freeformTaskOnOtherDisplay_returnNull() {
- val fullscreenTaskDefaultDisplay = createFullscreenTask(displayId = DEFAULT_DISPLAY)
- createFreeformTask(displayId = SECOND_DISPLAY)
-
- val result = controller.handleRequest(Binder(), createTransition(fullscreenTaskDefaultDisplay))
- assertThat(result).isNull()
- }
-
- @Test
- fun handleRequest_freeformTask_freeformVisible_aboveTaskLimit_minimize() {
- val freeformTasks = (1..MAX_TASK_LIMIT).map { _ -> setUpFreeformTask() }
- freeformTasks.forEach { markTaskVisible(it) }
- val newFreeformTask = createFreeformTask()
-
- val wct = controller.handleRequest(Binder(), createTransition(newFreeformTask, TRANSIT_OPEN))
-
- assertThat(wct?.hierarchyOps?.size).isEqualTo(1)
- wct!!.assertReorderAt(0, freeformTasks[0], toTop = false) // Reorder to the bottom
- }
-
- @Test
- fun handleRequest_freeformTask_relaunchActiveTask_taskBecomesUndefined() {
- val freeformTask = setUpFreeformTask()
- markTaskHidden(freeformTask)
-
- val wct =
- controller.handleRequest(Binder(), createTransition(freeformTask))
-
- // Should become undefined as the TDA is set to fullscreen. It will inherit from the TDA.
- assertNotNull(wct, "should handle request")
- assertThat(wct.changes[freeformTask.token.asBinder()]?.windowingMode)
- .isEqualTo(WINDOWING_MODE_UNDEFINED)
- }
-
- @Test
- fun handleRequest_freeformTask_relaunchTask_enforceDesktop_freeformDisplay_noWinModeChange() {
- whenever(DesktopModeStatus.enterDesktopByDefaultOnFreeformDisplay(context)).thenReturn(true)
- val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!!
- tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FREEFORM
-
- val freeformTask = setUpFreeformTask()
- markTaskHidden(freeformTask)
- val wct = controller.handleRequest(Binder(), createTransition(freeformTask))
-
- assertNotNull(wct, "should handle request")
- assertFalse(wct.anyWindowingModeChange(freeformTask.token))
- }
-
- @Test
- fun handleRequest_freeformTask_relaunchTask_enforceDesktop_fullscreenDisplay_becomesUndefined() {
- whenever(DesktopModeStatus.enterDesktopByDefaultOnFreeformDisplay(context)).thenReturn(true)
- val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!!
- tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FULLSCREEN
-
- val freeformTask = setUpFreeformTask()
- markTaskHidden(freeformTask)
- val wct = controller.handleRequest(Binder(), createTransition(freeformTask))
-
- assertNotNull(wct, "should handle request")
- assertThat(wct.changes[freeformTask.token.asBinder()]?.windowingMode)
- .isEqualTo(WINDOWING_MODE_UNDEFINED)
- }
-
- @Test
- @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- fun handleRequest_freeformTask_desktopWallpaperDisabled_freeformNotVisible_reorderedToTop() {
- val freeformTask1 = setUpFreeformTask()
- val freeformTask2 = createFreeformTask()
-
- markTaskHidden(freeformTask1)
- val result =
- controller.handleRequest(Binder(), createTransition(freeformTask2, type = TRANSIT_TO_FRONT))
-
- assertNotNull(result, "Should handle request")
- assertThat(result.hierarchyOps?.size).isEqualTo(2)
- result.assertReorderAt(1, freeformTask2, toTop = true)
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- fun handleRequest_freeformTask_desktopWallpaperEnabled_freeformNotVisible_reorderedToTop() {
- val freeformTask1 = setUpFreeformTask()
- val freeformTask2 = createFreeformTask()
-
- markTaskHidden(freeformTask1)
- val result =
- controller.handleRequest(Binder(), createTransition(freeformTask2, type = TRANSIT_TO_FRONT))
-
- assertNotNull(result, "Should handle request")
- assertThat(result.hierarchyOps?.size).isEqualTo(3)
- // Add desktop wallpaper activity
- result.assertPendingIntentAt(0, desktopWallpaperIntent)
- // Bring active desktop tasks to front
- result.assertReorderAt(1, freeformTask1, toTop = true)
- // Bring new task to front
- result.assertReorderAt(2, freeformTask2, toTop = true)
- }
-
- @Test
- @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- fun handleRequest_freeformTask_desktopWallpaperDisabled_noOtherTasks_reorderedToTop() {
- val task = createFreeformTask()
- val result = controller.handleRequest(Binder(), createTransition(task))
-
- assertNotNull(result, "Should handle request")
- assertThat(result.hierarchyOps?.size).isEqualTo(1)
- result.assertReorderAt(0, task, toTop = true)
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- fun handleRequest_freeformTask_desktopWallpaperEnabled_noOtherTasks_reorderedToTop() {
- val task = createFreeformTask()
- val result = controller.handleRequest(Binder(), createTransition(task))
-
- assertNotNull(result, "Should handle request")
- assertThat(result.hierarchyOps?.size).isEqualTo(2)
- // Add desktop wallpaper activity
- result.assertPendingIntentAt(0, desktopWallpaperIntent)
- // Bring new task to front
- result.assertReorderAt(1, task, toTop = true)
- }
-
- @Test
- @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- fun handleRequest_freeformTask_dskWallpaperDisabled_freeformOnOtherDisplayOnly_reorderedToTop() {
- val taskDefaultDisplay = createFreeformTask(displayId = DEFAULT_DISPLAY)
- // Second display task
- createFreeformTask(displayId = SECOND_DISPLAY)
-
- val result = controller.handleRequest(Binder(), createTransition(taskDefaultDisplay))
-
- assertNotNull(result, "Should handle request")
- assertThat(result.hierarchyOps?.size).isEqualTo(1)
- result.assertReorderAt(0, taskDefaultDisplay, toTop = true)
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- fun handleRequest_freeformTask_dskWallpaperEnabled_freeformOnOtherDisplayOnly_reorderedToTop() {
- val taskDefaultDisplay = createFreeformTask(displayId = DEFAULT_DISPLAY)
- // Second display task
- createFreeformTask(displayId = SECOND_DISPLAY)
-
- val result = controller.handleRequest(Binder(), createTransition(taskDefaultDisplay))
-
- assertNotNull(result, "Should handle request")
- assertThat(result.hierarchyOps?.size).isEqualTo(2)
- // Add desktop wallpaper activity
- result.assertPendingIntentAt(0, desktopWallpaperIntent)
- // Bring new task to front
- result.assertReorderAt(1, taskDefaultDisplay, toTop = true)
- }
-
- @Test
- fun handleRequest_freeformTask_alreadyInDesktop_noOverrideDensity_noConfigDensityChange() {
- whenever(DesktopModeStatus.useDesktopOverrideDensity()).thenReturn(false)
-
- val freeformTask1 = setUpFreeformTask()
- markTaskVisible(freeformTask1)
-
- val freeformTask2 = createFreeformTask()
- val result =
- controller.handleRequest(freeformTask2.token.asBinder(), createTransition(freeformTask2))
- assertFalse(result.anyDensityConfigChange(freeformTask2.token))
- }
-
- @Test
- fun handleRequest_freeformTask_alreadyInDesktop_overrideDensity_hasConfigDensityChange() {
- whenever(DesktopModeStatus.useDesktopOverrideDensity()).thenReturn(true)
-
- val freeformTask1 = setUpFreeformTask()
- markTaskVisible(freeformTask1)
-
- val freeformTask2 = createFreeformTask()
- val result =
- controller.handleRequest(freeformTask2.token.asBinder(), createTransition(freeformTask2))
- assertTrue(result.anyDensityConfigChange(freeformTask2.token))
- }
-
- @Test
- fun handleRequest_freeformTask_keyguardLocked_returnNull() {
- whenever(keyguardManager.isKeyguardLocked).thenReturn(true)
- val freeformTask = createFreeformTask(displayId = DEFAULT_DISPLAY)
-
- val result = controller.handleRequest(Binder(), createTransition(freeformTask))
-
- assertNull(result, "Should NOT handle request")
- }
-
- @Test
- fun handleRequest_notOpenOrToFrontTransition_returnNull() {
- val task =
- TestRunningTaskInfoBuilder()
- .setActivityType(ACTIVITY_TYPE_STANDARD)
- .setWindowingMode(WINDOWING_MODE_FULLSCREEN)
- .build()
- val transition = createTransition(task = task, type = TRANSIT_CLOSE)
- val result = controller.handleRequest(Binder(), transition)
- assertThat(result).isNull()
- }
-
- @Test
- fun handleRequest_noTriggerTask_returnNull() {
- assertThat(controller.handleRequest(Binder(), createTransition(task = null))).isNull()
- }
-
- @Test
- fun handleRequest_triggerTaskNotStandard_returnNull() {
- val task = TestRunningTaskInfoBuilder().setActivityType(ACTIVITY_TYPE_HOME).build()
- assertThat(controller.handleRequest(Binder(), createTransition(task))).isNull()
- }
-
- @Test
- fun handleRequest_triggerTaskNotFullscreenOrFreeform_returnNull() {
- val task =
- TestRunningTaskInfoBuilder()
- .setActivityType(ACTIVITY_TYPE_STANDARD)
- .setWindowingMode(WINDOWING_MODE_MULTI_WINDOW)
- .build()
- assertThat(controller.handleRequest(Binder(), createTransition(task))).isNull()
- }
-
- @Test
- fun handleRequest_recentsAnimationRunning_returnNull() {
- // Set up a visible freeform task so a fullscreen task should be converted to freeform
- val freeformTask = setUpFreeformTask()
- markTaskVisible(freeformTask)
-
- // Mark recents animation running
- recentsTransitionStateListener.onTransitionStateChanged(TRANSITION_STATE_ANIMATING)
-
- // Open a fullscreen task, check that it does not result in a WCT with changes to it
- val fullscreenTask = createFullscreenTask()
- assertThat(controller.handleRequest(Binder(), createTransition(fullscreenTask))).isNull()
- }
-
- @Test
- fun handleRequest_recentsAnimationRunning_relaunchActiveTask_taskBecomesUndefined() {
- // Set up a visible freeform task
- val freeformTask = setUpFreeformTask()
- markTaskVisible(freeformTask)
-
- // Mark recents animation running
- recentsTransitionStateListener.onTransitionStateChanged(TRANSITION_STATE_ANIMATING)
-
- // Should become undefined as the TDA is set to fullscreen. It will inherit from the TDA.
- val result = controller.handleRequest(Binder(), createTransition(freeformTask))
- assertThat(result?.changes?.get(freeformTask.token.asBinder())?.windowingMode)
- .isEqualTo(WINDOWING_MODE_UNDEFINED)
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
- fun handleRequest_topActivityTransparentWithoutDisplay_returnSwitchToFreeformWCT() {
- val freeformTask = setUpFreeformTask()
- markTaskVisible(freeformTask)
-
- val task =
- setUpFullscreenTask().apply {
- isActivityStackTransparent = true
- isTopActivityNoDisplay = true
- numActivities = 1
- }
-
- val result = controller.handleRequest(Binder(), createTransition(task))
- assertThat(result?.changes?.get(task.token.asBinder())?.windowingMode)
+ @Test
+ fun moveFocusedTaskToDesktop_fullscreenTaskIsMovedToDesktop() {
+ val task1 = setUpFullscreenTask()
+ val task2 = setUpFullscreenTask()
+ val task3 = setUpFullscreenTask()
+
+ task1.isFocused = true
+ task2.isFocused = false
+ task3.isFocused = false
+
+ controller.moveFocusedTaskToDesktop(DEFAULT_DISPLAY, transitionSource = UNKNOWN)
+
+ val wct = getLatestEnterDesktopWct()
+ assertThat(wct.changes[task1.token.asBinder()]?.windowingMode)
.isEqualTo(WINDOWING_MODE_FREEFORM)
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
- fun handleRequest_topActivityTransparentWithDisplay_returnSwitchToFullscreenWCT() {
- val freeformTask = setUpFreeformTask()
- markTaskVisible(freeformTask)
-
- val task =
- setUpFreeformTask().apply {
- isActivityStackTransparent = true
- isTopActivityNoDisplay = false
- numActivities = 1
- }
-
- val result = controller.handleRequest(Binder(), createTransition(task))
- assertThat(result?.changes?.get(task.token.asBinder())?.windowingMode)
+ }
+
+ @Test
+ fun moveFocusedTaskToDesktop_splitScreenTaskIsMovedToDesktop() {
+ val task1 = setUpSplitScreenTask()
+ val task2 = setUpFullscreenTask()
+ val task3 = setUpFullscreenTask()
+ val task4 = setUpSplitScreenTask()
+
+ task1.isFocused = true
+ task2.isFocused = false
+ task3.isFocused = false
+ task4.isFocused = true
+
+ task4.parentTaskId = task1.taskId
+
+ controller.moveFocusedTaskToDesktop(DEFAULT_DISPLAY, transitionSource = UNKNOWN)
+
+ val wct = getLatestEnterDesktopWct()
+ assertThat(wct.changes[task4.token.asBinder()]?.windowingMode)
+ .isEqualTo(WINDOWING_MODE_FREEFORM)
+ verify(splitScreenController)
+ .prepareExitSplitScreen(
+ any(),
+ anyInt(),
+ eq(SplitScreenController.EXIT_REASON_DESKTOP_MODE),
+ )
+ }
+
+ @Test
+ fun moveFocusedTaskToFullscreen() {
+ val task1 = setUpFreeformTask()
+ val task2 = setUpFreeformTask()
+ val task3 = setUpFreeformTask()
+
+ task1.isFocused = false
+ task2.isFocused = true
+ task3.isFocused = false
+
+ controller.enterFullscreen(DEFAULT_DISPLAY, transitionSource = UNKNOWN)
+
+ val wct = getLatestExitDesktopWct()
+ assertThat(wct.changes[task2.token.asBinder()]?.windowingMode)
.isEqualTo(WINDOWING_MODE_UNDEFINED) // inherited FULLSCREEN
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
- fun handleRequest_systemUIActivityWithDisplay_returnSwitchToFullscreenWCT() {
- val freeformTask = setUpFreeformTask()
- markTaskVisible(freeformTask)
-
- // Set task as systemUI package
- val systemUIPackageName = context.resources.getString(
- com.android.internal.R.string.config_systemUi)
- val baseComponent = ComponentName(systemUIPackageName, /* class */ "")
- val task =
- setUpFreeformTask().apply {
- baseActivity = baseComponent
- isTopActivityNoDisplay = false
- }
-
- val result = controller.handleRequest(Binder(), createTransition(task))
- assertThat(result?.changes?.get(task.token.asBinder())?.windowingMode)
+ }
+
+ @Test
+ fun moveFocusedTaskToFullscreen_onlyVisibleNonMinimizedTask_removesWallpaperActivity() {
+ val task1 = setUpFreeformTask()
+ val task2 = setUpFreeformTask()
+ val task3 = setUpFreeformTask()
+ val wallpaperToken = MockToken().token()
+
+ task1.isFocused = false
+ task2.isFocused = true
+ task3.isFocused = false
+ taskRepository.wallpaperActivityToken = wallpaperToken
+ taskRepository.minimizeTask(DEFAULT_DISPLAY, task1.taskId)
+ taskRepository.updateTask(DEFAULT_DISPLAY, task3.taskId, isVisible = false)
+
+ controller.enterFullscreen(DEFAULT_DISPLAY, transitionSource = UNKNOWN)
+
+ val wct = getLatestExitDesktopWct()
+ val taskChange = assertNotNull(wct.changes[task2.token.asBinder()])
+ assertThat(taskChange.windowingMode)
+ .isEqualTo(WINDOWING_MODE_UNDEFINED) // inherited FULLSCREEN
+ wct.assertRemoveAt(index = 0, wallpaperToken)
+ }
+
+ @Test
+ fun moveFocusedTaskToFullscreen_multipleVisibleTasks_doesNotRemoveWallpaperActivity() {
+ val task1 = setUpFreeformTask()
+ val task2 = setUpFreeformTask()
+ val task3 = setUpFreeformTask()
+ val wallpaperToken = MockToken().token()
+
+ task1.isFocused = false
+ task2.isFocused = true
+ task3.isFocused = false
+ taskRepository.wallpaperActivityToken = wallpaperToken
+ controller.enterFullscreen(DEFAULT_DISPLAY, transitionSource = UNKNOWN)
+
+ val wct = getLatestExitDesktopWct()
+ val taskChange = assertNotNull(wct.changes[task2.token.asBinder()])
+ assertThat(taskChange.windowingMode)
.isEqualTo(WINDOWING_MODE_UNDEFINED) // inherited FULLSCREEN
- }
+ // Does not remove wallpaper activity, as desktop still has visible desktop tasks
+ assertThat(wct.hierarchyOps).isEmpty()
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION)
+ fun removeDesktop_multipleTasks_removesAll() {
+ val task1 = setUpFreeformTask()
+ val task2 = setUpFreeformTask()
+ val task3 = setUpFreeformTask()
+ taskRepository.minimizeTask(DEFAULT_DISPLAY, task2.taskId)
+
+ controller.removeDesktop(displayId = DEFAULT_DISPLAY)
+
+ val wct = getLatestWct(TRANSIT_CLOSE)
+ assertThat(wct.hierarchyOps).hasSize(3)
+ wct.assertRemoveAt(index = 0, task1.token)
+ wct.assertRemoveAt(index = 1, task2.token)
+ wct.assertRemoveAt(index = 2, task3.token)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION)
+ fun removeDesktop_multipleTasksWithBackgroundTask_removesAll() {
+ val task1 = setUpFreeformTask()
+ val task2 = setUpFreeformTask()
+ val task3 = setUpFreeformTask()
+ taskRepository.minimizeTask(DEFAULT_DISPLAY, task2.taskId)
+ whenever(shellTaskOrganizer.getRunningTaskInfo(task3.taskId)).thenReturn(null)
+
+ controller.removeDesktop(displayId = DEFAULT_DISPLAY)
+
+ val wct = getLatestWct(TRANSIT_CLOSE)
+ assertThat(wct.hierarchyOps).hasSize(2)
+ wct.assertRemoveAt(index = 0, task1.token)
+ wct.assertRemoveAt(index = 1, task2.token)
+ verify(recentTasksController).removeBackgroundTask(task3.taskId)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+ fun dragToDesktop_landscapeDevice_resizable_undefinedOrientation_defaultLandscapeBounds() {
+ val spyController = spy(controller)
+ whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator)
+ whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull()))
+ .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR)
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
- fun handleRequest_systemUIActivityWithoutDisplay_returnSwitchToFreeformWCT() {
- val freeformTask = setUpFreeformTask()
- markTaskVisible(freeformTask)
-
- // Set task as systemUI package
- val systemUIPackageName = context.resources.getString(
- com.android.internal.R.string.config_systemUi)
- val baseComponent = ComponentName(systemUIPackageName, /* class */ "")
- val task =
- setUpFullscreenTask().apply {
- baseActivity = baseComponent
- isTopActivityNoDisplay = true
- }
-
- val result = controller.handleRequest(Binder(), createTransition(task))
- assertThat(result?.changes?.get(task.token.asBinder())?.windowingMode)
- .isEqualTo(WINDOWING_MODE_FREEFORM)
- }
-
- @Test
- @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,)
- fun handleRequest_backTransition_singleTaskNoToken_noWallpaper_doesNotHandle() {
- val task = setUpFreeformTask()
-
- val result = controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_TO_BACK))
-
- assertNull(result, "Should not handle request")
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,)
- fun handleRequest_backTransition_singleTaskNoToken_withWallpaper_removesTask() {
- val task = setUpFreeformTask()
-
- val result = controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_TO_BACK))
-
- assertNull(result, "Should not handle request")
- }
-
- @Test
- @EnableFlags(
- Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
- )
- fun handleRequest_backTransition_singleTaskNoToken_withWallpaper_notInDesktop_doesNotHandle() {
- val task = setUpFreeformTask()
- markTaskHidden(task)
-
- val result = controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_TO_BACK))
-
- assertNull(result, "Should not handle request")
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- fun handleRequest_backTransition_singleTaskNoToken_doesNotHandle() {
- val task = setUpFreeformTask()
-
- val result = controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_TO_BACK))
-
- assertNull(result, "Should not handle request")
- }
-
- @Test
- @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,)
- fun handleRequest_backTransition_singleTaskWithToken_noWallpaper_doesNotHandle() {
- val task = setUpFreeformTask()
-
- taskRepository.wallpaperActivityToken = MockToken().token()
- val result = controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_TO_BACK))
-
- assertNull(result, "Should not handle request")
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- fun handleRequest_backTransition_singleTaskWithToken_removesWallpaper() {
- val task = setUpFreeformTask()
- val wallpaperToken = MockToken().token()
-
- taskRepository.wallpaperActivityToken = wallpaperToken
- val result = controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_TO_BACK))
-
- // Should create remove wallpaper transaction
- assertNotNull(result, "Should handle request").assertRemoveAt(index = 0, wallpaperToken)
- }
-
- @Test
- @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,)
- fun handleRequest_backTransition_multipleTasks_noWallpaper_doesNotHandle() {
- val task1 = setUpFreeformTask()
- setUpFreeformTask()
-
- taskRepository.wallpaperActivityToken = MockToken().token()
- val result = controller.handleRequest(Binder(), createTransition(task1, type = TRANSIT_TO_BACK))
-
- assertNull(result, "Should not handle request")
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- fun handleRequest_backTransition_multipleTasks_doesNotHandle() {
- val task1 = setUpFreeformTask()
- setUpFreeformTask()
-
- taskRepository.wallpaperActivityToken = MockToken().token()
- val result = controller.handleRequest(Binder(), createTransition(task1, type = TRANSIT_TO_BACK))
-
- assertNull(result, "Should not handle request")
- }
-
- @Test
- @EnableFlags(
- Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
- Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION
- )
- fun handleRequest_backTransition_multipleTasksSingleNonClosing_removesWallpaperAndTask() {
- val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
- val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
- val wallpaperToken = MockToken().token()
-
- taskRepository.wallpaperActivityToken = wallpaperToken
- taskRepository.addClosingTask(displayId = DEFAULT_DISPLAY, taskId = task2.taskId)
- val result = controller.handleRequest(Binder(), createTransition(task1, type = TRANSIT_TO_BACK))
-
- // Should create remove wallpaper transaction
- assertNotNull(result, "Should handle request").assertRemoveAt(index = 0, wallpaperToken)
- }
-
- @Test
- @EnableFlags(
- Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
- )
- fun handleRequest_backTransition_multipleTasksSingleNonMinimized_removesWallpaperAndTask() {
- val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
- val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
- val wallpaperToken = MockToken().token()
-
- taskRepository.wallpaperActivityToken = wallpaperToken
- taskRepository.minimizeTask(displayId = DEFAULT_DISPLAY, taskId = task2.taskId)
- val result = controller.handleRequest(Binder(), createTransition(task1, type = TRANSIT_TO_BACK))
-
- // Should create remove wallpaper transaction
- assertNotNull(result, "Should handle request").assertRemoveAt(index = 0, wallpaperToken)
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,)
- fun handleRequest_backTransition_nonMinimizadTask_withWallpaper_removesWallpaper() {
- val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
- val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
- val wallpaperToken = MockToken().token()
-
- taskRepository.wallpaperActivityToken = wallpaperToken
- taskRepository.minimizeTask(displayId = DEFAULT_DISPLAY, taskId = task2.taskId)
- // Task is being minimized so mark it as not visible.
- taskRepository.updateTask(displayId = DEFAULT_DISPLAY, task2.taskId, isVisible = false)
- val result = controller.handleRequest(Binder(), createTransition(task2, type = TRANSIT_TO_BACK))
-
- assertNull(result, "Should not handle request")
- }
-
- @Test
- @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,)
- fun handleRequest_closeTransition_singleTaskNoToken_noWallpaper_doesNotHandle() {
- val task = setUpFreeformTask()
-
- val result = controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_CLOSE))
-
- assertNull(result, "Should not handle request")
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- fun handleRequest_closeTransition_singleTaskNoToken_doesNotHandle() {
- val task = setUpFreeformTask()
-
- val result = controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_CLOSE))
-
- assertNull(result, "Should not handle request")
- }
-
- @Test
- @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- fun handleRequest_closeTransition_singleTaskWithToken_noWallpaper_doesNotHandle() {
- val task = setUpFreeformTask()
-
- taskRepository.wallpaperActivityToken = MockToken().token()
- val result = controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_CLOSE))
-
- assertNull(result, "Should not handle request")
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- fun handleRequest_closeTransition_singleTaskWithToken_withWallpaper_removesWallpaper() {
- val task = setUpFreeformTask()
- val wallpaperToken = MockToken().token()
-
- taskRepository.wallpaperActivityToken = wallpaperToken
- val result = controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_CLOSE))
-
- // Should create remove wallpaper transaction
- assertNotNull(result, "Should handle request").assertRemoveAt(index = 0, wallpaperToken)
- }
-
- @Test
- @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- fun handleRequest_closeTransition_multipleTasks_noWallpaper_doesNotHandle() {
- val task1 = setUpFreeformTask()
- setUpFreeformTask()
-
- taskRepository.wallpaperActivityToken = MockToken().token()
- val result = controller.handleRequest(Binder(), createTransition(task1, type = TRANSIT_CLOSE))
-
- assertNull(result, "Should not handle request")
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- fun handleRequest_closeTransition_multipleTasksFlagEnabled_doesNotHandle() {
- val task1 = setUpFreeformTask()
- setUpFreeformTask()
-
- taskRepository.wallpaperActivityToken = MockToken().token()
- val result = controller.handleRequest(Binder(), createTransition(task1, type = TRANSIT_CLOSE))
-
- assertNull(result, "Should not handle request")
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- fun handleRequest_closeTransition_multipleTasksSingleNonClosing_removesWallpaper() {
- val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
- val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
- val wallpaperToken = MockToken().token()
-
- taskRepository.wallpaperActivityToken = wallpaperToken
- taskRepository.addClosingTask(displayId = DEFAULT_DISPLAY, taskId = task2.taskId)
- val result = controller.handleRequest(Binder(), createTransition(task1, type = TRANSIT_CLOSE))
-
- // Should create remove wallpaper transaction
- assertNotNull(result, "Should handle request").assertRemoveAt(index = 0, wallpaperToken)
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- fun handleRequest_closeTransition_multipleTasksSingleNonMinimized_removesWallpaper() {
- val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
- val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
- val wallpaperToken = MockToken().token()
-
- taskRepository.wallpaperActivityToken = wallpaperToken
- taskRepository.minimizeTask(displayId = DEFAULT_DISPLAY, taskId = task2.taskId)
- val result = controller.handleRequest(Binder(), createTransition(task1, type = TRANSIT_CLOSE))
-
- // Should create remove wallpaper transaction
- assertNotNull(result, "Should handle request").assertRemoveAt(index = 0, wallpaperToken)
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,)
- fun handleRequest_closeTransition_minimizadTask_withWallpaper_removesWallpaper() {
- val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
- val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
- val wallpaperToken = MockToken().token()
-
- taskRepository.wallpaperActivityToken = wallpaperToken
- taskRepository.minimizeTask(displayId = DEFAULT_DISPLAY, taskId = task2.taskId)
- // Task is being minimized so mark it as not visible.
- taskRepository.updateTask(displayId = DEFAULT_DISPLAY, task2.taskId, isVisible = false)
- val result = controller.handleRequest(Binder(), createTransition(task2, type = TRANSIT_TO_BACK))
-
- assertNull(result, "Should not handle request")
- }
-
- @Test
- fun moveFocusedTaskToDesktop_fullscreenTaskIsMovedToDesktop() {
- val task1 = setUpFullscreenTask()
- val task2 = setUpFullscreenTask()
- val task3 = setUpFullscreenTask()
-
- task1.isFocused = true
- task2.isFocused = false
- task3.isFocused = false
-
- controller.moveFocusedTaskToDesktop(DEFAULT_DISPLAY, transitionSource = UNKNOWN)
-
- val wct = getLatestEnterDesktopWct()
- assertThat(wct.changes[task1.token.asBinder()]?.windowingMode)
- .isEqualTo(WINDOWING_MODE_FREEFORM)
- }
-
- @Test
- fun moveFocusedTaskToDesktop_splitScreenTaskIsMovedToDesktop() {
- val task1 = setUpSplitScreenTask()
- val task2 = setUpFullscreenTask()
- val task3 = setUpFullscreenTask()
- val task4 = setUpSplitScreenTask()
-
- task1.isFocused = true
- task2.isFocused = false
- task3.isFocused = false
- task4.isFocused = true
-
- task4.parentTaskId = task1.taskId
-
- controller.moveFocusedTaskToDesktop(DEFAULT_DISPLAY, transitionSource = UNKNOWN)
-
- val wct = getLatestEnterDesktopWct()
- assertThat(wct.changes[task4.token.asBinder()]?.windowingMode)
- .isEqualTo(WINDOWING_MODE_FREEFORM)
- verify(splitScreenController)
- .prepareExitSplitScreen(any(), anyInt(), eq(SplitScreenController.EXIT_REASON_DESKTOP_MODE))
- }
-
- @Test
- fun moveFocusedTaskToFullscreen() {
- val task1 = setUpFreeformTask()
- val task2 = setUpFreeformTask()
- val task3 = setUpFreeformTask()
-
- task1.isFocused = false
- task2.isFocused = true
- task3.isFocused = false
-
- controller.enterFullscreen(DEFAULT_DISPLAY, transitionSource = UNKNOWN)
-
- val wct = getLatestExitDesktopWct()
- assertThat(wct.changes[task2.token.asBinder()]?.windowingMode)
- .isEqualTo(WINDOWING_MODE_UNDEFINED) // inherited FULLSCREEN
- }
-
- @Test
- fun moveFocusedTaskToFullscreen_onlyVisibleNonMinimizedTask_removesWallpaperActivity() {
- val task1 = setUpFreeformTask()
- val task2 = setUpFreeformTask()
- val task3 = setUpFreeformTask()
- val wallpaperToken = MockToken().token()
-
- task1.isFocused = false
- task2.isFocused = true
- task3.isFocused = false
- taskRepository.wallpaperActivityToken = wallpaperToken
- taskRepository.minimizeTask(DEFAULT_DISPLAY, task1.taskId)
- taskRepository.updateTask(DEFAULT_DISPLAY, task3.taskId, isVisible = false)
-
- controller.enterFullscreen(DEFAULT_DISPLAY, transitionSource = UNKNOWN)
-
- val wct = getLatestExitDesktopWct()
- val taskChange = assertNotNull(wct.changes[task2.token.asBinder()])
- assertThat(taskChange.windowingMode).isEqualTo(WINDOWING_MODE_UNDEFINED) // inherited FULLSCREEN
- wct.assertRemoveAt(index = 0, wallpaperToken)
- }
-
- @Test
- fun moveFocusedTaskToFullscreen_multipleVisibleTasks_doesNotRemoveWallpaperActivity() {
- val task1 = setUpFreeformTask()
- val task2 = setUpFreeformTask()
- val task3 = setUpFreeformTask()
- val wallpaperToken = MockToken().token()
-
- task1.isFocused = false
- task2.isFocused = true
- task3.isFocused = false
- taskRepository.wallpaperActivityToken = wallpaperToken
- controller.enterFullscreen(DEFAULT_DISPLAY, transitionSource = UNKNOWN)
-
- val wct = getLatestExitDesktopWct()
- val taskChange = assertNotNull(wct.changes[task2.token.asBinder()])
- assertThat(taskChange.windowingMode).isEqualTo(WINDOWING_MODE_UNDEFINED) // inherited FULLSCREEN
- // Does not remove wallpaper activity, as desktop still has visible desktop tasks
- assertThat(wct.hierarchyOps).isEmpty()
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION)
- fun removeDesktop_multipleTasks_removesAll() {
- val task1 = setUpFreeformTask()
- val task2 = setUpFreeformTask()
- val task3 = setUpFreeformTask()
- taskRepository.minimizeTask(DEFAULT_DISPLAY, task2.taskId)
-
- controller.removeDesktop(displayId = DEFAULT_DISPLAY)
-
- val wct = getLatestWct(TRANSIT_CLOSE)
- assertThat(wct.hierarchyOps).hasSize(3)
- wct.assertRemoveAt(index = 0, task1.token)
- wct.assertRemoveAt(index = 1, task2.token)
- wct.assertRemoveAt(index = 2, task3.token)
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION)
- fun removeDesktop_multipleTasksWithBackgroundTask_removesAll() {
- val task1 = setUpFreeformTask()
- val task2 = setUpFreeformTask()
- val task3 = setUpFreeformTask()
- taskRepository.minimizeTask(DEFAULT_DISPLAY, task2.taskId)
- whenever(shellTaskOrganizer.getRunningTaskInfo(task3.taskId)).thenReturn(null)
-
- controller.removeDesktop(displayId = DEFAULT_DISPLAY)
-
- val wct = getLatestWct(TRANSIT_CLOSE)
- assertThat(wct.hierarchyOps).hasSize(2)
- wct.assertRemoveAt(index = 0, task1.token)
- wct.assertRemoveAt(index = 1, task2.token)
- verify(recentTasksController).removeBackgroundTask(task3.taskId)
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
- fun dragToDesktop_landscapeDevice_resizable_undefinedOrientation_defaultLandscapeBounds() {
- val spyController = spy(controller)
- whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator)
- whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull()))
- .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR)
-
- val task = setUpFullscreenTask()
- setUpLandscapeDisplay()
-
- spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task, mockSurface)
- val wct = getLatestDragToDesktopWct()
- assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_LANDSCAPE_BOUNDS)
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
- fun dragToDesktop_landscapeDevice_resizable_landscapeOrientation_defaultLandscapeBounds() {
- val spyController = spy(controller)
- whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator)
- whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull()))
- .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR)
-
- val task = setUpFullscreenTask(screenOrientation = SCREEN_ORIENTATION_LANDSCAPE)
- setUpLandscapeDisplay()
-
- spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task, mockSurface)
- val wct = getLatestDragToDesktopWct()
- assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_LANDSCAPE_BOUNDS)
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
- fun dragToDesktop_landscapeDevice_resizable_portraitOrientation_resizablePortraitBounds() {
- val spyController = spy(controller)
- whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator)
- whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull()))
- .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR)
-
- val task =
- setUpFullscreenTask(screenOrientation = SCREEN_ORIENTATION_PORTRAIT, shouldLetterbox = true)
- setUpLandscapeDisplay()
-
- spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task, mockSurface)
- val wct = getLatestDragToDesktopWct()
- assertThat(findBoundsChange(wct, task)).isEqualTo(RESIZABLE_PORTRAIT_BOUNDS)
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
- fun dragToDesktop_landscapeDevice_unResizable_landscapeOrientation_defaultLandscapeBounds() {
- val spyController = spy(controller)
- whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator)
- whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull()))
- .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR)
-
- val task =
- setUpFullscreenTask(isResizable = false, screenOrientation = SCREEN_ORIENTATION_LANDSCAPE)
- setUpLandscapeDisplay()
-
- spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task, mockSurface)
- val wct = getLatestDragToDesktopWct()
- assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_LANDSCAPE_BOUNDS)
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
- fun dragToDesktop_landscapeDevice_unResizable_portraitOrientation_unResizablePortraitBounds() {
- val spyController = spy(controller)
- whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator)
- whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull()))
- .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR)
-
- val task =
- setUpFullscreenTask(
- isResizable = false,
- screenOrientation = SCREEN_ORIENTATION_PORTRAIT,
- shouldLetterbox = true)
- setUpLandscapeDisplay()
-
- spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task, mockSurface)
- val wct = getLatestDragToDesktopWct()
- assertThat(findBoundsChange(wct, task)).isEqualTo(UNRESIZABLE_PORTRAIT_BOUNDS)
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
- fun dragToDesktop_portraitDevice_resizable_undefinedOrientation_defaultPortraitBounds() {
- val spyController = spy(controller)
- whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator)
- whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull()))
- .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR)
-
- val task = setUpFullscreenTask(deviceOrientation = ORIENTATION_PORTRAIT)
- setUpPortraitDisplay()
-
- spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task, mockSurface)
- val wct = getLatestDragToDesktopWct()
- assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_PORTRAIT_BOUNDS)
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
- fun dragToDesktop_portraitDevice_resizable_portraitOrientation_defaultPortraitBounds() {
- val spyController = spy(controller)
- whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator)
- whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull()))
- .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR)
-
- val task =
- setUpFullscreenTask(
- deviceOrientation = ORIENTATION_PORTRAIT,
- screenOrientation = SCREEN_ORIENTATION_PORTRAIT)
- setUpPortraitDisplay()
-
- spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task, mockSurface)
- val wct = getLatestDragToDesktopWct()
- assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_PORTRAIT_BOUNDS)
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
- fun dragToDesktop_portraitDevice_resizable_landscapeOrientation_resizableLandscapeBounds() {
- val spyController = spy(controller)
- whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator)
- whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull()))
- .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR)
-
- val task =
- setUpFullscreenTask(
- deviceOrientation = ORIENTATION_PORTRAIT,
- screenOrientation = SCREEN_ORIENTATION_LANDSCAPE,
- shouldLetterbox = true)
- setUpPortraitDisplay()
-
- spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task, mockSurface)
- val wct = getLatestDragToDesktopWct()
- assertThat(findBoundsChange(wct, task)).isEqualTo(RESIZABLE_LANDSCAPE_BOUNDS)
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
- fun dragToDesktop_portraitDevice_unResizable_portraitOrientation_defaultPortraitBounds() {
- val spyController = spy(controller)
- whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator)
- whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull()))
- .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR)
-
- val task =
- setUpFullscreenTask(
- isResizable = false,
- deviceOrientation = ORIENTATION_PORTRAIT,
- screenOrientation = SCREEN_ORIENTATION_PORTRAIT)
- setUpPortraitDisplay()
-
- spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task, mockSurface)
- val wct = getLatestDragToDesktopWct()
- assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_PORTRAIT_BOUNDS)
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
- fun dragToDesktop_portraitDevice_unResizable_landscapeOrientation_unResizableLandscapeBounds() {
- val spyController = spy(controller)
- whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator)
- whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull()))
- .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR)
-
- val task =
- setUpFullscreenTask(
- isResizable = false,
- deviceOrientation = ORIENTATION_PORTRAIT,
- screenOrientation = SCREEN_ORIENTATION_LANDSCAPE,
- shouldLetterbox = true)
- setUpPortraitDisplay()
-
- spyController.onDragPositioningEndThroughStatusBar(PointF(200f, 200f), task, mockSurface)
- val wct = getLatestDragToDesktopWct()
- assertThat(findBoundsChange(wct, task)).isEqualTo(UNRESIZABLE_LANDSCAPE_BOUNDS)
- }
-
- @Test
- fun onDesktopDragMove_endsOutsideValidDragArea_snapsToValidBounds() {
- val task = setUpFreeformTask()
- val spyController = spy(controller)
- val mockSurface = mock(SurfaceControl::class.java)
- val mockDisplayLayout = mock(DisplayLayout::class.java)
- whenever(displayController.getDisplayLayout(task.displayId)).thenReturn(mockDisplayLayout)
- whenever(mockDisplayLayout.stableInsets()).thenReturn(Rect(0, 100, 2000, 2000))
- spyController.onDragPositioningMove(task, mockSurface, 200f, Rect(100, -100, 500, 1000))
-
- whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator)
- whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull()))
- .thenReturn(DesktopModeVisualIndicator.IndicatorType.NO_INDICATOR)
- spyController.onDragPositioningEnd(
- task,
- mockSurface,
- Point(100, -100), /* position */
- PointF(200f, -200f), /* inputCoordinate */
- Rect(100, -100, 500, 1000), /* currentDragBounds */
- Rect(0, 50, 2000, 2000), /* validDragArea */
- Rect() /* dragStartBounds */,
- motionEvent,
- desktopWindowDecoration,
+ val task = setUpFullscreenTask()
+ setUpLandscapeDisplay()
+
+ spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task, mockSurface)
+ val wct = getLatestDragToDesktopWct()
+ assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_LANDSCAPE_BOUNDS)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+ fun dragToDesktop_landscapeDevice_resizable_landscapeOrientation_defaultLandscapeBounds() {
+ val spyController = spy(controller)
+ whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator)
+ whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull()))
+ .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR)
+
+ val task = setUpFullscreenTask(screenOrientation = SCREEN_ORIENTATION_LANDSCAPE)
+ setUpLandscapeDisplay()
+
+ spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task, mockSurface)
+ val wct = getLatestDragToDesktopWct()
+ assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_LANDSCAPE_BOUNDS)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+ fun dragToDesktop_landscapeDevice_resizable_portraitOrientation_resizablePortraitBounds() {
+ val spyController = spy(controller)
+ whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator)
+ whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull()))
+ .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR)
+
+ val task =
+ setUpFullscreenTask(
+ screenOrientation = SCREEN_ORIENTATION_PORTRAIT,
+ shouldLetterbox = true,
+ )
+ setUpLandscapeDisplay()
+
+ spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task, mockSurface)
+ val wct = getLatestDragToDesktopWct()
+ assertThat(findBoundsChange(wct, task)).isEqualTo(RESIZABLE_PORTRAIT_BOUNDS)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+ fun dragToDesktop_landscapeDevice_unResizable_landscapeOrientation_defaultLandscapeBounds() {
+ val spyController = spy(controller)
+ whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator)
+ whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull()))
+ .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR)
+
+ val task =
+ setUpFullscreenTask(
+ isResizable = false,
+ screenOrientation = SCREEN_ORIENTATION_LANDSCAPE,
+ )
+ setUpLandscapeDisplay()
+
+ spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task, mockSurface)
+ val wct = getLatestDragToDesktopWct()
+ assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_LANDSCAPE_BOUNDS)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+ fun dragToDesktop_landscapeDevice_unResizable_portraitOrientation_unResizablePortraitBounds() {
+ val spyController = spy(controller)
+ whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator)
+ whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull()))
+ .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR)
+
+ val task =
+ setUpFullscreenTask(
+ isResizable = false,
+ screenOrientation = SCREEN_ORIENTATION_PORTRAIT,
+ shouldLetterbox = true,
+ )
+ setUpLandscapeDisplay()
+
+ spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task, mockSurface)
+ val wct = getLatestDragToDesktopWct()
+ assertThat(findBoundsChange(wct, task)).isEqualTo(UNRESIZABLE_PORTRAIT_BOUNDS)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+ fun dragToDesktop_portraitDevice_resizable_undefinedOrientation_defaultPortraitBounds() {
+ val spyController = spy(controller)
+ whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator)
+ whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull()))
+ .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR)
+
+ val task = setUpFullscreenTask(deviceOrientation = ORIENTATION_PORTRAIT)
+ setUpPortraitDisplay()
+
+ spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task, mockSurface)
+ val wct = getLatestDragToDesktopWct()
+ assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_PORTRAIT_BOUNDS)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+ fun dragToDesktop_portraitDevice_resizable_portraitOrientation_defaultPortraitBounds() {
+ val spyController = spy(controller)
+ whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator)
+ whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull()))
+ .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR)
+
+ val task =
+ setUpFullscreenTask(
+ deviceOrientation = ORIENTATION_PORTRAIT,
+ screenOrientation = SCREEN_ORIENTATION_PORTRAIT,
+ )
+ setUpPortraitDisplay()
+
+ spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task, mockSurface)
+ val wct = getLatestDragToDesktopWct()
+ assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_PORTRAIT_BOUNDS)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+ fun dragToDesktop_portraitDevice_resizable_landscapeOrientation_resizableLandscapeBounds() {
+ val spyController = spy(controller)
+ whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator)
+ whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull()))
+ .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR)
+
+ val task =
+ setUpFullscreenTask(
+ deviceOrientation = ORIENTATION_PORTRAIT,
+ screenOrientation = SCREEN_ORIENTATION_LANDSCAPE,
+ shouldLetterbox = true,
+ )
+ setUpPortraitDisplay()
+
+ spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task, mockSurface)
+ val wct = getLatestDragToDesktopWct()
+ assertThat(findBoundsChange(wct, task)).isEqualTo(RESIZABLE_LANDSCAPE_BOUNDS)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+ fun dragToDesktop_portraitDevice_unResizable_portraitOrientation_defaultPortraitBounds() {
+ val spyController = spy(controller)
+ whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator)
+ whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull()))
+ .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR)
+
+ val task =
+ setUpFullscreenTask(
+ isResizable = false,
+ deviceOrientation = ORIENTATION_PORTRAIT,
+ screenOrientation = SCREEN_ORIENTATION_PORTRAIT,
+ )
+ setUpPortraitDisplay()
+
+ spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task, mockSurface)
+ val wct = getLatestDragToDesktopWct()
+ assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_PORTRAIT_BOUNDS)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+ fun dragToDesktop_portraitDevice_unResizable_landscapeOrientation_unResizableLandscapeBounds() {
+ val spyController = spy(controller)
+ whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator)
+ whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull()))
+ .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR)
+
+ val task =
+ setUpFullscreenTask(
+ isResizable = false,
+ deviceOrientation = ORIENTATION_PORTRAIT,
+ screenOrientation = SCREEN_ORIENTATION_LANDSCAPE,
+ shouldLetterbox = true,
+ )
+ setUpPortraitDisplay()
+
+ spyController.onDragPositioningEndThroughStatusBar(PointF(200f, 200f), task, mockSurface)
+ val wct = getLatestDragToDesktopWct()
+ assertThat(findBoundsChange(wct, task)).isEqualTo(UNRESIZABLE_LANDSCAPE_BOUNDS)
+ }
+
+ @Test
+ fun onDesktopDragMove_endsOutsideValidDragArea_snapsToValidBounds() {
+ val task = setUpFreeformTask()
+ val spyController = spy(controller)
+ val mockSurface = mock(SurfaceControl::class.java)
+ val mockDisplayLayout = mock(DisplayLayout::class.java)
+ whenever(displayController.getDisplayLayout(task.displayId)).thenReturn(mockDisplayLayout)
+ whenever(mockDisplayLayout.stableInsets()).thenReturn(Rect(0, 100, 2000, 2000))
+ spyController.onDragPositioningMove(task, mockSurface, 200f, Rect(100, -100, 500, 1000))
+
+ whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator)
+ whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull()))
+ .thenReturn(DesktopModeVisualIndicator.IndicatorType.NO_INDICATOR)
+ spyController.onDragPositioningEnd(
+ task,
+ mockSurface,
+ Point(100, -100), /* position */
+ PointF(200f, -200f), /* inputCoordinate */
+ Rect(100, -100, 500, 1000), /* currentDragBounds */
+ Rect(0, 50, 2000, 2000), /* validDragArea */
+ Rect() /* dragStartBounds */,
+ motionEvent,
+ desktopWindowDecoration,
)
- val rectAfterEnd = Rect(100, 50, 500, 1150)
- verify(transitions)
- .startTransition(
- eq(TRANSIT_CHANGE),
- Mockito.argThat { wct ->
- return@argThat wct.changes.any { (token, change) ->
- change.configuration.windowConfiguration.bounds == rectAfterEnd
- }
- },
- eq(null))
- }
-
- @Test
- fun onDesktopDragEnd_noIndicator_updatesTaskBounds() {
- val task = setUpFreeformTask()
- val spyController = spy(controller)
- val mockSurface = mock(SurfaceControl::class.java)
- val mockDisplayLayout = mock(DisplayLayout::class.java)
- whenever(displayController.getDisplayLayout(task.displayId)).thenReturn(mockDisplayLayout)
- whenever(mockDisplayLayout.stableInsets()).thenReturn(Rect(0, 100, 2000, 2000))
- spyController.onDragPositioningMove(task, mockSurface, 200f, Rect(100, 200, 500, 1000))
-
- val currentDragBounds = Rect(100, 200, 500, 1000)
- whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator)
- whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull()))
- .thenReturn(DesktopModeVisualIndicator.IndicatorType.NO_INDICATOR)
-
- spyController.onDragPositioningEnd(
- task,
- mockSurface,
- Point(100, 200), /* position */
- PointF(200f, 300f), /* inputCoordinate */
- currentDragBounds, /* currentDragBounds */
- Rect(0, 50, 2000, 2000) /* validDragArea */,
- Rect() /* dragStartBounds */,
- motionEvent,
- desktopWindowDecoration,
- )
-
-
- verify(transitions)
- .startTransition(
- eq(TRANSIT_CHANGE),
- Mockito.argThat { wct ->
- return@argThat wct.changes.any { (token, change) ->
- change.configuration.windowConfiguration.bounds == currentDragBounds
- }
- },
- eq(null))
- }
-
- @Test
- fun onDesktopDragEnd_fullscreenIndicator_dragToExitDesktop() {
- val task = setUpFreeformTask(bounds = Rect(0, 0, 100, 100))
- val spyController = spy(controller)
- val mockSurface = mock(SurfaceControl::class.java)
- val mockDisplayLayout = mock(DisplayLayout::class.java)
- val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!!
- tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FULLSCREEN
- whenever(displayController.getDisplayLayout(task.displayId)).thenReturn(mockDisplayLayout)
- whenever(mockDisplayLayout.stableInsets()).thenReturn(Rect(0, 100, 2000, 2000))
- whenever(mockDisplayLayout.getStableBounds(any())).thenAnswer { i ->
- (i.arguments.first() as Rect).set(STABLE_BOUNDS)
- }
- whenever(DesktopModeStatus.shouldMaximizeWhenDragToTopEdge(context)).thenReturn(false)
- whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator)
- whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull()))
- .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR)
-
- // Drag move the task to the top edge
- spyController.onDragPositioningMove(task, mockSurface, 200f, Rect(100, 200, 500, 1000))
- spyController.onDragPositioningEnd(
- task,
- mockSurface,
- Point(100, 50), /* position */
- PointF(200f, 300f), /* inputCoordinate */
- Rect(100, 50, 500, 1000), /* currentDragBounds */
- Rect(0, 50, 2000, 2000) /* validDragArea */,
- Rect() /* dragStartBounds */,
- motionEvent,
- desktopWindowDecoration)
-
- // Assert the task exits desktop mode
- val wct = getLatestExitDesktopWct()
- assertThat(wct.changes[task.token.asBinder()]?.windowingMode)
- .isEqualTo(WINDOWING_MODE_UNDEFINED)
- }
-
- @Test
- fun onDesktopDragEnd_fullscreenIndicator_dragToMaximize() {
- val task = setUpFreeformTask(bounds = Rect(0, 0, 100, 100))
- val spyController = spy(controller)
- val mockSurface = mock(SurfaceControl::class.java)
- val mockDisplayLayout = mock(DisplayLayout::class.java)
- val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!!
- tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FULLSCREEN
- whenever(displayController.getDisplayLayout(task.displayId)).thenReturn(mockDisplayLayout)
- whenever(mockDisplayLayout.stableInsets()).thenReturn(Rect(0, 100, 2000, 2000))
- whenever(mockDisplayLayout.getStableBounds(any())).thenAnswer { i ->
- (i.arguments.first() as Rect).set(STABLE_BOUNDS)
- }
- whenever(DesktopModeStatus.shouldMaximizeWhenDragToTopEdge(context)).thenReturn(true)
- whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator)
- whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull()))
- .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR)
-
- // Drag move the task to the top edge
- val currentDragBounds = Rect(100, 50, 500, 1000)
- spyController.onDragPositioningMove(task, mockSurface, 200f, Rect(100, 200, 500, 1000))
- spyController.onDragPositioningEnd(
- task,
- mockSurface,
- Point(100, 50), /* position */
- PointF(200f, 300f), /* inputCoordinate */
- currentDragBounds,
- Rect(0, 50, 2000, 2000) /* validDragArea */,
- Rect() /* dragStartBounds */,
- motionEvent,
- desktopWindowDecoration)
-
- // Assert bounds set to stable bounds
- val wct = getLatestToggleResizeDesktopTaskWct(currentDragBounds)
- assertThat(findBoundsChange(wct, task)).isEqualTo(STABLE_BOUNDS)
- // Assert event is properly logged
- verify(desktopModeEventLogger, times(1)).logTaskResizingStarted(
- ResizeTrigger.DRAG_TO_TOP_RESIZE_TRIGGER,
- InputMethod.UNKNOWN_INPUT_METHOD,
- task,
- task.configuration.windowConfiguration.bounds.width(),
- task.configuration.windowConfiguration.bounds.height(),
- displayController
- )
- verify(desktopModeEventLogger, times(1)).logTaskResizingEnded(
- ResizeTrigger.DRAG_TO_TOP_RESIZE_TRIGGER,
- InputMethod.UNKNOWN_INPUT_METHOD,
- task,
- STABLE_BOUNDS.width(),
- STABLE_BOUNDS.height(),
- displayController
- )
- }
-
- @Test
- fun onDesktopDragEnd_fullscreenIndicator_dragToMaximize_noBoundsChange() {
- val task = setUpFreeformTask(bounds = STABLE_BOUNDS)
- val spyController = spy(controller)
- val mockSurface = mock(SurfaceControl::class.java)
- val mockDisplayLayout = mock(DisplayLayout::class.java)
- val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!!
- tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FULLSCREEN
- whenever(displayController.getDisplayLayout(task.displayId)).thenReturn(mockDisplayLayout)
- whenever(mockDisplayLayout.stableInsets()).thenReturn(Rect(0, 100, 2000, 2000))
- whenever(mockDisplayLayout.getStableBounds(any())).thenAnswer { i ->
- (i.arguments.first() as Rect).set(STABLE_BOUNDS)
- }
- whenever(DesktopModeStatus.shouldMaximizeWhenDragToTopEdge(context)).thenReturn(true)
- whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator)
- whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull()))
- .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR)
-
- // Drag move the task to the top edge
- val currentDragBounds = Rect(100, 50, 500, 1000)
- spyController.onDragPositioningMove(task, mockSurface, 200f, Rect(100, 200, 500, 1000))
- spyController.onDragPositioningEnd(
- task,
- mockSurface,
- Point(100, 50), /* position */
- PointF(200f, 300f), /* inputCoordinate */
- currentDragBounds, /* currentDragBounds */
- Rect(0, 50, 2000, 2000) /* validDragArea */,
- Rect() /* dragStartBounds */,
- motionEvent,
- desktopWindowDecoration)
-
- // Assert that task is NOT updated via WCT
- verify(toggleResizeDesktopTaskTransitionHandler, never()).startTransition(any(), any())
- // Assert that task leash is updated via Surface Animations
- verify(mReturnToDragStartAnimator).start(
- eq(task.taskId),
- eq(mockSurface),
- eq(currentDragBounds),
- eq(STABLE_BOUNDS),
- anyOrNull(),
- )
- // Assert no event is logged
- verify(desktopModeEventLogger, never()).logTaskResizingStarted(
- any(), any(), any(), any(), any(), any(), any()
- )
- verify(desktopModeEventLogger, never()).logTaskResizingEnded(
- any(), any(), any(), any(), any(), any(), any()
- )
- }
-
- @Test
- fun enterSplit_freeformTaskIsMovedToSplit() {
- val task1 = setUpFreeformTask()
- val task2 = setUpFreeformTask()
- val task3 = setUpFreeformTask()
-
- task1.isFocused = false
- task2.isFocused = true
- task3.isFocused = false
-
- controller.enterSplit(DEFAULT_DISPLAY, leftOrTop = false)
-
- verify(splitScreenController)
- .requestEnterSplitSelect(
- eq(task2),
- any(),
- eq(SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT),
- eq(task2.configuration.windowConfiguration.bounds))
- }
-
- @Test
- fun enterSplit_onlyVisibleNonMinimizedTask_removesWallpaperActivity() {
- val task1 = setUpFreeformTask()
- val task2 = setUpFreeformTask()
- val task3 = setUpFreeformTask()
- val wallpaperToken = MockToken().token()
-
- task1.isFocused = false
- task2.isFocused = true
- task3.isFocused = false
- taskRepository.wallpaperActivityToken = wallpaperToken
- taskRepository.minimizeTask(DEFAULT_DISPLAY, task1.taskId)
- taskRepository.updateTask(DEFAULT_DISPLAY, task3.taskId, isVisible = false)
-
- controller.enterSplit(DEFAULT_DISPLAY, leftOrTop = false)
-
- val wctArgument = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
- verify(splitScreenController)
- .requestEnterSplitSelect(
- eq(task2),
- wctArgument.capture(),
- eq(SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT),
- eq(task2.configuration.windowConfiguration.bounds))
- // Removes wallpaper activity when leaving desktop
- wctArgument.value.assertRemoveAt(index = 0, wallpaperToken)
- }
-
- @Test
- fun enterSplit_multipleVisibleNonMinimizedTasks_removesWallpaperActivity() {
- val task1 = setUpFreeformTask()
- val task2 = setUpFreeformTask()
- val task3 = setUpFreeformTask()
- val wallpaperToken = MockToken().token()
-
- task1.isFocused = false
- task2.isFocused = true
- task3.isFocused = false
- taskRepository.wallpaperActivityToken = wallpaperToken
-
- controller.enterSplit(DEFAULT_DISPLAY, leftOrTop = false)
-
- val wctArgument = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
- verify(splitScreenController)
- .requestEnterSplitSelect(
- eq(task2),
- wctArgument.capture(),
- eq(SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT),
- eq(task2.configuration.windowConfiguration.bounds))
- // Does not remove wallpaper activity, as desktop still has visible desktop tasks
- assertThat(wctArgument.value.hierarchyOps).isEmpty()
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MULTI_INSTANCE_FEATURES)
- fun newWindow_fromFullscreenOpensInSplit() {
- setUpLandscapeDisplay()
- val task = setUpFullscreenTask()
- val optionsCaptor = ArgumentCaptor.forClass(Bundle::class.java)
- runOpenNewWindow(task)
- verify(splitScreenController)
- .startIntent(any(), anyInt(), any(), any(),
- optionsCaptor.capture(), anyOrNull(), eq(true), eq(SPLIT_INDEX_UNDEFINED)
- )
- assertThat(ActivityOptions.fromBundle(optionsCaptor.value).launchWindowingMode)
- .isEqualTo(WINDOWING_MODE_MULTI_WINDOW)
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MULTI_INSTANCE_FEATURES)
- fun newWindow_fromSplitOpensInSplit() {
- setUpLandscapeDisplay()
- val task = setUpSplitScreenTask()
- val optionsCaptor = ArgumentCaptor.forClass(Bundle::class.java)
- runOpenNewWindow(task)
- verify(splitScreenController)
- .startIntent(
- any(), anyInt(), any(), any(),
- optionsCaptor.capture(), anyOrNull(), eq(true), eq(SPLIT_INDEX_UNDEFINED)
- )
- assertThat(ActivityOptions.fromBundle(optionsCaptor.value).launchWindowingMode)
- .isEqualTo(WINDOWING_MODE_MULTI_WINDOW)
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MULTI_INSTANCE_FEATURES)
- fun newWindow_fromFreeformAddsNewWindow() {
- setUpLandscapeDisplay()
- val task = setUpFreeformTask()
- val wctCaptor = argumentCaptor<WindowContainerTransaction>()
- val transition = Binder()
- whenever(mMockDesktopImmersiveController
- .exitImmersiveIfApplicable(any(), anyInt(), anyOrNull(), any()))
- .thenReturn(ExitResult.NoExit)
- whenever(desktopMixedTransitionHandler
- .startLaunchTransition(anyInt(), any(), anyOrNull(), anyOrNull(), anyOrNull()))
- .thenReturn(transition)
-
- runOpenNewWindow(task)
-
- verify(desktopMixedTransitionHandler)
- .startLaunchTransition(anyInt(), wctCaptor.capture(), anyOrNull(), anyOrNull(), anyOrNull())
- assertThat(ActivityOptions.fromBundle(wctCaptor.firstValue.hierarchyOps[0].launchOptions)
- .launchWindowingMode).isEqualTo(WINDOWING_MODE_FREEFORM)
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MULTI_INSTANCE_FEATURES)
- fun newWindow_fromFreeform_exitsImmersiveIfNeeded() {
- setUpLandscapeDisplay()
- val immersiveTask = setUpFreeformTask()
- val task = setUpFreeformTask()
- val runOnStart = RunOnStartTransitionCallback()
- val transition = Binder()
- whenever(mMockDesktopImmersiveController
- .exitImmersiveIfApplicable(any(), anyInt(), anyOrNull(), any()))
- .thenReturn(ExitResult.Exit(immersiveTask.taskId, runOnStart))
- whenever(desktopMixedTransitionHandler
- .startLaunchTransition(anyInt(), any(), anyOrNull(), anyOrNull(), anyOrNull()))
- .thenReturn(transition)
-
- runOpenNewWindow(task)
-
- runOnStart.assertOnlyInvocation(transition)
- }
-
- private fun runOpenNewWindow(task: RunningTaskInfo) {
- markTaskVisible(task)
- task.baseActivity = mock(ComponentName::class.java)
- task.isFocused = true
- runningTasks.add(task)
- controller.openNewWindow(task)
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MULTI_INSTANCE_FEATURES)
- fun openInstance_fromFullscreenOpensInSplit() {
- setUpLandscapeDisplay()
- val task = setUpFullscreenTask()
- val taskToRequest = setUpFreeformTask()
- val optionsCaptor = ArgumentCaptor.forClass(Bundle::class.java)
- runOpenInstance(task, taskToRequest.taskId)
- verify(splitScreenController)
- .startTask(anyInt(), anyInt(), optionsCaptor.capture(), anyOrNull())
- assertThat(ActivityOptions.fromBundle(optionsCaptor.value).launchWindowingMode)
- .isEqualTo(WINDOWING_MODE_MULTI_WINDOW)
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MULTI_INSTANCE_FEATURES)
- fun openInstance_fromSplitOpensInSplit() {
- setUpLandscapeDisplay()
- val task = setUpSplitScreenTask()
- val taskToRequest = setUpFreeformTask()
- val optionsCaptor = ArgumentCaptor.forClass(Bundle::class.java)
- runOpenInstance(task, taskToRequest.taskId)
- verify(splitScreenController)
- .startTask(anyInt(), anyInt(), optionsCaptor.capture(), anyOrNull())
- assertThat(ActivityOptions.fromBundle(optionsCaptor.value).launchWindowingMode)
- .isEqualTo(WINDOWING_MODE_MULTI_WINDOW)
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MULTI_INSTANCE_FEATURES)
- fun openInstance_fromFreeformAddsNewWindow() {
- setUpLandscapeDisplay()
- val task = setUpFreeformTask()
- val taskToRequest = setUpFreeformTask()
- runOpenInstance(task, taskToRequest.taskId)
- verify(desktopMixedTransitionHandler).startLaunchTransition(anyInt(), any(), anyInt(),
- anyOrNull(), anyOrNull())
- val wct = getLatestDesktopMixedTaskWct(type = TRANSIT_TO_FRONT)
- assertThat(wct.hierarchyOps).hasSize(1)
- wct.assertReorderAt(index = 0, taskToRequest)
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MULTI_INSTANCE_FEATURES)
- fun openInstance_fromFreeform_minimizesIfNeeded() {
- setUpLandscapeDisplay()
- val freeformTasks = (1..MAX_TASK_LIMIT + 1).map { _ -> setUpFreeformTask() }
- val oldestTask = freeformTasks.first()
- val newestTask = freeformTasks.last()
-
- val transition = Binder()
- val wctCaptor = argumentCaptor<WindowContainerTransaction>()
- whenever(desktopMixedTransitionHandler.startLaunchTransition(anyInt(), wctCaptor.capture(),
- anyInt(), anyOrNull(), anyOrNull()
- ))
- .thenReturn(transition)
-
- runOpenInstance(newestTask, freeformTasks[1].taskId)
-
- val wct = wctCaptor.firstValue
- assertThat(wct.hierarchyOps.size).isEqualTo(2) // move-to-front + minimize
- wct.assertReorderAt(0, freeformTasks[1], toTop = true)
- wct.assertReorderAt(1, oldestTask, toTop = false)
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MULTI_INSTANCE_FEATURES)
- fun openInstance_fromFreeform_exitsImmersiveIfNeeded() {
- setUpLandscapeDisplay()
- val freeformTask = setUpFreeformTask()
- val immersiveTask = setUpFreeformTask()
- taskRepository.setTaskInFullImmersiveState(
- displayId = immersiveTask.displayId,
- taskId = immersiveTask.taskId,
- immersive = true
- )
- val runOnStartTransit = RunOnStartTransitionCallback()
- val transition = Binder()
- whenever(desktopMixedTransitionHandler.startLaunchTransition(anyInt(), any(), anyInt(),
- anyOrNull(), anyOrNull()
- ))
- .thenReturn(transition)
- whenever(mMockDesktopImmersiveController
- .exitImmersiveIfApplicable(
- any(), eq(DEFAULT_DISPLAY), eq(freeformTask.taskId), any()))
- .thenReturn(
- ExitResult.Exit(
- exitingTask = immersiveTask.taskId,
- runOnTransitionStart = runOnStartTransit,
- ))
-
- runOpenInstance(immersiveTask, freeformTask.taskId)
-
- verify(mMockDesktopImmersiveController)
- .exitImmersiveIfApplicable(any(), eq(immersiveTask.displayId), eq(freeformTask.taskId), any())
- runOnStartTransit.assertOnlyInvocation(transition)
- }
-
- private fun runOpenInstance(
- callingTask: RunningTaskInfo,
- requestedTaskId: Int
- ) {
- markTaskVisible(callingTask)
- callingTask.baseActivity = mock(ComponentName::class.java)
- callingTask.isFocused = true
- runningTasks.add(callingTask)
- controller.openInstance(callingTask, requestedTaskId)
- }
-
- @Test
- fun toggleBounds_togglesToStableBounds() {
- val bounds = Rect(0, 0, 100, 100)
- val task = setUpFreeformTask(DEFAULT_DISPLAY, bounds)
-
- controller.toggleDesktopTaskSize(
- task,
- ToggleTaskSizeInteraction(
- ToggleTaskSizeInteraction.Direction.MAXIMIZE,
- ToggleTaskSizeInteraction.Source.HEADER_BUTTON_TO_MAXIMIZE,
- InputMethod.TOUCH
- )
- )
+ val rectAfterEnd = Rect(100, 50, 500, 1150)
+ verify(transitions)
+ .startTransition(
+ eq(TRANSIT_CHANGE),
+ Mockito.argThat { wct ->
+ return@argThat wct.changes.any { (token, change) ->
+ change.configuration.windowConfiguration.bounds == rectAfterEnd
+ }
+ },
+ eq(null),
+ )
+ }
- // Assert bounds set to stable bounds
- val wct = getLatestToggleResizeDesktopTaskWct()
- assertThat(findBoundsChange(wct, task)).isEqualTo(STABLE_BOUNDS)
- verify(desktopModeEventLogger, times(1)).logTaskResizingEnded(
- ResizeTrigger.MAXIMIZE_BUTTON,
- InputMethod.TOUCH,
- task,
- STABLE_BOUNDS.width(),
- STABLE_BOUNDS.height(),
- displayController
- )
- }
-
- @Test
- @DisableFlags(Flags.FLAG_ENABLE_TILE_RESIZING)
- fun snapToHalfScreen_getSnapBounds_calculatesBoundsForResizable() {
- val bounds = Rect(100, 100, 300, 300)
- val task = setUpFreeformTask(DEFAULT_DISPLAY, bounds).apply {
- topActivityInfo = ActivityInfo().apply {
- screenOrientation = SCREEN_ORIENTATION_LANDSCAPE
- configuration.windowConfiguration.appBounds = bounds
- }
- isResizeable = true
- }
-
- val currentDragBounds = Rect(0, 100, 200, 300)
- val expectedBounds = Rect(
- STABLE_BOUNDS.left, STABLE_BOUNDS.top, STABLE_BOUNDS.right / 2, STABLE_BOUNDS.bottom
- )
+ @Test
+ fun onDesktopDragEnd_noIndicator_updatesTaskBounds() {
+ val task = setUpFreeformTask()
+ val spyController = spy(controller)
+ val mockSurface = mock(SurfaceControl::class.java)
+ val mockDisplayLayout = mock(DisplayLayout::class.java)
+ whenever(displayController.getDisplayLayout(task.displayId)).thenReturn(mockDisplayLayout)
+ whenever(mockDisplayLayout.stableInsets()).thenReturn(Rect(0, 100, 2000, 2000))
+ spyController.onDragPositioningMove(task, mockSurface, 200f, Rect(100, 200, 500, 1000))
+
+ val currentDragBounds = Rect(100, 200, 500, 1000)
+ whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator)
+ whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull()))
+ .thenReturn(DesktopModeVisualIndicator.IndicatorType.NO_INDICATOR)
+
+ spyController.onDragPositioningEnd(
+ task,
+ mockSurface,
+ Point(100, 200), /* position */
+ PointF(200f, 300f), /* inputCoordinate */
+ currentDragBounds, /* currentDragBounds */
+ Rect(0, 50, 2000, 2000) /* validDragArea */,
+ Rect() /* dragStartBounds */,
+ motionEvent,
+ desktopWindowDecoration,
+ )
- controller.snapToHalfScreen(task, mockSurface, currentDragBounds, SnapPosition.LEFT,
- ResizeTrigger.SNAP_LEFT_MENU, InputMethod.TOUCH, desktopWindowDecoration)
- // Assert bounds set to stable bounds
- val wct = getLatestToggleResizeDesktopTaskWct(currentDragBounds)
- assertThat(findBoundsChange(wct, task)).isEqualTo(expectedBounds)
- verify(desktopModeEventLogger, times(1)).logTaskResizingEnded(
- ResizeTrigger.SNAP_LEFT_MENU,
- InputMethod.TOUCH,
- task,
- expectedBounds.width(),
- expectedBounds.height(),
- displayController
- )
- }
-
- @Test
- @DisableFlags(Flags.FLAG_ENABLE_TILE_RESIZING)
- fun snapToHalfScreen_snapBoundsWhenAlreadySnapped_animatesSurfaceWithoutWCT() {
- // Set up task to already be in snapped-left bounds
- val bounds = Rect(
- STABLE_BOUNDS.left, STABLE_BOUNDS.top, STABLE_BOUNDS.right / 2, STABLE_BOUNDS.bottom
- )
- val task = setUpFreeformTask(DEFAULT_DISPLAY, bounds).apply {
- topActivityInfo = ActivityInfo().apply {
- screenOrientation = SCREEN_ORIENTATION_LANDSCAPE
- configuration.windowConfiguration.appBounds = bounds
- }
- isResizeable = true
- }
-
- // Attempt to snap left again
- val currentDragBounds = Rect(bounds).apply { offset(-100, 0) }
- controller.snapToHalfScreen(task, mockSurface, currentDragBounds, SnapPosition.LEFT,
- ResizeTrigger.SNAP_LEFT_MENU, InputMethod.TOUCH, desktopWindowDecoration)
- // Assert that task is NOT updated via WCT
- verify(toggleResizeDesktopTaskTransitionHandler, never()).startTransition(any(), any())
-
- // Assert that task leash is updated via Surface Animations
- verify(mReturnToDragStartAnimator).start(
- eq(task.taskId),
- eq(mockSurface),
- eq(currentDragBounds),
- eq(bounds),
- anyOrNull(),
- )
- verify(desktopModeEventLogger, times(1)).logTaskResizingEnded(
- ResizeTrigger.SNAP_LEFT_MENU,
- InputMethod.TOUCH,
- task,
- bounds.width(),
- bounds.height(),
- displayController
- )
- }
+ verify(transitions)
+ .startTransition(
+ eq(TRANSIT_CHANGE),
+ Mockito.argThat { wct ->
+ return@argThat wct.changes.any { (token, change) ->
+ change.configuration.windowConfiguration.bounds == currentDragBounds
+ }
+ },
+ eq(null),
+ )
+ }
- @Test
- @DisableFlags(Flags.FLAG_DISABLE_NON_RESIZABLE_APP_SNAP_RESIZING, Flags.FLAG_ENABLE_TILE_RESIZING)
- fun handleSnapResizingTaskOnDrag_nonResizable_snapsToHalfScreen() {
- val task = setUpFreeformTask(DEFAULT_DISPLAY, Rect(0, 0, 200, 100)).apply {
- isResizeable = false
+ @Test
+ fun onDesktopDragEnd_fullscreenIndicator_dragToExitDesktop() {
+ val task = setUpFreeformTask(bounds = Rect(0, 0, 100, 100))
+ val spyController = spy(controller)
+ val mockSurface = mock(SurfaceControl::class.java)
+ val mockDisplayLayout = mock(DisplayLayout::class.java)
+ val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!!
+ tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FULLSCREEN
+ whenever(displayController.getDisplayLayout(task.displayId)).thenReturn(mockDisplayLayout)
+ whenever(mockDisplayLayout.stableInsets()).thenReturn(Rect(0, 100, 2000, 2000))
+ whenever(mockDisplayLayout.getStableBounds(any())).thenAnswer { i ->
+ (i.arguments.first() as Rect).set(STABLE_BOUNDS)
+ }
+ whenever(DesktopModeStatus.shouldMaximizeWhenDragToTopEdge(context)).thenReturn(false)
+ whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator)
+ whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull()))
+ .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR)
+
+ // Drag move the task to the top edge
+ spyController.onDragPositioningMove(task, mockSurface, 200f, Rect(100, 200, 500, 1000))
+ spyController.onDragPositioningEnd(
+ task,
+ mockSurface,
+ Point(100, 50), /* position */
+ PointF(200f, 300f), /* inputCoordinate */
+ Rect(100, 50, 500, 1000), /* currentDragBounds */
+ Rect(0, 50, 2000, 2000) /* validDragArea */,
+ Rect() /* dragStartBounds */,
+ motionEvent,
+ desktopWindowDecoration,
+ )
+
+ // Assert the task exits desktop mode
+ val wct = getLatestExitDesktopWct()
+ assertThat(wct.changes[task.token.asBinder()]?.windowingMode)
+ .isEqualTo(WINDOWING_MODE_UNDEFINED)
}
- val preDragBounds = Rect(100, 100, 400, 500)
- val currentDragBounds = Rect(0, 100, 300, 500)
- val expectedBounds =
- Rect(STABLE_BOUNDS.left, STABLE_BOUNDS.top, STABLE_BOUNDS.right / 2, STABLE_BOUNDS.bottom)
- controller.handleSnapResizingTaskOnDrag(
+ @Test
+ fun onDesktopDragEnd_fullscreenIndicator_dragToMaximize() {
+ val task = setUpFreeformTask(bounds = Rect(0, 0, 100, 100))
+ val spyController = spy(controller)
+ val mockSurface = mock(SurfaceControl::class.java)
+ val mockDisplayLayout = mock(DisplayLayout::class.java)
+ val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!!
+ tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FULLSCREEN
+ whenever(displayController.getDisplayLayout(task.displayId)).thenReturn(mockDisplayLayout)
+ whenever(mockDisplayLayout.stableInsets()).thenReturn(Rect(0, 100, 2000, 2000))
+ whenever(mockDisplayLayout.getStableBounds(any())).thenAnswer { i ->
+ (i.arguments.first() as Rect).set(STABLE_BOUNDS)
+ }
+ whenever(DesktopModeStatus.shouldMaximizeWhenDragToTopEdge(context)).thenReturn(true)
+ whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator)
+ whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull()))
+ .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR)
+
+ // Drag move the task to the top edge
+ val currentDragBounds = Rect(100, 50, 500, 1000)
+ spyController.onDragPositioningMove(task, mockSurface, 200f, Rect(100, 200, 500, 1000))
+ spyController.onDragPositioningEnd(
+ task,
+ mockSurface,
+ Point(100, 50), /* position */
+ PointF(200f, 300f), /* inputCoordinate */
+ currentDragBounds,
+ Rect(0, 50, 2000, 2000) /* validDragArea */,
+ Rect() /* dragStartBounds */,
+ motionEvent,
+ desktopWindowDecoration,
+ )
- task, SnapPosition.LEFT, mockSurface, currentDragBounds, preDragBounds, motionEvent,
- desktopWindowDecoration
- )
- val wct = getLatestToggleResizeDesktopTaskWct(currentDragBounds)
- assertThat(findBoundsChange(wct, task)).isEqualTo(
- expectedBounds
- )
- verify(desktopModeEventLogger, times(1)).logTaskResizingStarted(
- ResizeTrigger.DRAG_LEFT,
- InputMethod.UNKNOWN_INPUT_METHOD,
- task,
- preDragBounds.width(),
- preDragBounds.height(),
- displayController
- )
- }
-
- @Test
- @EnableFlags(Flags.FLAG_DISABLE_NON_RESIZABLE_APP_SNAP_RESIZING)
- fun handleSnapResizingTaskOnDrag_nonResizable_startsRepositionAnimation() {
- val task = setUpFreeformTask(DEFAULT_DISPLAY, Rect(0, 0, 200, 100)).apply {
- isResizeable = false
- }
- val preDragBounds = Rect(100, 100, 400, 500)
- val currentDragBounds = Rect(0, 100, 300, 500)
-
- controller.handleSnapResizingTaskOnDrag(
- task, SnapPosition.LEFT, mockSurface, currentDragBounds, preDragBounds, motionEvent,
- desktopWindowDecoration)
- verify(mReturnToDragStartAnimator).start(
- eq(task.taskId),
- eq(mockSurface),
- eq(currentDragBounds),
- eq(preDragBounds),
- any(),
- )
- verify(desktopModeEventLogger, never()).logTaskResizingStarted(
- any(),
- any(),
- any(),
- any(),
- any(),
- any(),
- any()
- )
- }
-
- @Test
- @EnableFlags(
- Flags.FLAG_DISABLE_NON_RESIZABLE_APP_SNAP_RESIZING
- )
- fun handleInstantSnapResizingTask_nonResizable_animatorNotStartedAndShowsToast() {
- val taskBounds = Rect(0, 0, 200, 100)
- val task = setUpFreeformTask(DEFAULT_DISPLAY, taskBounds).apply {
- isResizeable = false
- }
-
- controller.handleInstantSnapResizingTask(
- task,
- SnapPosition.LEFT,
- ResizeTrigger.SNAP_LEFT_MENU,
- InputMethod.MOUSE,
- desktopWindowDecoration
- )
+ // Assert bounds set to stable bounds
+ val wct = getLatestToggleResizeDesktopTaskWct(currentDragBounds)
+ assertThat(findBoundsChange(wct, task)).isEqualTo(STABLE_BOUNDS)
+ // Assert event is properly logged
+ verify(desktopModeEventLogger, times(1))
+ .logTaskResizingStarted(
+ ResizeTrigger.DRAG_TO_TOP_RESIZE_TRIGGER,
+ InputMethod.UNKNOWN_INPUT_METHOD,
+ task,
+ task.configuration.windowConfiguration.bounds.width(),
+ task.configuration.windowConfiguration.bounds.height(),
+ displayController,
+ )
+ verify(desktopModeEventLogger, times(1))
+ .logTaskResizingEnded(
+ ResizeTrigger.DRAG_TO_TOP_RESIZE_TRIGGER,
+ InputMethod.UNKNOWN_INPUT_METHOD,
+ task,
+ STABLE_BOUNDS.width(),
+ STABLE_BOUNDS.height(),
+ displayController,
+ )
+ }
- // Assert that task is NOT updated via WCT
- verify(toggleResizeDesktopTaskTransitionHandler, never()).startTransition(any(), any())
- verify(mockToast).show()
- }
-
- @Test
- @EnableFlags(Flags.FLAG_DISABLE_NON_RESIZABLE_APP_SNAP_RESIZING)
- @DisableFlags(Flags.FLAG_ENABLE_TILE_RESIZING)
- fun handleInstantSnapResizingTask_resizable_snapsToHalfScreenAndNotShowToast() {
- val taskBounds = Rect(0, 0, 200, 100)
- val task = setUpFreeformTask(DEFAULT_DISPLAY, taskBounds).apply {
- isResizeable = true
- }
- val expectedBounds = Rect(
- STABLE_BOUNDS.left, STABLE_BOUNDS.top, STABLE_BOUNDS.right / 2, STABLE_BOUNDS.bottom
- )
+ @Test
+ fun onDesktopDragEnd_fullscreenIndicator_dragToMaximize_noBoundsChange() {
+ val task = setUpFreeformTask(bounds = STABLE_BOUNDS)
+ val spyController = spy(controller)
+ val mockSurface = mock(SurfaceControl::class.java)
+ val mockDisplayLayout = mock(DisplayLayout::class.java)
+ val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!!
+ tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FULLSCREEN
+ whenever(displayController.getDisplayLayout(task.displayId)).thenReturn(mockDisplayLayout)
+ whenever(mockDisplayLayout.stableInsets()).thenReturn(Rect(0, 100, 2000, 2000))
+ whenever(mockDisplayLayout.getStableBounds(any())).thenAnswer { i ->
+ (i.arguments.first() as Rect).set(STABLE_BOUNDS)
+ }
+ whenever(DesktopModeStatus.shouldMaximizeWhenDragToTopEdge(context)).thenReturn(true)
+ whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator)
+ whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull()))
+ .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR)
+
+ // Drag move the task to the top edge
+ val currentDragBounds = Rect(100, 50, 500, 1000)
+ spyController.onDragPositioningMove(task, mockSurface, 200f, Rect(100, 200, 500, 1000))
+ spyController.onDragPositioningEnd(
+ task,
+ mockSurface,
+ Point(100, 50), /* position */
+ PointF(200f, 300f), /* inputCoordinate */
+ currentDragBounds, /* currentDragBounds */
+ Rect(0, 50, 2000, 2000) /* validDragArea */,
+ Rect() /* dragStartBounds */,
+ motionEvent,
+ desktopWindowDecoration,
+ )
- controller.handleInstantSnapResizingTask(
- task, SnapPosition.LEFT, ResizeTrigger.SNAP_LEFT_MENU, InputMethod.MOUSE,
- desktopWindowDecoration
- )
+ // Assert that task is NOT updated via WCT
+ verify(toggleResizeDesktopTaskTransitionHandler, never()).startTransition(any(), any())
+ // Assert that task leash is updated via Surface Animations
+ verify(mReturnToDragStartAnimator)
+ .start(
+ eq(task.taskId),
+ eq(mockSurface),
+ eq(currentDragBounds),
+ eq(STABLE_BOUNDS),
+ anyOrNull(),
+ )
+ // Assert no event is logged
+ verify(desktopModeEventLogger, never())
+ .logTaskResizingStarted(any(), any(), any(), any(), any(), any(), any())
+ verify(desktopModeEventLogger, never())
+ .logTaskResizingEnded(any(), any(), any(), any(), any(), any(), any())
+ }
- // Assert bounds set to half of the stable bounds
- val wct = getLatestToggleResizeDesktopTaskWct(taskBounds)
- assertThat(findBoundsChange(wct, task)).isEqualTo(expectedBounds)
- verify(mockToast, never()).show()
- verify(desktopModeEventLogger, times(1)).logTaskResizingStarted(
- ResizeTrigger.SNAP_LEFT_MENU,
- InputMethod.MOUSE,
- task,
- taskBounds.width(),
- taskBounds.height(),
- displayController
- )
- verify(desktopModeEventLogger, times(1)).logTaskResizingEnded(
- ResizeTrigger.SNAP_LEFT_MENU,
- InputMethod.MOUSE,
- task,
- expectedBounds.width(),
- expectedBounds.height(),
- displayController
- )
- }
-
- @Test
- fun toggleBounds_togglesToCalculatedBoundsForNonResizable() {
- val bounds = Rect(0, 0, 200, 100)
- val task = setUpFreeformTask(DEFAULT_DISPLAY, bounds).apply {
- topActivityInfo = ActivityInfo().apply {
- screenOrientation = SCREEN_ORIENTATION_LANDSCAPE
- configuration.windowConfiguration.appBounds = bounds
- }
- appCompatTaskInfo.topActivityLetterboxAppWidth = bounds.width()
- appCompatTaskInfo.topActivityLetterboxAppHeight = bounds.height()
- isResizeable = false
- }
-
- // Bounds should be 1000 x 500, vertically centered in the 1000 x 1000 stable bounds
- val expectedBounds = Rect(STABLE_BOUNDS.left, 250, STABLE_BOUNDS.right, 750)
-
- controller.toggleDesktopTaskSize(
- task,
- ToggleTaskSizeInteraction(
- ToggleTaskSizeInteraction.Direction.MAXIMIZE,
- ToggleTaskSizeInteraction.Source.HEADER_BUTTON_TO_MAXIMIZE,
- InputMethod.TOUCH
- )
- )
+ @Test
+ fun enterSplit_freeformTaskIsMovedToSplit() {
+ val task1 = setUpFreeformTask()
+ val task2 = setUpFreeformTask()
+ val task3 = setUpFreeformTask()
+
+ task1.isFocused = false
+ task2.isFocused = true
+ task3.isFocused = false
+
+ controller.enterSplit(DEFAULT_DISPLAY, leftOrTop = false)
+
+ verify(splitScreenController)
+ .requestEnterSplitSelect(
+ eq(task2),
+ any(),
+ eq(SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT),
+ eq(task2.configuration.windowConfiguration.bounds),
+ )
+ }
- // Assert bounds set to stable bounds
- val wct = getLatestToggleResizeDesktopTaskWct()
- assertThat(findBoundsChange(wct, task)).isEqualTo(expectedBounds)
- verify(desktopModeEventLogger, times(1)).logTaskResizingEnded(
- ResizeTrigger.MAXIMIZE_BUTTON,
- InputMethod.TOUCH,
- task,
- expectedBounds.width(),
- expectedBounds.height(),
- displayController
- )
- }
-
- @Test
- fun toggleBounds_lastBoundsBeforeMaximizeSaved() {
- val bounds = Rect(0, 0, 100, 100)
- val task = setUpFreeformTask(DEFAULT_DISPLAY, bounds)
-
- controller.toggleDesktopTaskSize(
- task,
- ToggleTaskSizeInteraction(
- ToggleTaskSizeInteraction.Direction.MAXIMIZE,
- ToggleTaskSizeInteraction.Source.HEADER_BUTTON_TO_MAXIMIZE,
- InputMethod.TOUCH
- )
- )
+ @Test
+ fun enterSplit_onlyVisibleNonMinimizedTask_removesWallpaperActivity() {
+ val task1 = setUpFreeformTask()
+ val task2 = setUpFreeformTask()
+ val task3 = setUpFreeformTask()
+ val wallpaperToken = MockToken().token()
+
+ task1.isFocused = false
+ task2.isFocused = true
+ task3.isFocused = false
+ taskRepository.wallpaperActivityToken = wallpaperToken
+ taskRepository.minimizeTask(DEFAULT_DISPLAY, task1.taskId)
+ taskRepository.updateTask(DEFAULT_DISPLAY, task3.taskId, isVisible = false)
+
+ controller.enterSplit(DEFAULT_DISPLAY, leftOrTop = false)
+
+ val wctArgument = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
+ verify(splitScreenController)
+ .requestEnterSplitSelect(
+ eq(task2),
+ wctArgument.capture(),
+ eq(SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT),
+ eq(task2.configuration.windowConfiguration.bounds),
+ )
+ // Removes wallpaper activity when leaving desktop
+ wctArgument.value.assertRemoveAt(index = 0, wallpaperToken)
+ }
- assertThat(taskRepository.removeBoundsBeforeMaximize(task.taskId)).isEqualTo(bounds)
- verify(desktopModeEventLogger, never()).logTaskResizingEnded(
- any(), any(), any(), any(),
- any(), any(), any()
- )
- }
-
- @Test
- fun toggleBounds_togglesFromStableBoundsToLastBoundsBeforeMaximize() {
- val boundsBeforeMaximize = Rect(0, 0, 100, 100)
- val task = setUpFreeformTask(DEFAULT_DISPLAY, boundsBeforeMaximize)
-
- // Maximize
- controller.toggleDesktopTaskSize(
- task,
- ToggleTaskSizeInteraction(
- ToggleTaskSizeInteraction.Direction.MAXIMIZE,
- ToggleTaskSizeInteraction.Source.HEADER_BUTTON_TO_MAXIMIZE,
- InputMethod.TOUCH
- )
- )
- task.configuration.windowConfiguration.bounds.set(STABLE_BOUNDS)
-
- // Restore
- controller.toggleDesktopTaskSize(
- task,
- ToggleTaskSizeInteraction(
- ToggleTaskSizeInteraction.Direction.RESTORE,
- ToggleTaskSizeInteraction.Source.HEADER_BUTTON_TO_RESTORE,
- InputMethod.TOUCH
- )
- )
+ @Test
+ fun enterSplit_multipleVisibleNonMinimizedTasks_removesWallpaperActivity() {
+ val task1 = setUpFreeformTask()
+ val task2 = setUpFreeformTask()
+ val task3 = setUpFreeformTask()
+ val wallpaperToken = MockToken().token()
+
+ task1.isFocused = false
+ task2.isFocused = true
+ task3.isFocused = false
+ taskRepository.wallpaperActivityToken = wallpaperToken
+
+ controller.enterSplit(DEFAULT_DISPLAY, leftOrTop = false)
+
+ val wctArgument = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
+ verify(splitScreenController)
+ .requestEnterSplitSelect(
+ eq(task2),
+ wctArgument.capture(),
+ eq(SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT),
+ eq(task2.configuration.windowConfiguration.bounds),
+ )
+ // Does not remove wallpaper activity, as desktop still has visible desktop tasks
+ assertThat(wctArgument.value.hierarchyOps).isEmpty()
+ }
- // Assert bounds set to last bounds before maximize
- val wct = getLatestToggleResizeDesktopTaskWct()
- assertThat(findBoundsChange(wct, task)).isEqualTo(boundsBeforeMaximize)
- verify(desktopModeEventLogger, times(1)).logTaskResizingEnded(
- ResizeTrigger.MAXIMIZE_BUTTON,
- InputMethod.TOUCH,
- task,
- boundsBeforeMaximize.width(),
- boundsBeforeMaximize.height(),
- displayController
- )
- }
-
- @Test
- fun toggleBounds_togglesFromStableBoundsToLastBoundsBeforeMaximize_nonResizeableEqualWidth() {
- val boundsBeforeMaximize = Rect(0, 0, 100, 100)
- val task = setUpFreeformTask(DEFAULT_DISPLAY, boundsBeforeMaximize).apply {
- isResizeable = false
- }
-
- // Maximize
- controller.toggleDesktopTaskSize(
- task,
- ToggleTaskSizeInteraction(
- ToggleTaskSizeInteraction.Direction.MAXIMIZE,
- ToggleTaskSizeInteraction.Source.HEADER_BUTTON_TO_MAXIMIZE,
- InputMethod.TOUCH
- )
- )
- task.configuration.windowConfiguration.bounds.set(STABLE_BOUNDS.left,
- boundsBeforeMaximize.top, STABLE_BOUNDS.right, boundsBeforeMaximize.bottom)
-
- // Restore
- controller.toggleDesktopTaskSize(
- task,
- ToggleTaskSizeInteraction(
- ToggleTaskSizeInteraction.Direction.RESTORE,
- ToggleTaskSizeInteraction.Source.HEADER_BUTTON_TO_RESTORE,
- InputMethod.TOUCH
- )
- )
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MULTI_INSTANCE_FEATURES)
+ fun newWindow_fromFullscreenOpensInSplit() {
+ setUpLandscapeDisplay()
+ val task = setUpFullscreenTask()
+ val optionsCaptor = ArgumentCaptor.forClass(Bundle::class.java)
+ runOpenNewWindow(task)
+ verify(splitScreenController)
+ .startIntent(
+ any(),
+ anyInt(),
+ any(),
+ any(),
+ optionsCaptor.capture(),
+ anyOrNull(),
+ eq(true),
+ eq(SPLIT_INDEX_UNDEFINED),
+ )
+ assertThat(ActivityOptions.fromBundle(optionsCaptor.value).launchWindowingMode)
+ .isEqualTo(WINDOWING_MODE_MULTI_WINDOW)
+ }
- // Assert bounds set to last bounds before maximize
- val wct = getLatestToggleResizeDesktopTaskWct()
- assertThat(findBoundsChange(wct, task)).isEqualTo(boundsBeforeMaximize)
- verify(desktopModeEventLogger, times(1)).logTaskResizingEnded(
- ResizeTrigger.MAXIMIZE_BUTTON,
- InputMethod.TOUCH,
- task,
- boundsBeforeMaximize.width(),
- boundsBeforeMaximize.height(),
- displayController
- )
- }
-
- @Test
- fun toggleBounds_togglesFromStableBoundsToLastBoundsBeforeMaximize_nonResizeableEqualHeight() {
- val boundsBeforeMaximize = Rect(0, 0, 100, 100)
- val task = setUpFreeformTask(DEFAULT_DISPLAY, boundsBeforeMaximize).apply {
- isResizeable = false
- }
-
- // Maximize
- controller.toggleDesktopTaskSize(
- task,
- ToggleTaskSizeInteraction(
- ToggleTaskSizeInteraction.Direction.MAXIMIZE,
- ToggleTaskSizeInteraction.Source.HEADER_BUTTON_TO_MAXIMIZE,
- InputMethod.TOUCH
- )
- )
- task.configuration.windowConfiguration.bounds.set(boundsBeforeMaximize.left,
- STABLE_BOUNDS.top, boundsBeforeMaximize.right, STABLE_BOUNDS.bottom)
-
- // Restore
- controller.toggleDesktopTaskSize(
- task,
- ToggleTaskSizeInteraction(
- ToggleTaskSizeInteraction.Direction.RESTORE,
- ToggleTaskSizeInteraction.Source.HEADER_BUTTON_TO_RESTORE,
- InputMethod.TOUCH
- )
- )
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MULTI_INSTANCE_FEATURES)
+ fun newWindow_fromSplitOpensInSplit() {
+ setUpLandscapeDisplay()
+ val task = setUpSplitScreenTask()
+ val optionsCaptor = ArgumentCaptor.forClass(Bundle::class.java)
+ runOpenNewWindow(task)
+ verify(splitScreenController)
+ .startIntent(
+ any(),
+ anyInt(),
+ any(),
+ any(),
+ optionsCaptor.capture(),
+ anyOrNull(),
+ eq(true),
+ eq(SPLIT_INDEX_UNDEFINED),
+ )
+ assertThat(ActivityOptions.fromBundle(optionsCaptor.value).launchWindowingMode)
+ .isEqualTo(WINDOWING_MODE_MULTI_WINDOW)
+ }
- // Assert bounds set to last bounds before maximize
- val wct = getLatestToggleResizeDesktopTaskWct()
- assertThat(findBoundsChange(wct, task)).isEqualTo(boundsBeforeMaximize)
- verify(desktopModeEventLogger, times(1)).logTaskResizingEnded(
- ResizeTrigger.MAXIMIZE_BUTTON,
- InputMethod.TOUCH,
- task,
- boundsBeforeMaximize.width(),
- boundsBeforeMaximize.height(),
- displayController
- )
- }
-
- @Test
- fun toggleBounds_removesLastBoundsBeforeMaximizeAfterRestoringBounds() {
- val boundsBeforeMaximize = Rect(0, 0, 100, 100)
- val task = setUpFreeformTask(DEFAULT_DISPLAY, boundsBeforeMaximize)
-
- // Maximize
- controller.toggleDesktopTaskSize(
- task,
- ToggleTaskSizeInteraction(
- ToggleTaskSizeInteraction.Direction.MAXIMIZE,
- ToggleTaskSizeInteraction.Source.HEADER_BUTTON_TO_MAXIMIZE,
- InputMethod.TOUCH
- )
- )
- task.configuration.windowConfiguration.bounds.set(STABLE_BOUNDS)
-
- // Restore
- controller.toggleDesktopTaskSize(
- task,
- ToggleTaskSizeInteraction(
- ToggleTaskSizeInteraction.Direction.RESTORE,
- ToggleTaskSizeInteraction.Source.HEADER_BUTTON_TO_RESTORE,
- InputMethod.TOUCH
- )
- )
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MULTI_INSTANCE_FEATURES)
+ fun newWindow_fromFreeformAddsNewWindow() {
+ setUpLandscapeDisplay()
+ val task = setUpFreeformTask()
+ val wctCaptor = argumentCaptor<WindowContainerTransaction>()
+ val transition = Binder()
+ whenever(
+ mMockDesktopImmersiveController.exitImmersiveIfApplicable(
+ any(),
+ anyInt(),
+ anyOrNull(),
+ any(),
+ )
+ )
+ .thenReturn(ExitResult.NoExit)
+ whenever(
+ desktopMixedTransitionHandler.startLaunchTransition(
+ anyInt(),
+ any(),
+ anyOrNull(),
+ anyOrNull(),
+ anyOrNull(),
+ )
+ )
+ .thenReturn(transition)
+
+ runOpenNewWindow(task)
+
+ verify(desktopMixedTransitionHandler)
+ .startLaunchTransition(
+ anyInt(),
+ wctCaptor.capture(),
+ anyOrNull(),
+ anyOrNull(),
+ anyOrNull(),
+ )
+ assertThat(
+ ActivityOptions.fromBundle(wctCaptor.firstValue.hierarchyOps[0].launchOptions)
+ .launchWindowingMode
+ )
+ .isEqualTo(WINDOWING_MODE_FREEFORM)
+ }
- // Assert last bounds before maximize removed after use
- assertThat(taskRepository.removeBoundsBeforeMaximize(task.taskId)).isNull()
- verify(desktopModeEventLogger, times(1)).logTaskResizingEnded(
- ResizeTrigger.MAXIMIZE_BUTTON,
- InputMethod.TOUCH,
- task,
- boundsBeforeMaximize.width(),
- boundsBeforeMaximize.height(),
- displayController
- )
- }
-
- @Test
- fun onUnhandledDrag_newFreeformIntent() {
- testOnUnhandledDrag(DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR,
- PointF(1200f, 700f),
- Rect(240, 700, 2160, 1900))
- }
-
- @Test
- fun onUnhandledDrag_newFreeformIntentSplitLeft() {
- testOnUnhandledDrag(DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_LEFT_INDICATOR,
- PointF(50f, 700f),
- Rect(0, 0, 500, 1000))
- }
-
- @Test
- fun onUnhandledDrag_newFreeformIntentSplitRight() {
- testOnUnhandledDrag(DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_RIGHT_INDICATOR,
- PointF(2500f, 700f),
- Rect(500, 0, 1000, 1000))
- }
-
- @Test
- fun onUnhandledDrag_newFullscreenIntent() {
- testOnUnhandledDrag(DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR,
- PointF(1200f, 50f),
- Rect())
- }
-
- @Test
- fun shellController_registersUserChangeListener() {
- verify(shellController, times(2)).addUserChangeListener(any())
- }
-
- @Test
- @EnableFlags(FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP)
- fun onTaskInfoChanged_inImmersiveUnrequestsImmersive_exits() {
- val task = setUpFreeformTask(DEFAULT_DISPLAY)
- taskRepository.setTaskInFullImmersiveState(DEFAULT_DISPLAY, task.taskId, immersive = true)
-
- task.requestedVisibleTypes = WindowInsets.Type.statusBars()
- controller.onTaskInfoChanged(task)
-
- verify(mMockDesktopImmersiveController).moveTaskToNonImmersive(eq(task), any())
- }
-
- @Test
- @EnableFlags(FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP)
- fun onTaskInfoChanged_notInImmersiveUnrequestsImmersive_noReExit() {
- val task = setUpFreeformTask(DEFAULT_DISPLAY)
- taskRepository.setTaskInFullImmersiveState(DEFAULT_DISPLAY, task.taskId, immersive = false)
-
- task.requestedVisibleTypes = WindowInsets.Type.statusBars()
- controller.onTaskInfoChanged(task)
-
- verify(mMockDesktopImmersiveController, never()).moveTaskToNonImmersive(eq(task), any())
- }
-
- @Test
- @EnableFlags(FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP)
- fun onTaskInfoChanged_inImmersiveUnrequestsImmersive_inRecentsTransition_noExit() {
- val task = setUpFreeformTask(DEFAULT_DISPLAY)
- taskRepository.setTaskInFullImmersiveState(DEFAULT_DISPLAY, task.taskId, immersive = true)
- recentsTransitionStateListener.onTransitionStateChanged(TRANSITION_STATE_REQUESTED)
-
- task.requestedVisibleTypes = WindowInsets.Type.statusBars()
- controller.onTaskInfoChanged(task)
-
- verify(mMockDesktopImmersiveController, never()).moveTaskToNonImmersive(eq(task), any())
- }
-
- @Test
- fun moveTaskToDesktop_background_attemptsImmersiveExit() {
- val task = setUpFreeformTask(background = true)
- val wct = WindowContainerTransaction()
- val runOnStartTransit = RunOnStartTransitionCallback()
- val transition = Binder()
- whenever(mMockDesktopImmersiveController
- .exitImmersiveIfApplicable(eq(wct), eq(task.displayId), eq(task.taskId), any()))
- .thenReturn(
- ExitResult.Exit(
- exitingTask = 5,
- runOnTransitionStart = runOnStartTransit,
- ))
- whenever(enterDesktopTransitionHandler.moveToDesktop(wct, UNKNOWN)).thenReturn(transition)
-
- controller.moveTaskToDesktop(taskId = task.taskId, wct = wct, transitionSource = UNKNOWN)
-
- verify(mMockDesktopImmersiveController)
- .exitImmersiveIfApplicable(eq(wct), eq(task.displayId), eq(task.taskId), any())
- runOnStartTransit.assertOnlyInvocation(transition)
- }
-
- @Test
- fun moveTaskToDesktop_foreground_attemptsImmersiveExit() {
- val task = setUpFreeformTask(background = false)
- val wct = WindowContainerTransaction()
- val runOnStartTransit = RunOnStartTransitionCallback()
- val transition = Binder()
- whenever(mMockDesktopImmersiveController
- .exitImmersiveIfApplicable(eq(wct), eq(task.displayId), eq(task.taskId), any()))
- .thenReturn(
- ExitResult.Exit(
- exitingTask = 5,
- runOnTransitionStart = runOnStartTransit,
- ))
- whenever(enterDesktopTransitionHandler.moveToDesktop(wct, UNKNOWN)).thenReturn(transition)
-
- controller.moveTaskToDesktop(taskId = task.taskId, wct = wct, transitionSource = UNKNOWN)
-
- verify(mMockDesktopImmersiveController)
- .exitImmersiveIfApplicable(eq(wct), eq(task.displayId), eq(task.taskId), any())
- runOnStartTransit.assertOnlyInvocation(transition)
- }
-
- @Test
- fun moveTaskToFront_background_attemptsImmersiveExit() {
- val task = setUpFreeformTask(background = true)
- val runOnStartTransit = RunOnStartTransitionCallback()
- val transition = Binder()
- whenever(mMockDesktopImmersiveController
- .exitImmersiveIfApplicable(any(), eq(task.displayId), eq(task.taskId), any()))
- .thenReturn(
- ExitResult.Exit(
- exitingTask = 5,
- runOnTransitionStart = runOnStartTransit,
- ))
- whenever(desktopMixedTransitionHandler
- .startLaunchTransition(any(), any(), anyInt(), anyOrNull(), anyOrNull()))
- .thenReturn(transition)
-
- controller.moveTaskToFront(task.taskId, remoteTransition = null)
-
- verify(mMockDesktopImmersiveController)
- .exitImmersiveIfApplicable(any(), eq(task.displayId), eq(task.taskId), any())
- runOnStartTransit.assertOnlyInvocation(transition)
- }
-
- @Test
- fun moveTaskToFront_foreground_attemptsImmersiveExit() {
- val task = setUpFreeformTask(background = false)
- val runOnStartTransit = RunOnStartTransitionCallback()
- val transition = Binder()
- whenever(mMockDesktopImmersiveController
- .exitImmersiveIfApplicable(any(), eq(task.displayId), eq(task.taskId), any()))
- .thenReturn(
- ExitResult.Exit(
- exitingTask = 5,
- runOnTransitionStart = runOnStartTransit,
- ))
- whenever(desktopMixedTransitionHandler
- .startLaunchTransition(any(), any(), eq(task.taskId), anyOrNull(), anyOrNull()))
- .thenReturn(transition)
-
- controller.moveTaskToFront(task.taskId, remoteTransition = null)
-
- verify(mMockDesktopImmersiveController)
- .exitImmersiveIfApplicable(any(), eq(task.displayId), eq(task.taskId), any())
- runOnStartTransit.assertOnlyInvocation(transition)
- }
-
- @Test
- fun handleRequest_freeformLaunchToDesktop_attemptsImmersiveExit() {
- markTaskVisible(setUpFreeformTask())
- val task = setUpFreeformTask()
- markTaskVisible(task)
- val binder = Binder()
-
- controller.handleRequest(binder, createTransition(task))
-
- verify(mMockDesktopImmersiveController)
- .exitImmersiveIfApplicable(eq(binder), any(), eq(task.displayId), any())
- }
-
- @Test
- fun handleRequest_fullscreenLaunchToDesktop_attemptsImmersiveExit() {
- setUpFreeformTask()
- val task = setUpFullscreenTask()
- val binder = Binder()
-
- controller.handleRequest(binder, createTransition(task))
-
- verify(mMockDesktopImmersiveController)
- .exitImmersiveIfApplicable(eq(binder), any(), eq(task.displayId), any())
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP)
- fun shouldPlayDesktopAnimation_notShowingDesktop_doesNotPlay() {
- val triggerTask = setUpFullscreenTask(displayId = 5)
- taskRepository.setTaskInFullImmersiveState(
- displayId = triggerTask.displayId,
- taskId = triggerTask.taskId,
- immersive = true
- )
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MULTI_INSTANCE_FEATURES)
+ fun newWindow_fromFreeform_exitsImmersiveIfNeeded() {
+ setUpLandscapeDisplay()
+ val immersiveTask = setUpFreeformTask()
+ val task = setUpFreeformTask()
+ val runOnStart = RunOnStartTransitionCallback()
+ val transition = Binder()
+ whenever(
+ mMockDesktopImmersiveController.exitImmersiveIfApplicable(
+ any(),
+ anyInt(),
+ anyOrNull(),
+ any(),
+ )
+ )
+ .thenReturn(ExitResult.Exit(immersiveTask.taskId, runOnStart))
+ whenever(
+ desktopMixedTransitionHandler.startLaunchTransition(
+ anyInt(),
+ any(),
+ anyOrNull(),
+ anyOrNull(),
+ anyOrNull(),
+ )
+ )
+ .thenReturn(transition)
+
+ runOpenNewWindow(task)
+
+ runOnStart.assertOnlyInvocation(transition)
+ }
- assertThat(controller.shouldPlayDesktopAnimation(
- TransitionRequestInfo(TRANSIT_OPEN, triggerTask, /* remoteTransition= */ null)
- )).isFalse()
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP)
- fun shouldPlayDesktopAnimation_notOpening_doesNotPlay() {
- val triggerTask = setUpFreeformTask(displayId = 5)
- taskRepository.setTaskInFullImmersiveState(
- displayId = triggerTask.displayId,
- taskId = triggerTask.taskId,
- immersive = true
- )
+ private fun runOpenNewWindow(task: RunningTaskInfo) {
+ markTaskVisible(task)
+ task.baseActivity = mock(ComponentName::class.java)
+ task.isFocused = true
+ runningTasks.add(task)
+ controller.openNewWindow(task)
+ }
- assertThat(controller.shouldPlayDesktopAnimation(
- TransitionRequestInfo(TRANSIT_CHANGE, triggerTask, /* remoteTransition= */ null)
- )).isFalse()
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP)
- fun shouldPlayDesktopAnimation_notImmersive_doesNotPlay() {
- val triggerTask = setUpFreeformTask(displayId = 5)
- taskRepository.setTaskInFullImmersiveState(
- displayId = triggerTask.displayId,
- taskId = triggerTask.taskId,
- immersive = false
- )
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MULTI_INSTANCE_FEATURES)
+ fun openInstance_fromFullscreenOpensInSplit() {
+ setUpLandscapeDisplay()
+ val task = setUpFullscreenTask()
+ val taskToRequest = setUpFreeformTask()
+ val optionsCaptor = ArgumentCaptor.forClass(Bundle::class.java)
+ runOpenInstance(task, taskToRequest.taskId)
+ verify(splitScreenController)
+ .startTask(anyInt(), anyInt(), optionsCaptor.capture(), anyOrNull())
+ assertThat(ActivityOptions.fromBundle(optionsCaptor.value).launchWindowingMode)
+ .isEqualTo(WINDOWING_MODE_MULTI_WINDOW)
+ }
- assertThat(controller.shouldPlayDesktopAnimation(
- TransitionRequestInfo(TRANSIT_OPEN, triggerTask, /* remoteTransition= */ null)
- )).isFalse()
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP)
- fun shouldPlayDesktopAnimation_fullscreenEntersDesktop_plays() {
- // At least one freeform task to be in a desktop.
- val existingTask = setUpFreeformTask(displayId = 5)
- val triggerTask = setUpFullscreenTask(displayId = 5)
- assertThat(controller.isDesktopModeShowing(triggerTask.displayId)).isTrue()
- taskRepository.setTaskInFullImmersiveState(
- displayId = existingTask.displayId,
- taskId = existingTask.taskId,
- immersive = true
- )
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MULTI_INSTANCE_FEATURES)
+ fun openInstance_fromSplitOpensInSplit() {
+ setUpLandscapeDisplay()
+ val task = setUpSplitScreenTask()
+ val taskToRequest = setUpFreeformTask()
+ val optionsCaptor = ArgumentCaptor.forClass(Bundle::class.java)
+ runOpenInstance(task, taskToRequest.taskId)
+ verify(splitScreenController)
+ .startTask(anyInt(), anyInt(), optionsCaptor.capture(), anyOrNull())
+ assertThat(ActivityOptions.fromBundle(optionsCaptor.value).launchWindowingMode)
+ .isEqualTo(WINDOWING_MODE_MULTI_WINDOW)
+ }
- assertThat(
- controller.shouldPlayDesktopAnimation(
- TransitionRequestInfo(TRANSIT_OPEN, triggerTask, /* remoteTransition= */ null)
- )
- ).isTrue()
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP)
- fun shouldPlayDesktopAnimation_fullscreenStaysFullscreen_doesNotPlay() {
- val triggerTask = setUpFullscreenTask(displayId = 5)
- assertThat(controller.isDesktopModeShowing(triggerTask.displayId)).isFalse()
-
- assertThat(controller.shouldPlayDesktopAnimation(
- TransitionRequestInfo(TRANSIT_OPEN, triggerTask, /* remoteTransition= */ null)
- )).isFalse()
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP)
- fun shouldPlayDesktopAnimation_freeformStaysInDesktop_plays() {
- // At least one freeform task to be in a desktop.
- val existingTask = setUpFreeformTask(displayId = 5)
- val triggerTask = setUpFreeformTask(displayId = 5, active = false)
- assertThat(controller.isDesktopModeShowing(triggerTask.displayId)).isTrue()
- taskRepository.setTaskInFullImmersiveState(
- displayId = existingTask.displayId,
- taskId = existingTask.taskId,
- immersive = true
- )
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MULTI_INSTANCE_FEATURES)
+ fun openInstance_fromFreeformAddsNewWindow() {
+ setUpLandscapeDisplay()
+ val task = setUpFreeformTask()
+ val taskToRequest = setUpFreeformTask()
+ runOpenInstance(task, taskToRequest.taskId)
+ verify(desktopMixedTransitionHandler)
+ .startLaunchTransition(anyInt(), any(), anyInt(), anyOrNull(), anyOrNull())
+ val wct = getLatestDesktopMixedTaskWct(type = TRANSIT_TO_FRONT)
+ assertThat(wct.hierarchyOps).hasSize(1)
+ wct.assertReorderAt(index = 0, taskToRequest)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MULTI_INSTANCE_FEATURES)
+ fun openInstance_fromFreeform_minimizesIfNeeded() {
+ setUpLandscapeDisplay()
+ val freeformTasks = (1..MAX_TASK_LIMIT + 1).map { _ -> setUpFreeformTask() }
+ val oldestTask = freeformTasks.first()
+ val newestTask = freeformTasks.last()
+
+ val transition = Binder()
+ val wctCaptor = argumentCaptor<WindowContainerTransaction>()
+ whenever(
+ desktopMixedTransitionHandler.startLaunchTransition(
+ anyInt(),
+ wctCaptor.capture(),
+ anyInt(),
+ anyOrNull(),
+ anyOrNull(),
+ )
+ )
+ .thenReturn(transition)
+
+ runOpenInstance(newestTask, freeformTasks[1].taskId)
+
+ val wct = wctCaptor.firstValue
+ assertThat(wct.hierarchyOps.size).isEqualTo(2) // move-to-front + minimize
+ wct.assertReorderAt(0, freeformTasks[1], toTop = true)
+ wct.assertReorderAt(1, oldestTask, toTop = false)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MULTI_INSTANCE_FEATURES)
+ fun openInstance_fromFreeform_exitsImmersiveIfNeeded() {
+ setUpLandscapeDisplay()
+ val freeformTask = setUpFreeformTask()
+ val immersiveTask = setUpFreeformTask()
+ taskRepository.setTaskInFullImmersiveState(
+ displayId = immersiveTask.displayId,
+ taskId = immersiveTask.taskId,
+ immersive = true,
+ )
+ val runOnStartTransit = RunOnStartTransitionCallback()
+ val transition = Binder()
+ whenever(
+ desktopMixedTransitionHandler.startLaunchTransition(
+ anyInt(),
+ any(),
+ anyInt(),
+ anyOrNull(),
+ anyOrNull(),
+ )
+ )
+ .thenReturn(transition)
+ whenever(
+ mMockDesktopImmersiveController.exitImmersiveIfApplicable(
+ any(),
+ eq(DEFAULT_DISPLAY),
+ eq(freeformTask.taskId),
+ any(),
+ )
+ )
+ .thenReturn(
+ ExitResult.Exit(
+ exitingTask = immersiveTask.taskId,
+ runOnTransitionStart = runOnStartTransit,
+ )
+ )
+
+ runOpenInstance(immersiveTask, freeformTask.taskId)
+
+ verify(mMockDesktopImmersiveController)
+ .exitImmersiveIfApplicable(
+ any(),
+ eq(immersiveTask.displayId),
+ eq(freeformTask.taskId),
+ any(),
+ )
+ runOnStartTransit.assertOnlyInvocation(transition)
+ }
+
+ private fun runOpenInstance(callingTask: RunningTaskInfo, requestedTaskId: Int) {
+ markTaskVisible(callingTask)
+ callingTask.baseActivity = mock(ComponentName::class.java)
+ callingTask.isFocused = true
+ runningTasks.add(callingTask)
+ controller.openInstance(callingTask, requestedTaskId)
+ }
+
+ @Test
+ fun toggleBounds_togglesToStableBounds() {
+ val bounds = Rect(0, 0, 100, 100)
+ val task = setUpFreeformTask(DEFAULT_DISPLAY, bounds)
+
+ controller.toggleDesktopTaskSize(
+ task,
+ ToggleTaskSizeInteraction(
+ ToggleTaskSizeInteraction.Direction.MAXIMIZE,
+ ToggleTaskSizeInteraction.Source.HEADER_BUTTON_TO_MAXIMIZE,
+ InputMethod.TOUCH,
+ ),
+ )
+
+ // Assert bounds set to stable bounds
+ val wct = getLatestToggleResizeDesktopTaskWct()
+ assertThat(findBoundsChange(wct, task)).isEqualTo(STABLE_BOUNDS)
+ verify(desktopModeEventLogger, times(1))
+ .logTaskResizingEnded(
+ ResizeTrigger.MAXIMIZE_BUTTON,
+ InputMethod.TOUCH,
+ task,
+ STABLE_BOUNDS.width(),
+ STABLE_BOUNDS.height(),
+ displayController,
+ )
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_TILE_RESIZING)
+ fun snapToHalfScreen_getSnapBounds_calculatesBoundsForResizable() {
+ val bounds = Rect(100, 100, 300, 300)
+ val task =
+ setUpFreeformTask(DEFAULT_DISPLAY, bounds).apply {
+ topActivityInfo =
+ ActivityInfo().apply {
+ screenOrientation = SCREEN_ORIENTATION_LANDSCAPE
+ configuration.windowConfiguration.appBounds = bounds
+ }
+ isResizeable = true
+ }
+
+ val currentDragBounds = Rect(0, 100, 200, 300)
+ val expectedBounds =
+ Rect(
+ STABLE_BOUNDS.left,
+ STABLE_BOUNDS.top,
+ STABLE_BOUNDS.right / 2,
+ STABLE_BOUNDS.bottom,
+ )
+
+ controller.snapToHalfScreen(
+ task,
+ mockSurface,
+ currentDragBounds,
+ SnapPosition.LEFT,
+ ResizeTrigger.SNAP_LEFT_MENU,
+ InputMethod.TOUCH,
+ desktopWindowDecoration,
+ )
+ // Assert bounds set to stable bounds
+ val wct = getLatestToggleResizeDesktopTaskWct(currentDragBounds)
+ assertThat(findBoundsChange(wct, task)).isEqualTo(expectedBounds)
+ verify(desktopModeEventLogger, times(1))
+ .logTaskResizingEnded(
+ ResizeTrigger.SNAP_LEFT_MENU,
+ InputMethod.TOUCH,
+ task,
+ expectedBounds.width(),
+ expectedBounds.height(),
+ displayController,
+ )
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_TILE_RESIZING)
+ fun snapToHalfScreen_snapBoundsWhenAlreadySnapped_animatesSurfaceWithoutWCT() {
+ // Set up task to already be in snapped-left bounds
+ val bounds =
+ Rect(
+ STABLE_BOUNDS.left,
+ STABLE_BOUNDS.top,
+ STABLE_BOUNDS.right / 2,
+ STABLE_BOUNDS.bottom,
+ )
+ val task =
+ setUpFreeformTask(DEFAULT_DISPLAY, bounds).apply {
+ topActivityInfo =
+ ActivityInfo().apply {
+ screenOrientation = SCREEN_ORIENTATION_LANDSCAPE
+ configuration.windowConfiguration.appBounds = bounds
+ }
+ isResizeable = true
+ }
+
+ // Attempt to snap left again
+ val currentDragBounds = Rect(bounds).apply { offset(-100, 0) }
+ controller.snapToHalfScreen(
+ task,
+ mockSurface,
+ currentDragBounds,
+ SnapPosition.LEFT,
+ ResizeTrigger.SNAP_LEFT_MENU,
+ InputMethod.TOUCH,
+ desktopWindowDecoration,
+ )
+ // Assert that task is NOT updated via WCT
+ verify(toggleResizeDesktopTaskTransitionHandler, never()).startTransition(any(), any())
+
+ // Assert that task leash is updated via Surface Animations
+ verify(mReturnToDragStartAnimator)
+ .start(eq(task.taskId), eq(mockSurface), eq(currentDragBounds), eq(bounds), anyOrNull())
+ verify(desktopModeEventLogger, times(1))
+ .logTaskResizingEnded(
+ ResizeTrigger.SNAP_LEFT_MENU,
+ InputMethod.TOUCH,
+ task,
+ bounds.width(),
+ bounds.height(),
+ displayController,
+ )
+ }
- assertThat(
- controller.shouldPlayDesktopAnimation(
- TransitionRequestInfo(TRANSIT_OPEN, triggerTask, /* remoteTransition= */ null)
- )
- ).isTrue()
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP)
- fun shouldPlayDesktopAnimation_freeformExitsDesktop_doesNotPlay() {
- val triggerTask = setUpFreeformTask(displayId = 5, active = false)
- assertThat(controller.isDesktopModeShowing(triggerTask.displayId)).isFalse()
-
- assertThat(controller.shouldPlayDesktopAnimation(
- TransitionRequestInfo(TRANSIT_OPEN, triggerTask, /* remoteTransition= */ null)
- )).isFalse()
- }
-
- private class RunOnStartTransitionCallback : ((IBinder) -> Unit) {
- var invocations = 0
- private set
- var lastInvoked: IBinder? = null
- private set
-
- override fun invoke(transition: IBinder) {
- invocations++
- lastInvoked = transition
- }
- }
-
- private fun RunOnStartTransitionCallback.assertOnlyInvocation(transition: IBinder) {
- assertThat(invocations).isEqualTo(1)
- assertThat(lastInvoked).isEqualTo(transition)
- }
-
- /**
- * Assert that an unhandled drag event launches a PendingIntent with the
- * windowing mode and bounds we are expecting.
- */
- private fun testOnUnhandledDrag(
- indicatorType: DesktopModeVisualIndicator.IndicatorType,
- inputCoordinate: PointF,
- expectedBounds: Rect
- ) {
- setUpLandscapeDisplay()
- val task = setUpFreeformTask()
- markTaskVisible(task)
- task.isFocused = true
- val runningTasks = ArrayList<RunningTaskInfo>()
- runningTasks.add(task)
- val spyController = spy(controller)
- val mockPendingIntent = mock(PendingIntent::class.java)
- val mockDragEvent = mock(DragEvent::class.java)
- val mockCallback = mock(Consumer::class.java)
- val b = SurfaceControl.Builder()
- b.setName("test surface")
- val dragSurface = b.build()
- whenever(shellTaskOrganizer.runningTasks).thenReturn(runningTasks)
- whenever(mockDragEvent.dragSurface).thenReturn(dragSurface)
- whenever(mockDragEvent.x).thenReturn(inputCoordinate.x)
- whenever(mockDragEvent.y).thenReturn(inputCoordinate.y)
- whenever(multiInstanceHelper.supportsMultiInstanceSplit(anyOrNull())).thenReturn(true)
- whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator)
- doReturn(indicatorType)
- .whenever(spyController).updateVisualIndicator(
- eq(task),
- anyOrNull(),
- anyOrNull(),
- anyOrNull(),
- eq(DesktopModeVisualIndicator.DragStartState.DRAGGED_INTENT)
- )
-
- spyController.onUnhandledDrag(
- mockPendingIntent,
- mockDragEvent,
- mockCallback as Consumer<Boolean>
+ @Test
+ @DisableFlags(
+ Flags.FLAG_DISABLE_NON_RESIZABLE_APP_SNAP_RESIZING,
+ Flags.FLAG_ENABLE_TILE_RESIZING,
)
- val arg: ArgumentCaptor<WindowContainerTransaction> =
- ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
- var expectedWindowingMode: Int
- if (indicatorType == DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR) {
- expectedWindowingMode = WINDOWING_MODE_FULLSCREEN
- // Fullscreen launches currently use default transitions
- verify(transitions).startTransition(any(), capture(arg), anyOrNull())
- } else {
- expectedWindowingMode = WINDOWING_MODE_FREEFORM
- // All other launches use a special handler.
- verify(dragAndDropTransitionHandler).handleDropEvent(capture(arg))
- }
- assertThat(ActivityOptions.fromBundle(arg.value.hierarchyOps[0].launchOptions)
- .launchWindowingMode).isEqualTo(expectedWindowingMode)
- assertThat(ActivityOptions.fromBundle(arg.value.hierarchyOps[0].launchOptions)
- .launchBounds).isEqualTo(expectedBounds)
- }
-
- private val desktopWallpaperIntent: Intent
- get() = Intent(context, DesktopWallpaperActivity::class.java)
-
- private fun addFreeformTaskAtPosition(
- pos: DesktopTaskPosition,
- stableBounds: Rect,
- bounds: Rect = DEFAULT_LANDSCAPE_BOUNDS,
- offsetPos: Point = Point(0, 0)
- ): RunningTaskInfo {
- val offset = pos.getTopLeftCoordinates(stableBounds, bounds)
- val prevTaskBounds = Rect(bounds)
- prevTaskBounds.offsetTo(offset.x + offsetPos.x, offset.y + offsetPos.y)
- return setUpFreeformTask(bounds = prevTaskBounds)
- }
-
- private fun setUpFreeformTask(
- displayId: Int = DEFAULT_DISPLAY,
- bounds: Rect? = null,
- active: Boolean = true,
- background: Boolean = false,
- ): RunningTaskInfo {
- val task = createFreeformTask(displayId, bounds)
- val activityInfo = ActivityInfo()
- task.topActivityInfo = activityInfo
- if (background) {
- whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(null)
- whenever(recentTasksController.findTaskInBackground(task.taskId))
- .thenReturn(createTaskInfo(task.taskId))
- } else {
- whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task)
- }
- taskRepository.addTask(displayId, task.taskId, isVisible = active)
- if (!background) {
- runningTasks.add(task)
- }
- return task
- }
-
- private fun setUpPipTask(autoEnterEnabled: Boolean): RunningTaskInfo {
- return setUpFreeformTask().apply {
- pictureInPictureParams = PictureInPictureParams.Builder()
- .setAutoEnterEnabled(autoEnterEnabled)
- .build()
- }
- }
-
- private fun setUpHomeTask(displayId: Int = DEFAULT_DISPLAY): RunningTaskInfo {
- val task = createHomeTask(displayId)
- whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task)
- runningTasks.add(task)
- return task
- }
-
- private fun setUpFullscreenTask(
- displayId: Int = DEFAULT_DISPLAY,
- isResizable: Boolean = true,
- windowingMode: Int = WINDOWING_MODE_FULLSCREEN,
- deviceOrientation: Int = ORIENTATION_LANDSCAPE,
- screenOrientation: Int = SCREEN_ORIENTATION_UNSPECIFIED,
- shouldLetterbox: Boolean = false,
- gravity: Int = Gravity.NO_GRAVITY,
- enableUserFullscreenOverride: Boolean = false,
- enableSystemFullscreenOverride: Boolean = false,
- aspectRatioOverrideApplied: Boolean = false
- ): RunningTaskInfo {
- val task = createFullscreenTask(displayId)
- val activityInfo = ActivityInfo()
- activityInfo.screenOrientation = screenOrientation
- activityInfo.windowLayout = ActivityInfo.WindowLayout(0, 0F, 0, 0F, gravity, 0, 0)
- with(task) {
- topActivityInfo = activityInfo
- isResizeable = isResizable
- configuration.orientation = deviceOrientation
- configuration.windowConfiguration.windowingMode = windowingMode
- appCompatTaskInfo.isUserFullscreenOverrideEnabled = enableUserFullscreenOverride
- appCompatTaskInfo.isSystemFullscreenOverrideEnabled = enableSystemFullscreenOverride
-
- if (deviceOrientation == ORIENTATION_LANDSCAPE) {
- configuration.windowConfiguration.appBounds =
- Rect(0, 0, DISPLAY_DIMENSION_LONG, DISPLAY_DIMENSION_SHORT)
- appCompatTaskInfo.topActivityLetterboxAppWidth = DISPLAY_DIMENSION_LONG
- appCompatTaskInfo.topActivityLetterboxAppHeight = DISPLAY_DIMENSION_SHORT
- } else {
- configuration.windowConfiguration.appBounds =
- Rect(0, 0, DISPLAY_DIMENSION_SHORT, DISPLAY_DIMENSION_LONG)
- appCompatTaskInfo.topActivityLetterboxAppWidth = DISPLAY_DIMENSION_SHORT
- appCompatTaskInfo.topActivityLetterboxAppHeight = DISPLAY_DIMENSION_LONG
- }
-
- if (shouldLetterbox) {
- appCompatTaskInfo.setHasMinAspectRatioOverride(aspectRatioOverrideApplied)
- if (deviceOrientation == ORIENTATION_LANDSCAPE &&
- screenOrientation == SCREEN_ORIENTATION_PORTRAIT) {
- // Letterbox to portrait size
- appCompatTaskInfo.setTopActivityLetterboxed(true)
- appCompatTaskInfo.topActivityLetterboxAppWidth = 1200
- appCompatTaskInfo.topActivityLetterboxAppHeight = 1600
- } else if (deviceOrientation == ORIENTATION_PORTRAIT &&
- screenOrientation == SCREEN_ORIENTATION_LANDSCAPE) {
- // Letterbox to landscape size
- appCompatTaskInfo.setTopActivityLetterboxed(true)
- appCompatTaskInfo.topActivityLetterboxAppWidth = 1600
- appCompatTaskInfo.topActivityLetterboxAppHeight = 1200
+ fun handleSnapResizingTaskOnDrag_nonResizable_snapsToHalfScreen() {
+ val task =
+ setUpFreeformTask(DEFAULT_DISPLAY, Rect(0, 0, 200, 100)).apply { isResizeable = false }
+ val preDragBounds = Rect(100, 100, 400, 500)
+ val currentDragBounds = Rect(0, 100, 300, 500)
+ val expectedBounds =
+ Rect(
+ STABLE_BOUNDS.left,
+ STABLE_BOUNDS.top,
+ STABLE_BOUNDS.right / 2,
+ STABLE_BOUNDS.bottom,
+ )
+
+ controller.handleSnapResizingTaskOnDrag(
+ task,
+ SnapPosition.LEFT,
+ mockSurface,
+ currentDragBounds,
+ preDragBounds,
+ motionEvent,
+ desktopWindowDecoration,
+ )
+ val wct = getLatestToggleResizeDesktopTaskWct(currentDragBounds)
+ assertThat(findBoundsChange(wct, task)).isEqualTo(expectedBounds)
+ verify(desktopModeEventLogger, times(1))
+ .logTaskResizingStarted(
+ ResizeTrigger.DRAG_LEFT,
+ InputMethod.UNKNOWN_INPUT_METHOD,
+ task,
+ preDragBounds.width(),
+ preDragBounds.height(),
+ displayController,
+ )
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_DISABLE_NON_RESIZABLE_APP_SNAP_RESIZING)
+ fun handleSnapResizingTaskOnDrag_nonResizable_startsRepositionAnimation() {
+ val task =
+ setUpFreeformTask(DEFAULT_DISPLAY, Rect(0, 0, 200, 100)).apply { isResizeable = false }
+ val preDragBounds = Rect(100, 100, 400, 500)
+ val currentDragBounds = Rect(0, 100, 300, 500)
+
+ controller.handleSnapResizingTaskOnDrag(
+ task,
+ SnapPosition.LEFT,
+ mockSurface,
+ currentDragBounds,
+ preDragBounds,
+ motionEvent,
+ desktopWindowDecoration,
+ )
+ verify(mReturnToDragStartAnimator)
+ .start(
+ eq(task.taskId),
+ eq(mockSurface),
+ eq(currentDragBounds),
+ eq(preDragBounds),
+ any(),
+ )
+ verify(desktopModeEventLogger, never())
+ .logTaskResizingStarted(any(), any(), any(), any(), any(), any(), any())
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_DISABLE_NON_RESIZABLE_APP_SNAP_RESIZING)
+ fun handleInstantSnapResizingTask_nonResizable_animatorNotStartedAndShowsToast() {
+ val taskBounds = Rect(0, 0, 200, 100)
+ val task = setUpFreeformTask(DEFAULT_DISPLAY, taskBounds).apply { isResizeable = false }
+
+ controller.handleInstantSnapResizingTask(
+ task,
+ SnapPosition.LEFT,
+ ResizeTrigger.SNAP_LEFT_MENU,
+ InputMethod.MOUSE,
+ desktopWindowDecoration,
+ )
+
+ // Assert that task is NOT updated via WCT
+ verify(toggleResizeDesktopTaskTransitionHandler, never()).startTransition(any(), any())
+ verify(mockToast).show()
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_DISABLE_NON_RESIZABLE_APP_SNAP_RESIZING)
+ @DisableFlags(Flags.FLAG_ENABLE_TILE_RESIZING)
+ fun handleInstantSnapResizingTask_resizable_snapsToHalfScreenAndNotShowToast() {
+ val taskBounds = Rect(0, 0, 200, 100)
+ val task = setUpFreeformTask(DEFAULT_DISPLAY, taskBounds).apply { isResizeable = true }
+ val expectedBounds =
+ Rect(
+ STABLE_BOUNDS.left,
+ STABLE_BOUNDS.top,
+ STABLE_BOUNDS.right / 2,
+ STABLE_BOUNDS.bottom,
+ )
+
+ controller.handleInstantSnapResizingTask(
+ task,
+ SnapPosition.LEFT,
+ ResizeTrigger.SNAP_LEFT_MENU,
+ InputMethod.MOUSE,
+ desktopWindowDecoration,
+ )
+
+ // Assert bounds set to half of the stable bounds
+ val wct = getLatestToggleResizeDesktopTaskWct(taskBounds)
+ assertThat(findBoundsChange(wct, task)).isEqualTo(expectedBounds)
+ verify(mockToast, never()).show()
+ verify(desktopModeEventLogger, times(1))
+ .logTaskResizingStarted(
+ ResizeTrigger.SNAP_LEFT_MENU,
+ InputMethod.MOUSE,
+ task,
+ taskBounds.width(),
+ taskBounds.height(),
+ displayController,
+ )
+ verify(desktopModeEventLogger, times(1))
+ .logTaskResizingEnded(
+ ResizeTrigger.SNAP_LEFT_MENU,
+ InputMethod.MOUSE,
+ task,
+ expectedBounds.width(),
+ expectedBounds.height(),
+ displayController,
+ )
+ }
+
+ @Test
+ fun toggleBounds_togglesToCalculatedBoundsForNonResizable() {
+ val bounds = Rect(0, 0, 200, 100)
+ val task =
+ setUpFreeformTask(DEFAULT_DISPLAY, bounds).apply {
+ topActivityInfo =
+ ActivityInfo().apply {
+ screenOrientation = SCREEN_ORIENTATION_LANDSCAPE
+ configuration.windowConfiguration.appBounds = bounds
+ }
+ appCompatTaskInfo.topActivityLetterboxAppWidth = bounds.width()
+ appCompatTaskInfo.topActivityLetterboxAppHeight = bounds.height()
+ isResizeable = false
+ }
+
+ // Bounds should be 1000 x 500, vertically centered in the 1000 x 1000 stable bounds
+ val expectedBounds = Rect(STABLE_BOUNDS.left, 250, STABLE_BOUNDS.right, 750)
+
+ controller.toggleDesktopTaskSize(
+ task,
+ ToggleTaskSizeInteraction(
+ ToggleTaskSizeInteraction.Direction.MAXIMIZE,
+ ToggleTaskSizeInteraction.Source.HEADER_BUTTON_TO_MAXIMIZE,
+ InputMethod.TOUCH,
+ ),
+ )
+
+ // Assert bounds set to stable bounds
+ val wct = getLatestToggleResizeDesktopTaskWct()
+ assertThat(findBoundsChange(wct, task)).isEqualTo(expectedBounds)
+ verify(desktopModeEventLogger, times(1))
+ .logTaskResizingEnded(
+ ResizeTrigger.MAXIMIZE_BUTTON,
+ InputMethod.TOUCH,
+ task,
+ expectedBounds.width(),
+ expectedBounds.height(),
+ displayController,
+ )
+ }
+
+ @Test
+ fun toggleBounds_lastBoundsBeforeMaximizeSaved() {
+ val bounds = Rect(0, 0, 100, 100)
+ val task = setUpFreeformTask(DEFAULT_DISPLAY, bounds)
+
+ controller.toggleDesktopTaskSize(
+ task,
+ ToggleTaskSizeInteraction(
+ ToggleTaskSizeInteraction.Direction.MAXIMIZE,
+ ToggleTaskSizeInteraction.Source.HEADER_BUTTON_TO_MAXIMIZE,
+ InputMethod.TOUCH,
+ ),
+ )
+
+ assertThat(taskRepository.removeBoundsBeforeMaximize(task.taskId)).isEqualTo(bounds)
+ verify(desktopModeEventLogger, never())
+ .logTaskResizingEnded(any(), any(), any(), any(), any(), any(), any())
+ }
+
+ @Test
+ fun toggleBounds_togglesFromStableBoundsToLastBoundsBeforeMaximize() {
+ val boundsBeforeMaximize = Rect(0, 0, 100, 100)
+ val task = setUpFreeformTask(DEFAULT_DISPLAY, boundsBeforeMaximize)
+
+ // Maximize
+ controller.toggleDesktopTaskSize(
+ task,
+ ToggleTaskSizeInteraction(
+ ToggleTaskSizeInteraction.Direction.MAXIMIZE,
+ ToggleTaskSizeInteraction.Source.HEADER_BUTTON_TO_MAXIMIZE,
+ InputMethod.TOUCH,
+ ),
+ )
+ task.configuration.windowConfiguration.bounds.set(STABLE_BOUNDS)
+
+ // Restore
+ controller.toggleDesktopTaskSize(
+ task,
+ ToggleTaskSizeInteraction(
+ ToggleTaskSizeInteraction.Direction.RESTORE,
+ ToggleTaskSizeInteraction.Source.HEADER_BUTTON_TO_RESTORE,
+ InputMethod.TOUCH,
+ ),
+ )
+
+ // Assert bounds set to last bounds before maximize
+ val wct = getLatestToggleResizeDesktopTaskWct()
+ assertThat(findBoundsChange(wct, task)).isEqualTo(boundsBeforeMaximize)
+ verify(desktopModeEventLogger, times(1))
+ .logTaskResizingEnded(
+ ResizeTrigger.MAXIMIZE_BUTTON,
+ InputMethod.TOUCH,
+ task,
+ boundsBeforeMaximize.width(),
+ boundsBeforeMaximize.height(),
+ displayController,
+ )
+ }
+
+ @Test
+ fun toggleBounds_togglesFromStableBoundsToLastBoundsBeforeMaximize_nonResizeableEqualWidth() {
+ val boundsBeforeMaximize = Rect(0, 0, 100, 100)
+ val task =
+ setUpFreeformTask(DEFAULT_DISPLAY, boundsBeforeMaximize).apply { isResizeable = false }
+
+ // Maximize
+ controller.toggleDesktopTaskSize(
+ task,
+ ToggleTaskSizeInteraction(
+ ToggleTaskSizeInteraction.Direction.MAXIMIZE,
+ ToggleTaskSizeInteraction.Source.HEADER_BUTTON_TO_MAXIMIZE,
+ InputMethod.TOUCH,
+ ),
+ )
+ task.configuration.windowConfiguration.bounds.set(
+ STABLE_BOUNDS.left,
+ boundsBeforeMaximize.top,
+ STABLE_BOUNDS.right,
+ boundsBeforeMaximize.bottom,
+ )
+
+ // Restore
+ controller.toggleDesktopTaskSize(
+ task,
+ ToggleTaskSizeInteraction(
+ ToggleTaskSizeInteraction.Direction.RESTORE,
+ ToggleTaskSizeInteraction.Source.HEADER_BUTTON_TO_RESTORE,
+ InputMethod.TOUCH,
+ ),
+ )
+
+ // Assert bounds set to last bounds before maximize
+ val wct = getLatestToggleResizeDesktopTaskWct()
+ assertThat(findBoundsChange(wct, task)).isEqualTo(boundsBeforeMaximize)
+ verify(desktopModeEventLogger, times(1))
+ .logTaskResizingEnded(
+ ResizeTrigger.MAXIMIZE_BUTTON,
+ InputMethod.TOUCH,
+ task,
+ boundsBeforeMaximize.width(),
+ boundsBeforeMaximize.height(),
+ displayController,
+ )
+ }
+
+ @Test
+ fun toggleBounds_togglesFromStableBoundsToLastBoundsBeforeMaximize_nonResizeableEqualHeight() {
+ val boundsBeforeMaximize = Rect(0, 0, 100, 100)
+ val task =
+ setUpFreeformTask(DEFAULT_DISPLAY, boundsBeforeMaximize).apply { isResizeable = false }
+
+ // Maximize
+ controller.toggleDesktopTaskSize(
+ task,
+ ToggleTaskSizeInteraction(
+ ToggleTaskSizeInteraction.Direction.MAXIMIZE,
+ ToggleTaskSizeInteraction.Source.HEADER_BUTTON_TO_MAXIMIZE,
+ InputMethod.TOUCH,
+ ),
+ )
+ task.configuration.windowConfiguration.bounds.set(
+ boundsBeforeMaximize.left,
+ STABLE_BOUNDS.top,
+ boundsBeforeMaximize.right,
+ STABLE_BOUNDS.bottom,
+ )
+
+ // Restore
+ controller.toggleDesktopTaskSize(
+ task,
+ ToggleTaskSizeInteraction(
+ ToggleTaskSizeInteraction.Direction.RESTORE,
+ ToggleTaskSizeInteraction.Source.HEADER_BUTTON_TO_RESTORE,
+ InputMethod.TOUCH,
+ ),
+ )
+
+ // Assert bounds set to last bounds before maximize
+ val wct = getLatestToggleResizeDesktopTaskWct()
+ assertThat(findBoundsChange(wct, task)).isEqualTo(boundsBeforeMaximize)
+ verify(desktopModeEventLogger, times(1))
+ .logTaskResizingEnded(
+ ResizeTrigger.MAXIMIZE_BUTTON,
+ InputMethod.TOUCH,
+ task,
+ boundsBeforeMaximize.width(),
+ boundsBeforeMaximize.height(),
+ displayController,
+ )
+ }
+
+ @Test
+ fun toggleBounds_removesLastBoundsBeforeMaximizeAfterRestoringBounds() {
+ val boundsBeforeMaximize = Rect(0, 0, 100, 100)
+ val task = setUpFreeformTask(DEFAULT_DISPLAY, boundsBeforeMaximize)
+
+ // Maximize
+ controller.toggleDesktopTaskSize(
+ task,
+ ToggleTaskSizeInteraction(
+ ToggleTaskSizeInteraction.Direction.MAXIMIZE,
+ ToggleTaskSizeInteraction.Source.HEADER_BUTTON_TO_MAXIMIZE,
+ InputMethod.TOUCH,
+ ),
+ )
+ task.configuration.windowConfiguration.bounds.set(STABLE_BOUNDS)
+
+ // Restore
+ controller.toggleDesktopTaskSize(
+ task,
+ ToggleTaskSizeInteraction(
+ ToggleTaskSizeInteraction.Direction.RESTORE,
+ ToggleTaskSizeInteraction.Source.HEADER_BUTTON_TO_RESTORE,
+ InputMethod.TOUCH,
+ ),
+ )
+
+ // Assert last bounds before maximize removed after use
+ assertThat(taskRepository.removeBoundsBeforeMaximize(task.taskId)).isNull()
+ verify(desktopModeEventLogger, times(1))
+ .logTaskResizingEnded(
+ ResizeTrigger.MAXIMIZE_BUTTON,
+ InputMethod.TOUCH,
+ task,
+ boundsBeforeMaximize.width(),
+ boundsBeforeMaximize.height(),
+ displayController,
+ )
+ }
+
+ @Test
+ fun onUnhandledDrag_newFreeformIntent() {
+ testOnUnhandledDrag(
+ DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR,
+ PointF(1200f, 700f),
+ Rect(240, 700, 2160, 1900),
+ )
+ }
+
+ @Test
+ fun onUnhandledDrag_newFreeformIntentSplitLeft() {
+ testOnUnhandledDrag(
+ DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_LEFT_INDICATOR,
+ PointF(50f, 700f),
+ Rect(0, 0, 500, 1000),
+ )
+ }
+
+ @Test
+ fun onUnhandledDrag_newFreeformIntentSplitRight() {
+ testOnUnhandledDrag(
+ DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_RIGHT_INDICATOR,
+ PointF(2500f, 700f),
+ Rect(500, 0, 1000, 1000),
+ )
+ }
+
+ @Test
+ fun onUnhandledDrag_newFullscreenIntent() {
+ testOnUnhandledDrag(
+ DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR,
+ PointF(1200f, 50f),
+ Rect(),
+ )
+ }
+
+ @Test
+ fun shellController_registersUserChangeListener() {
+ verify(shellController, times(2)).addUserChangeListener(any())
+ }
+
+ @Test
+ @EnableFlags(FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP)
+ fun onTaskInfoChanged_inImmersiveUnrequestsImmersive_exits() {
+ val task = setUpFreeformTask(DEFAULT_DISPLAY)
+ taskRepository.setTaskInFullImmersiveState(DEFAULT_DISPLAY, task.taskId, immersive = true)
+
+ task.requestedVisibleTypes = WindowInsets.Type.statusBars()
+ controller.onTaskInfoChanged(task)
+
+ verify(mMockDesktopImmersiveController).moveTaskToNonImmersive(eq(task), any())
+ }
+
+ @Test
+ @EnableFlags(FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP)
+ fun onTaskInfoChanged_notInImmersiveUnrequestsImmersive_noReExit() {
+ val task = setUpFreeformTask(DEFAULT_DISPLAY)
+ taskRepository.setTaskInFullImmersiveState(DEFAULT_DISPLAY, task.taskId, immersive = false)
+
+ task.requestedVisibleTypes = WindowInsets.Type.statusBars()
+ controller.onTaskInfoChanged(task)
+
+ verify(mMockDesktopImmersiveController, never()).moveTaskToNonImmersive(eq(task), any())
+ }
+
+ @Test
+ @EnableFlags(FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP)
+ fun onTaskInfoChanged_inImmersiveUnrequestsImmersive_inRecentsTransition_noExit() {
+ val task = setUpFreeformTask(DEFAULT_DISPLAY)
+ taskRepository.setTaskInFullImmersiveState(DEFAULT_DISPLAY, task.taskId, immersive = true)
+ recentsTransitionStateListener.onTransitionStateChanged(TRANSITION_STATE_REQUESTED)
+
+ task.requestedVisibleTypes = WindowInsets.Type.statusBars()
+ controller.onTaskInfoChanged(task)
+
+ verify(mMockDesktopImmersiveController, never()).moveTaskToNonImmersive(eq(task), any())
+ }
+
+ @Test
+ fun moveTaskToDesktop_background_attemptsImmersiveExit() {
+ val task = setUpFreeformTask(background = true)
+ val wct = WindowContainerTransaction()
+ val runOnStartTransit = RunOnStartTransitionCallback()
+ val transition = Binder()
+ whenever(
+ mMockDesktopImmersiveController.exitImmersiveIfApplicable(
+ eq(wct),
+ eq(task.displayId),
+ eq(task.taskId),
+ any(),
+ )
+ )
+ .thenReturn(ExitResult.Exit(exitingTask = 5, runOnTransitionStart = runOnStartTransit))
+ whenever(enterDesktopTransitionHandler.moveToDesktop(wct, UNKNOWN)).thenReturn(transition)
+
+ controller.moveTaskToDesktop(taskId = task.taskId, wct = wct, transitionSource = UNKNOWN)
+
+ verify(mMockDesktopImmersiveController)
+ .exitImmersiveIfApplicable(eq(wct), eq(task.displayId), eq(task.taskId), any())
+ runOnStartTransit.assertOnlyInvocation(transition)
+ }
+
+ @Test
+ fun moveTaskToDesktop_foreground_attemptsImmersiveExit() {
+ val task = setUpFreeformTask(background = false)
+ val wct = WindowContainerTransaction()
+ val runOnStartTransit = RunOnStartTransitionCallback()
+ val transition = Binder()
+ whenever(
+ mMockDesktopImmersiveController.exitImmersiveIfApplicable(
+ eq(wct),
+ eq(task.displayId),
+ eq(task.taskId),
+ any(),
+ )
+ )
+ .thenReturn(ExitResult.Exit(exitingTask = 5, runOnTransitionStart = runOnStartTransit))
+ whenever(enterDesktopTransitionHandler.moveToDesktop(wct, UNKNOWN)).thenReturn(transition)
+
+ controller.moveTaskToDesktop(taskId = task.taskId, wct = wct, transitionSource = UNKNOWN)
+
+ verify(mMockDesktopImmersiveController)
+ .exitImmersiveIfApplicable(eq(wct), eq(task.displayId), eq(task.taskId), any())
+ runOnStartTransit.assertOnlyInvocation(transition)
+ }
+
+ @Test
+ fun moveTaskToFront_background_attemptsImmersiveExit() {
+ val task = setUpFreeformTask(background = true)
+ val runOnStartTransit = RunOnStartTransitionCallback()
+ val transition = Binder()
+ whenever(
+ mMockDesktopImmersiveController.exitImmersiveIfApplicable(
+ any(),
+ eq(task.displayId),
+ eq(task.taskId),
+ any(),
+ )
+ )
+ .thenReturn(ExitResult.Exit(exitingTask = 5, runOnTransitionStart = runOnStartTransit))
+ whenever(
+ desktopMixedTransitionHandler.startLaunchTransition(
+ any(),
+ any(),
+ anyInt(),
+ anyOrNull(),
+ anyOrNull(),
+ )
+ )
+ .thenReturn(transition)
+
+ controller.moveTaskToFront(task.taskId, remoteTransition = null)
+
+ verify(mMockDesktopImmersiveController)
+ .exitImmersiveIfApplicable(any(), eq(task.displayId), eq(task.taskId), any())
+ runOnStartTransit.assertOnlyInvocation(transition)
+ }
+
+ @Test
+ fun moveTaskToFront_foreground_attemptsImmersiveExit() {
+ val task = setUpFreeformTask(background = false)
+ val runOnStartTransit = RunOnStartTransitionCallback()
+ val transition = Binder()
+ whenever(
+ mMockDesktopImmersiveController.exitImmersiveIfApplicable(
+ any(),
+ eq(task.displayId),
+ eq(task.taskId),
+ any(),
+ )
+ )
+ .thenReturn(ExitResult.Exit(exitingTask = 5, runOnTransitionStart = runOnStartTransit))
+ whenever(
+ desktopMixedTransitionHandler.startLaunchTransition(
+ any(),
+ any(),
+ eq(task.taskId),
+ anyOrNull(),
+ anyOrNull(),
+ )
+ )
+ .thenReturn(transition)
+
+ controller.moveTaskToFront(task.taskId, remoteTransition = null)
+
+ verify(mMockDesktopImmersiveController)
+ .exitImmersiveIfApplicable(any(), eq(task.displayId), eq(task.taskId), any())
+ runOnStartTransit.assertOnlyInvocation(transition)
+ }
+
+ @Test
+ fun handleRequest_freeformLaunchToDesktop_attemptsImmersiveExit() {
+ markTaskVisible(setUpFreeformTask())
+ val task = setUpFreeformTask()
+ markTaskVisible(task)
+ val binder = Binder()
+
+ controller.handleRequest(binder, createTransition(task))
+
+ verify(mMockDesktopImmersiveController)
+ .exitImmersiveIfApplicable(eq(binder), any(), eq(task.displayId), any())
+ }
+
+ @Test
+ fun handleRequest_fullscreenLaunchToDesktop_attemptsImmersiveExit() {
+ setUpFreeformTask()
+ val task = setUpFullscreenTask()
+ val binder = Binder()
+
+ controller.handleRequest(binder, createTransition(task))
+
+ verify(mMockDesktopImmersiveController)
+ .exitImmersiveIfApplicable(eq(binder), any(), eq(task.displayId), any())
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP)
+ fun shouldPlayDesktopAnimation_notShowingDesktop_doesNotPlay() {
+ val triggerTask = setUpFullscreenTask(displayId = 5)
+ taskRepository.setTaskInFullImmersiveState(
+ displayId = triggerTask.displayId,
+ taskId = triggerTask.taskId,
+ immersive = true,
+ )
+
+ assertThat(
+ controller.shouldPlayDesktopAnimation(
+ TransitionRequestInfo(TRANSIT_OPEN, triggerTask, /* remoteTransition= */ null)
+ )
+ )
+ .isFalse()
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP)
+ fun shouldPlayDesktopAnimation_notOpening_doesNotPlay() {
+ val triggerTask = setUpFreeformTask(displayId = 5)
+ taskRepository.setTaskInFullImmersiveState(
+ displayId = triggerTask.displayId,
+ taskId = triggerTask.taskId,
+ immersive = true,
+ )
+
+ assertThat(
+ controller.shouldPlayDesktopAnimation(
+ TransitionRequestInfo(TRANSIT_CHANGE, triggerTask, /* remoteTransition= */ null)
+ )
+ )
+ .isFalse()
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP)
+ fun shouldPlayDesktopAnimation_notImmersive_doesNotPlay() {
+ val triggerTask = setUpFreeformTask(displayId = 5)
+ taskRepository.setTaskInFullImmersiveState(
+ displayId = triggerTask.displayId,
+ taskId = triggerTask.taskId,
+ immersive = false,
+ )
+
+ assertThat(
+ controller.shouldPlayDesktopAnimation(
+ TransitionRequestInfo(TRANSIT_OPEN, triggerTask, /* remoteTransition= */ null)
+ )
+ )
+ .isFalse()
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP)
+ fun shouldPlayDesktopAnimation_fullscreenEntersDesktop_plays() {
+ // At least one freeform task to be in a desktop.
+ val existingTask = setUpFreeformTask(displayId = 5)
+ val triggerTask = setUpFullscreenTask(displayId = 5)
+ assertThat(controller.isDesktopModeShowing(triggerTask.displayId)).isTrue()
+ taskRepository.setTaskInFullImmersiveState(
+ displayId = existingTask.displayId,
+ taskId = existingTask.taskId,
+ immersive = true,
+ )
+
+ assertThat(
+ controller.shouldPlayDesktopAnimation(
+ TransitionRequestInfo(TRANSIT_OPEN, triggerTask, /* remoteTransition= */ null)
+ )
+ )
+ .isTrue()
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP)
+ fun shouldPlayDesktopAnimation_fullscreenStaysFullscreen_doesNotPlay() {
+ val triggerTask = setUpFullscreenTask(displayId = 5)
+ assertThat(controller.isDesktopModeShowing(triggerTask.displayId)).isFalse()
+
+ assertThat(
+ controller.shouldPlayDesktopAnimation(
+ TransitionRequestInfo(TRANSIT_OPEN, triggerTask, /* remoteTransition= */ null)
+ )
+ )
+ .isFalse()
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP)
+ fun shouldPlayDesktopAnimation_freeformStaysInDesktop_plays() {
+ // At least one freeform task to be in a desktop.
+ val existingTask = setUpFreeformTask(displayId = 5)
+ val triggerTask = setUpFreeformTask(displayId = 5, active = false)
+ assertThat(controller.isDesktopModeShowing(triggerTask.displayId)).isTrue()
+ taskRepository.setTaskInFullImmersiveState(
+ displayId = existingTask.displayId,
+ taskId = existingTask.taskId,
+ immersive = true,
+ )
+
+ assertThat(
+ controller.shouldPlayDesktopAnimation(
+ TransitionRequestInfo(TRANSIT_OPEN, triggerTask, /* remoteTransition= */ null)
+ )
+ )
+ .isTrue()
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP)
+ fun shouldPlayDesktopAnimation_freeformExitsDesktop_doesNotPlay() {
+ val triggerTask = setUpFreeformTask(displayId = 5, active = false)
+ assertThat(controller.isDesktopModeShowing(triggerTask.displayId)).isFalse()
+
+ assertThat(
+ controller.shouldPlayDesktopAnimation(
+ TransitionRequestInfo(TRANSIT_OPEN, triggerTask, /* remoteTransition= */ null)
+ )
+ )
+ .isFalse()
+ }
+
+ private class RunOnStartTransitionCallback : ((IBinder) -> Unit) {
+ var invocations = 0
+ private set
+
+ var lastInvoked: IBinder? = null
+ private set
+
+ override fun invoke(transition: IBinder) {
+ invocations++
+ lastInvoked = transition
}
- }
- }
- whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task)
- runningTasks.add(task)
- return task
- }
-
- private fun setUpLandscapeDisplay() {
- whenever(displayLayout.width()).thenReturn(DISPLAY_DIMENSION_LONG)
- whenever(displayLayout.height()).thenReturn(DISPLAY_DIMENSION_SHORT)
- val stableBounds = Rect(0, 0, DISPLAY_DIMENSION_LONG,
- DISPLAY_DIMENSION_SHORT - Companion.TASKBAR_FRAME_HEIGHT
- )
- whenever(displayLayout.getStableBoundsForDesktopMode(any())).thenAnswer { i ->
- (i.arguments.first() as Rect).set(stableBounds)
}
- }
- private fun setUpPortraitDisplay() {
- whenever(displayLayout.width()).thenReturn(DISPLAY_DIMENSION_SHORT)
- whenever(displayLayout.height()).thenReturn(DISPLAY_DIMENSION_LONG)
- val stableBounds = Rect(0, 0, DISPLAY_DIMENSION_SHORT,
- DISPLAY_DIMENSION_LONG - Companion.TASKBAR_FRAME_HEIGHT
- )
- whenever(displayLayout.getStableBoundsForDesktopMode(any())).thenAnswer { i ->
- (i.arguments.first() as Rect).set(stableBounds)
- }
- }
-
- private fun setUpSplitScreenTask(displayId: Int = DEFAULT_DISPLAY): RunningTaskInfo {
- val task = createSplitScreenTask(displayId)
- whenever(splitScreenController.isTaskInSplitScreen(task.taskId)).thenReturn(true)
- whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task)
- runningTasks.add(task)
- return task
- }
-
- private fun markTaskVisible(task: RunningTaskInfo) {
- taskRepository.updateTask(task.displayId, task.taskId, isVisible = true)
- }
-
- private fun markTaskHidden(task: RunningTaskInfo) {
- taskRepository.updateTask(task.displayId, task.taskId, isVisible = false)
- }
-
- private fun getLatestWct(
- @WindowManager.TransitionType type: Int = TRANSIT_OPEN,
- handlerClass: Class<out TransitionHandler>? = null
- ): WindowContainerTransaction {
- val arg = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
- if (handlerClass == null) {
- verify(transitions).startTransition(eq(type), arg.capture(), isNull())
- } else {
- verify(transitions).startTransition(eq(type), arg.capture(), isA(handlerClass))
- }
- return arg.value
- }
-
- private fun getLatestToggleResizeDesktopTaskWct(
- currentBounds: Rect? = null
- ): WindowContainerTransaction {
- val arg: ArgumentCaptor<WindowContainerTransaction> =
- ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
- verify(toggleResizeDesktopTaskTransitionHandler, atLeastOnce())
- .startTransition(capture(arg), eq(currentBounds))
- return arg.value
- }
-
- private fun getLatestDesktopMixedTaskWct(
- @WindowManager.TransitionType type: Int = TRANSIT_OPEN,
- ): WindowContainerTransaction {
- val arg: ArgumentCaptor<WindowContainerTransaction> =
- ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
- verify(desktopMixedTransitionHandler)
- .startLaunchTransition(eq(type), capture(arg), anyInt(), anyOrNull(), anyOrNull())
- return arg.value
- }
-
- private fun getLatestEnterDesktopWct(): WindowContainerTransaction {
- val arg = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
- verify(enterDesktopTransitionHandler).moveToDesktop(arg.capture(), any())
- return arg.value
- }
-
- private fun getLatestDragToDesktopWct(): WindowContainerTransaction {
- val arg: ArgumentCaptor<WindowContainerTransaction> =
- ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
- verify(dragToDesktopTransitionHandler).finishDragToDesktopTransition(capture(arg))
- return arg.value
- }
-
- private fun getLatestExitDesktopWct(): WindowContainerTransaction {
- val arg = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
- verify(exitDesktopTransitionHandler).startTransition(any(), arg.capture(), any(), any())
- return arg.value
- }
-
- private fun findBoundsChange(wct: WindowContainerTransaction, task: RunningTaskInfo): Rect? =
- wct.changes[task.token.asBinder()]?.configuration?.windowConfiguration?.bounds
-
- private fun verifyWCTNotExecuted() {
- verify(transitions, never()).startTransition(anyInt(), any(), isNull())
- }
-
- private fun verifyExitDesktopWCTNotExecuted() {
- verify(exitDesktopTransitionHandler, never()).startTransition(any(), any(), any(), any())
- }
-
- private fun verifyEnterDesktopWCTNotExecuted() {
- verify(enterDesktopTransitionHandler, never()).moveToDesktop(any(), any())
- }
-
- private fun createTransition(
- task: RunningTaskInfo?,
- @WindowManager.TransitionType type: Int = TRANSIT_OPEN
- ): TransitionRequestInfo {
- return TransitionRequestInfo(type, task, null /* remoteTransition */)
- }
-
- private companion object {
- const val SECOND_DISPLAY = 2
- val STABLE_BOUNDS = Rect(0, 0, 1000, 1000)
- const val MAX_TASK_LIMIT = 6
- private const val TASKBAR_FRAME_HEIGHT = 200
- }
+ private fun RunOnStartTransitionCallback.assertOnlyInvocation(transition: IBinder) {
+ assertThat(invocations).isEqualTo(1)
+ assertThat(lastInvoked).isEqualTo(transition)
+ }
+
+ /**
+ * Assert that an unhandled drag event launches a PendingIntent with the windowing mode and
+ * bounds we are expecting.
+ */
+ private fun testOnUnhandledDrag(
+ indicatorType: DesktopModeVisualIndicator.IndicatorType,
+ inputCoordinate: PointF,
+ expectedBounds: Rect,
+ ) {
+ setUpLandscapeDisplay()
+ val task = setUpFreeformTask()
+ markTaskVisible(task)
+ task.isFocused = true
+ val runningTasks = ArrayList<RunningTaskInfo>()
+ runningTasks.add(task)
+ val spyController = spy(controller)
+ val mockPendingIntent = mock(PendingIntent::class.java)
+ val mockDragEvent = mock(DragEvent::class.java)
+ val mockCallback = mock(Consumer::class.java)
+ val b = SurfaceControl.Builder()
+ b.setName("test surface")
+ val dragSurface = b.build()
+ whenever(shellTaskOrganizer.runningTasks).thenReturn(runningTasks)
+ whenever(mockDragEvent.dragSurface).thenReturn(dragSurface)
+ whenever(mockDragEvent.x).thenReturn(inputCoordinate.x)
+ whenever(mockDragEvent.y).thenReturn(inputCoordinate.y)
+ whenever(multiInstanceHelper.supportsMultiInstanceSplit(anyOrNull())).thenReturn(true)
+ whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator)
+ doReturn(indicatorType)
+ .whenever(spyController)
+ .updateVisualIndicator(
+ eq(task),
+ anyOrNull(),
+ anyOrNull(),
+ anyOrNull(),
+ eq(DesktopModeVisualIndicator.DragStartState.DRAGGED_INTENT),
+ )
+
+ spyController.onUnhandledDrag(
+ mockPendingIntent,
+ mockDragEvent,
+ mockCallback as Consumer<Boolean>,
+ )
+ val arg: ArgumentCaptor<WindowContainerTransaction> =
+ ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
+ var expectedWindowingMode: Int
+ if (indicatorType == DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR) {
+ expectedWindowingMode = WINDOWING_MODE_FULLSCREEN
+ // Fullscreen launches currently use default transitions
+ verify(transitions).startTransition(any(), capture(arg), anyOrNull())
+ } else {
+ expectedWindowingMode = WINDOWING_MODE_FREEFORM
+ // All other launches use a special handler.
+ verify(dragAndDropTransitionHandler).handleDropEvent(capture(arg))
+ }
+ assertThat(
+ ActivityOptions.fromBundle(arg.value.hierarchyOps[0].launchOptions)
+ .launchWindowingMode
+ )
+ .isEqualTo(expectedWindowingMode)
+ assertThat(ActivityOptions.fromBundle(arg.value.hierarchyOps[0].launchOptions).launchBounds)
+ .isEqualTo(expectedBounds)
+ }
+
+ private val desktopWallpaperIntent: Intent
+ get() = Intent(context, DesktopWallpaperActivity::class.java)
+
+ private fun addFreeformTaskAtPosition(
+ pos: DesktopTaskPosition,
+ stableBounds: Rect,
+ bounds: Rect = DEFAULT_LANDSCAPE_BOUNDS,
+ offsetPos: Point = Point(0, 0),
+ ): RunningTaskInfo {
+ val offset = pos.getTopLeftCoordinates(stableBounds, bounds)
+ val prevTaskBounds = Rect(bounds)
+ prevTaskBounds.offsetTo(offset.x + offsetPos.x, offset.y + offsetPos.y)
+ return setUpFreeformTask(bounds = prevTaskBounds)
+ }
+
+ private fun setUpFreeformTask(
+ displayId: Int = DEFAULT_DISPLAY,
+ bounds: Rect? = null,
+ active: Boolean = true,
+ background: Boolean = false,
+ ): RunningTaskInfo {
+ val task = createFreeformTask(displayId, bounds)
+ val activityInfo = ActivityInfo()
+ task.topActivityInfo = activityInfo
+ if (background) {
+ whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(null)
+ whenever(recentTasksController.findTaskInBackground(task.taskId))
+ .thenReturn(createTaskInfo(task.taskId))
+ } else {
+ whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task)
+ }
+ taskRepository.addTask(displayId, task.taskId, isVisible = active)
+ if (!background) {
+ runningTasks.add(task)
+ }
+ return task
+ }
+
+ private fun setUpPipTask(autoEnterEnabled: Boolean): RunningTaskInfo {
+ return setUpFreeformTask().apply {
+ pictureInPictureParams =
+ PictureInPictureParams.Builder().setAutoEnterEnabled(autoEnterEnabled).build()
+ }
+ }
+
+ private fun setUpHomeTask(displayId: Int = DEFAULT_DISPLAY): RunningTaskInfo {
+ val task = createHomeTask(displayId)
+ whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task)
+ runningTasks.add(task)
+ return task
+ }
+
+ private fun setUpFullscreenTask(
+ displayId: Int = DEFAULT_DISPLAY,
+ isResizable: Boolean = true,
+ windowingMode: Int = WINDOWING_MODE_FULLSCREEN,
+ deviceOrientation: Int = ORIENTATION_LANDSCAPE,
+ screenOrientation: Int = SCREEN_ORIENTATION_UNSPECIFIED,
+ shouldLetterbox: Boolean = false,
+ gravity: Int = Gravity.NO_GRAVITY,
+ enableUserFullscreenOverride: Boolean = false,
+ enableSystemFullscreenOverride: Boolean = false,
+ aspectRatioOverrideApplied: Boolean = false,
+ ): RunningTaskInfo {
+ val task = createFullscreenTask(displayId)
+ val activityInfo = ActivityInfo()
+ activityInfo.screenOrientation = screenOrientation
+ activityInfo.windowLayout = ActivityInfo.WindowLayout(0, 0F, 0, 0F, gravity, 0, 0)
+ with(task) {
+ topActivityInfo = activityInfo
+ isResizeable = isResizable
+ configuration.orientation = deviceOrientation
+ configuration.windowConfiguration.windowingMode = windowingMode
+ appCompatTaskInfo.isUserFullscreenOverrideEnabled = enableUserFullscreenOverride
+ appCompatTaskInfo.isSystemFullscreenOverrideEnabled = enableSystemFullscreenOverride
+
+ if (deviceOrientation == ORIENTATION_LANDSCAPE) {
+ configuration.windowConfiguration.appBounds =
+ Rect(0, 0, DISPLAY_DIMENSION_LONG, DISPLAY_DIMENSION_SHORT)
+ appCompatTaskInfo.topActivityLetterboxAppWidth = DISPLAY_DIMENSION_LONG
+ appCompatTaskInfo.topActivityLetterboxAppHeight = DISPLAY_DIMENSION_SHORT
+ } else {
+ configuration.windowConfiguration.appBounds =
+ Rect(0, 0, DISPLAY_DIMENSION_SHORT, DISPLAY_DIMENSION_LONG)
+ appCompatTaskInfo.topActivityLetterboxAppWidth = DISPLAY_DIMENSION_SHORT
+ appCompatTaskInfo.topActivityLetterboxAppHeight = DISPLAY_DIMENSION_LONG
+ }
+
+ if (shouldLetterbox) {
+ appCompatTaskInfo.setHasMinAspectRatioOverride(aspectRatioOverrideApplied)
+ if (
+ deviceOrientation == ORIENTATION_LANDSCAPE &&
+ screenOrientation == SCREEN_ORIENTATION_PORTRAIT
+ ) {
+ // Letterbox to portrait size
+ appCompatTaskInfo.setTopActivityLetterboxed(true)
+ appCompatTaskInfo.topActivityLetterboxAppWidth = 1200
+ appCompatTaskInfo.topActivityLetterboxAppHeight = 1600
+ } else if (
+ deviceOrientation == ORIENTATION_PORTRAIT &&
+ screenOrientation == SCREEN_ORIENTATION_LANDSCAPE
+ ) {
+ // Letterbox to landscape size
+ appCompatTaskInfo.setTopActivityLetterboxed(true)
+ appCompatTaskInfo.topActivityLetterboxAppWidth = 1600
+ appCompatTaskInfo.topActivityLetterboxAppHeight = 1200
+ }
+ }
+ }
+ whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task)
+ runningTasks.add(task)
+ return task
+ }
+
+ private fun setUpLandscapeDisplay() {
+ whenever(displayLayout.width()).thenReturn(DISPLAY_DIMENSION_LONG)
+ whenever(displayLayout.height()).thenReturn(DISPLAY_DIMENSION_SHORT)
+ val stableBounds =
+ Rect(
+ 0,
+ 0,
+ DISPLAY_DIMENSION_LONG,
+ DISPLAY_DIMENSION_SHORT - Companion.TASKBAR_FRAME_HEIGHT,
+ )
+ whenever(displayLayout.getStableBoundsForDesktopMode(any())).thenAnswer { i ->
+ (i.arguments.first() as Rect).set(stableBounds)
+ }
+ }
+
+ private fun setUpPortraitDisplay() {
+ whenever(displayLayout.width()).thenReturn(DISPLAY_DIMENSION_SHORT)
+ whenever(displayLayout.height()).thenReturn(DISPLAY_DIMENSION_LONG)
+ val stableBounds =
+ Rect(
+ 0,
+ 0,
+ DISPLAY_DIMENSION_SHORT,
+ DISPLAY_DIMENSION_LONG - Companion.TASKBAR_FRAME_HEIGHT,
+ )
+ whenever(displayLayout.getStableBoundsForDesktopMode(any())).thenAnswer { i ->
+ (i.arguments.first() as Rect).set(stableBounds)
+ }
+ }
+
+ private fun setUpSplitScreenTask(displayId: Int = DEFAULT_DISPLAY): RunningTaskInfo {
+ val task = createSplitScreenTask(displayId)
+ whenever(splitScreenController.isTaskInSplitScreen(task.taskId)).thenReturn(true)
+ whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task)
+ runningTasks.add(task)
+ return task
+ }
+
+ private fun markTaskVisible(task: RunningTaskInfo) {
+ taskRepository.updateTask(task.displayId, task.taskId, isVisible = true)
+ }
+
+ private fun markTaskHidden(task: RunningTaskInfo) {
+ taskRepository.updateTask(task.displayId, task.taskId, isVisible = false)
+ }
+
+ private fun getLatestWct(
+ @WindowManager.TransitionType type: Int = TRANSIT_OPEN,
+ handlerClass: Class<out TransitionHandler>? = null,
+ ): WindowContainerTransaction {
+ val arg = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
+ if (handlerClass == null) {
+ verify(transitions).startTransition(eq(type), arg.capture(), isNull())
+ } else {
+ verify(transitions).startTransition(eq(type), arg.capture(), isA(handlerClass))
+ }
+ return arg.value
+ }
+
+ private fun getLatestToggleResizeDesktopTaskWct(
+ currentBounds: Rect? = null
+ ): WindowContainerTransaction {
+ val arg: ArgumentCaptor<WindowContainerTransaction> =
+ ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
+ verify(toggleResizeDesktopTaskTransitionHandler, atLeastOnce())
+ .startTransition(capture(arg), eq(currentBounds))
+ return arg.value
+ }
+
+ private fun getLatestDesktopMixedTaskWct(
+ @WindowManager.TransitionType type: Int = TRANSIT_OPEN
+ ): WindowContainerTransaction {
+ val arg: ArgumentCaptor<WindowContainerTransaction> =
+ ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
+ verify(desktopMixedTransitionHandler)
+ .startLaunchTransition(eq(type), capture(arg), anyInt(), anyOrNull(), anyOrNull())
+ return arg.value
+ }
+
+ private fun getLatestEnterDesktopWct(): WindowContainerTransaction {
+ val arg = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
+ verify(enterDesktopTransitionHandler).moveToDesktop(arg.capture(), any())
+ return arg.value
+ }
+
+ private fun getLatestDragToDesktopWct(): WindowContainerTransaction {
+ val arg: ArgumentCaptor<WindowContainerTransaction> =
+ ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
+ verify(dragToDesktopTransitionHandler).finishDragToDesktopTransition(capture(arg))
+ return arg.value
+ }
+
+ private fun getLatestExitDesktopWct(): WindowContainerTransaction {
+ val arg = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
+ verify(exitDesktopTransitionHandler).startTransition(any(), arg.capture(), any(), any())
+ return arg.value
+ }
+
+ private fun findBoundsChange(wct: WindowContainerTransaction, task: RunningTaskInfo): Rect? =
+ wct.changes[task.token.asBinder()]?.configuration?.windowConfiguration?.bounds
+
+ private fun verifyWCTNotExecuted() {
+ verify(transitions, never()).startTransition(anyInt(), any(), isNull())
+ }
+
+ private fun verifyExitDesktopWCTNotExecuted() {
+ verify(exitDesktopTransitionHandler, never()).startTransition(any(), any(), any(), any())
+ }
+
+ private fun verifyEnterDesktopWCTNotExecuted() {
+ verify(enterDesktopTransitionHandler, never()).moveToDesktop(any(), any())
+ }
+
+ private fun createTransition(
+ task: RunningTaskInfo?,
+ @WindowManager.TransitionType type: Int = TRANSIT_OPEN,
+ ): TransitionRequestInfo {
+ return TransitionRequestInfo(type, task, null /* remoteTransition */)
+ }
+
+ private companion object {
+ const val SECOND_DISPLAY = 2
+ val STABLE_BOUNDS = Rect(0, 0, 1000, 1000)
+ const val MAX_TASK_LIMIT = 6
+ private const val TASKBAR_FRAME_HEIGHT = 200
+ }
}
private fun WindowContainerTransaction.assertIndexInBounds(index: Int) {
- assertWithMessage("WCT does not have a hierarchy operation at index $index")
- .that(hierarchyOps.size)
- .isGreaterThan(index)
+ assertWithMessage("WCT does not have a hierarchy operation at index $index")
+ .that(hierarchyOps.size)
+ .isGreaterThan(index)
}
private fun WindowContainerTransaction.assertReorderAt(
index: Int,
task: RunningTaskInfo,
- toTop: Boolean? = null
+ toTop: Boolean? = null,
) {
- assertIndexInBounds(index)
- val op = hierarchyOps[index]
- assertThat(op.type).isEqualTo(HIERARCHY_OP_TYPE_REORDER)
- assertThat(op.container).isEqualTo(task.token.asBinder())
- toTop?.let { assertThat(op.toTop).isEqualTo(it) }
+ assertIndexInBounds(index)
+ val op = hierarchyOps[index]
+ assertThat(op.type).isEqualTo(HIERARCHY_OP_TYPE_REORDER)
+ assertThat(op.container).isEqualTo(task.token.asBinder())
+ toTop?.let { assertThat(op.toTop).isEqualTo(it) }
}
private fun WindowContainerTransaction.assertReorderSequence(vararg tasks: RunningTaskInfo) {
- for (i in tasks.indices) {
- assertReorderAt(i, tasks[i])
- }
+ for (i in tasks.indices) {
+ assertReorderAt(i, tasks[i])
+ }
}
/** Checks if the reorder hierarchy operations in [range] correspond to [tasks] list */
private fun WindowContainerTransaction.assertReorderSequenceInRange(
- range: IntRange,
- vararg tasks: RunningTaskInfo
+ range: IntRange,
+ vararg tasks: RunningTaskInfo,
) {
- assertThat(hierarchyOps.slice(range).map { it.type to it.container })
- .containsExactlyElementsIn(tasks.map { HIERARCHY_OP_TYPE_REORDER to it.token.asBinder() })
- .inOrder()
+ assertThat(hierarchyOps.slice(range).map { it.type to it.container })
+ .containsExactlyElementsIn(tasks.map { HIERARCHY_OP_TYPE_REORDER to it.token.asBinder() })
+ .inOrder()
}
private fun WindowContainerTransaction.assertRemoveAt(index: Int, token: WindowContainerToken) {
- assertIndexInBounds(index)
- val op = hierarchyOps[index]
- assertThat(op.type).isEqualTo(HIERARCHY_OP_TYPE_REMOVE_TASK)
- assertThat(op.container).isEqualTo(token.asBinder())
+ assertIndexInBounds(index)
+ val op = hierarchyOps[index]
+ assertThat(op.type).isEqualTo(HIERARCHY_OP_TYPE_REMOVE_TASK)
+ assertThat(op.container).isEqualTo(token.asBinder())
}
private fun WindowContainerTransaction.assertNoRemoveAt(index: Int, token: WindowContainerToken) {
- assertIndexInBounds(index)
- val op = hierarchyOps[index]
- assertThat(op.type).isEqualTo(HIERARCHY_OP_TYPE_REMOVE_TASK)
- assertThat(op.container).isEqualTo(token.asBinder())
+ assertIndexInBounds(index)
+ val op = hierarchyOps[index]
+ assertThat(op.type).isEqualTo(HIERARCHY_OP_TYPE_REMOVE_TASK)
+ assertThat(op.container).isEqualTo(token.asBinder())
}
private fun WindowContainerTransaction.hasRemoveAt(index: Int, token: WindowContainerToken) {
- assertIndexInBounds(index)
- val op = hierarchyOps[index]
- assertThat(op.type).isEqualTo(HIERARCHY_OP_TYPE_REMOVE_TASK)
- assertThat(op.container).isEqualTo(token.asBinder())
+ assertIndexInBounds(index)
+ val op = hierarchyOps[index]
+ assertThat(op.type).isEqualTo(HIERARCHY_OP_TYPE_REMOVE_TASK)
+ assertThat(op.container).isEqualTo(token.asBinder())
}
private fun WindowContainerTransaction.assertPendingIntentAt(index: Int, intent: Intent) {
- assertIndexInBounds(index)
- val op = hierarchyOps[index]
- assertThat(op.type).isEqualTo(HIERARCHY_OP_TYPE_PENDING_INTENT)
- assertThat(op.pendingIntent?.intent?.component).isEqualTo(intent.component)
+ assertIndexInBounds(index)
+ val op = hierarchyOps[index]
+ assertThat(op.type).isEqualTo(HIERARCHY_OP_TYPE_PENDING_INTENT)
+ assertThat(op.pendingIntent?.intent?.component).isEqualTo(intent.component)
}
private fun WindowContainerTransaction.assertLaunchTaskAt(
index: Int,
taskId: Int,
- windowingMode: Int
+ windowingMode: Int,
) {
- val keyLaunchWindowingMode = "android.activity.windowingMode"
-
- assertIndexInBounds(index)
- val op = hierarchyOps[index]
- assertThat(op.type).isEqualTo(HIERARCHY_OP_TYPE_LAUNCH_TASK)
- assertThat(op.launchOptions?.getInt(LAUNCH_KEY_TASK_ID)).isEqualTo(taskId)
- assertThat(op.launchOptions?.getInt(keyLaunchWindowingMode, WINDOWING_MODE_UNDEFINED))
- .isEqualTo(windowingMode)
+ val keyLaunchWindowingMode = "android.activity.windowingMode"
+
+ assertIndexInBounds(index)
+ val op = hierarchyOps[index]
+ assertThat(op.type).isEqualTo(HIERARCHY_OP_TYPE_LAUNCH_TASK)
+ assertThat(op.launchOptions?.getInt(LAUNCH_KEY_TASK_ID)).isEqualTo(taskId)
+ assertThat(op.launchOptions?.getInt(keyLaunchWindowingMode, WINDOWING_MODE_UNDEFINED))
+ .isEqualTo(windowingMode)
}
private fun WindowContainerTransaction?.anyDensityConfigChange(
token: WindowContainerToken
): Boolean {
- return this?.changes?.any { change ->
- change.key == token.asBinder() && ((change.value.configSetMask and CONFIG_DENSITY) != 0)
- } ?: false
+ return this?.changes?.any { change ->
+ change.key == token.asBinder() && ((change.value.configSetMask and CONFIG_DENSITY) != 0)
+ } ?: false
}
private fun WindowContainerTransaction?.anyWindowingModeChange(
- token: WindowContainerToken
+ token: WindowContainerToken
): Boolean {
-return this?.changes?.any { change ->
- change.key == token.asBinder() && change.value.windowingMode >= 0
-} ?: false
+ return this?.changes?.any { change ->
+ change.key == token.asBinder() && change.value.windowingMode >= 0
+ } ?: false
}
private fun createTaskInfo(id: Int) =
RecentTaskInfo().apply {
- taskId = id
- token = WindowContainerToken(mock(IWindowContainerToken::class.java))
+ taskId = id
+ token = WindowContainerToken(mock(IWindowContainerToken::class.java))
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt
index 1e4d108a9cda..e6f1fcf7f14f 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt
@@ -42,12 +42,12 @@ import com.android.internal.jank.InteractionJankMonitor
import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION
import com.android.wm.shell.ShellTaskOrganizer
import com.android.wm.shell.ShellTestCase
-import com.android.wm.shell.sysui.ShellController
import com.android.wm.shell.common.ShellExecutor
import com.android.wm.shell.desktopmode.DesktopTestHelpers.createFreeformTask
import com.android.wm.shell.desktopmode.persistence.DesktopPersistentRepository
import com.android.wm.shell.desktopmode.persistence.DesktopRepositoryInitializer
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
+import com.android.wm.shell.sysui.ShellController
import com.android.wm.shell.sysui.ShellInit
import com.android.wm.shell.transition.TransitionInfoBuilder
import com.android.wm.shell.transition.Transitions
@@ -85,9 +85,7 @@ import org.mockito.quality.Strictness
@ExperimentalCoroutinesApi
class DesktopTasksLimiterTest : ShellTestCase() {
- @JvmField
- @Rule
- val setFlagsRule = SetFlagsRule()
+ @JvmField @Rule val setFlagsRule = SetFlagsRule()
@Mock lateinit var shellTaskOrganizer: ShellTaskOrganizer
@Mock lateinit var transitions: Transitions
@@ -108,9 +106,12 @@ class DesktopTasksLimiterTest : ShellTestCase() {
@Before
fun setUp() {
- mockitoSession = ExtendedMockito.mockitoSession().strictness(Strictness.LENIENT)
- .spyStatic(DesktopModeStatus::class.java).startMocking()
- doReturn(true).`when`{ DesktopModeStatus.canEnterDesktopMode(any()) }
+ mockitoSession =
+ ExtendedMockito.mockitoSession()
+ .strictness(Strictness.LENIENT)
+ .spyStatic(DesktopModeStatus::class.java)
+ .startMocking()
+ doReturn(true).`when` { DesktopModeStatus.canEnterDesktopMode(any()) }
shellInit = spy(ShellInit(testExecutor))
Dispatchers.setMain(StandardTestDispatcher())
testScope = CoroutineScope(Dispatchers.Unconfined + SupervisorJob())
@@ -123,12 +124,19 @@ class DesktopTasksLimiterTest : ShellTestCase() {
persistentRepository,
repositoryInitializer,
testScope,
- userManager
+ userManager,
)
desktopTaskRepo = userRepositories.current
desktopTasksLimiter =
- DesktopTasksLimiter(transitions, userRepositories, shellTaskOrganizer, MAX_TASK_LIMIT,
- interactionJankMonitor, mContext, handler)
+ DesktopTasksLimiter(
+ transitions,
+ userRepositories,
+ shellTaskOrganizer,
+ MAX_TASK_LIMIT,
+ interactionJankMonitor,
+ mContext,
+ handler,
+ )
}
@After
@@ -140,16 +148,30 @@ class DesktopTasksLimiterTest : ShellTestCase() {
@Test
fun createDesktopTasksLimiter_withZeroLimit_shouldThrow() {
assertFailsWith<IllegalArgumentException> {
- DesktopTasksLimiter(transitions, userRepositories, shellTaskOrganizer, 0,
- interactionJankMonitor, mContext, handler)
+ DesktopTasksLimiter(
+ transitions,
+ userRepositories,
+ shellTaskOrganizer,
+ 0,
+ interactionJankMonitor,
+ mContext,
+ handler,
+ )
}
}
@Test
fun createDesktopTasksLimiter_withNegativeLimit_shouldThrow() {
assertFailsWith<IllegalArgumentException> {
- DesktopTasksLimiter(transitions, userRepositories, shellTaskOrganizer, -5,
- interactionJankMonitor, mContext, handler)
+ DesktopTasksLimiter(
+ transitions,
+ userRepositories,
+ shellTaskOrganizer,
+ -5,
+ interactionJankMonitor,
+ mContext,
+ handler,
+ )
}
}
@@ -168,11 +190,14 @@ class DesktopTasksLimiterTest : ShellTestCase() {
val task = setUpFreeformTask()
markTaskHidden(task)
- desktopTasksLimiter.getTransitionObserver().onTransitionReady(
+ desktopTasksLimiter
+ .getTransitionObserver()
+ .onTransitionReady(
Binder() /* transition */,
TransitionInfoBuilder(TRANSIT_OPEN).addChange(TRANSIT_TO_BACK, task).build(),
StubTransaction() /* startTransaction */,
- StubTransaction() /* finishTransaction */)
+ StubTransaction(), /* finishTransaction */
+ )
assertThat(desktopTaskRepo.isMinimizedTask(taskId = task.taskId)).isFalse()
}
@@ -184,13 +209,19 @@ class DesktopTasksLimiterTest : ShellTestCase() {
val task = setUpFreeformTask()
markTaskHidden(task)
desktopTasksLimiter.addPendingMinimizeChange(
- pendingTransition, displayId = DEFAULT_DISPLAY, taskId = task.taskId)
+ pendingTransition,
+ displayId = DEFAULT_DISPLAY,
+ taskId = task.taskId,
+ )
- desktopTasksLimiter.getTransitionObserver().onTransitionReady(
- taskTransition /* transition */,
- TransitionInfoBuilder(TRANSIT_OPEN).addChange(TRANSIT_TO_BACK, task).build(),
- StubTransaction() /* startTransaction */,
- StubTransaction() /* finishTransaction */)
+ desktopTasksLimiter
+ .getTransitionObserver()
+ .onTransitionReady(
+ taskTransition /* transition */,
+ TransitionInfoBuilder(TRANSIT_OPEN).addChange(TRANSIT_TO_BACK, task).build(),
+ StubTransaction() /* startTransaction */,
+ StubTransaction(), /* finishTransaction */
+ )
assertThat(desktopTaskRepo.isMinimizedTask(taskId = task.taskId)).isFalse()
}
@@ -201,13 +232,19 @@ class DesktopTasksLimiterTest : ShellTestCase() {
val task = setUpFreeformTask()
markTaskVisible(task)
desktopTasksLimiter.addPendingMinimizeChange(
- transition, displayId = DEFAULT_DISPLAY, taskId = task.taskId)
+ transition,
+ displayId = DEFAULT_DISPLAY,
+ taskId = task.taskId,
+ )
- desktopTasksLimiter.getTransitionObserver().onTransitionReady(
+ desktopTasksLimiter
+ .getTransitionObserver()
+ .onTransitionReady(
transition,
TransitionInfoBuilder(TRANSIT_OPEN).build(),
StubTransaction() /* startTransaction */,
- StubTransaction() /* finishTransaction */)
+ StubTransaction(), /* finishTransaction */
+ )
assertThat(desktopTaskRepo.isMinimizedTask(taskId = task.taskId)).isFalse()
}
@@ -218,13 +255,19 @@ class DesktopTasksLimiterTest : ShellTestCase() {
val task = setUpFreeformTask()
markTaskHidden(task)
desktopTasksLimiter.addPendingMinimizeChange(
- transition, displayId = DEFAULT_DISPLAY, taskId = task.taskId)
+ transition,
+ displayId = DEFAULT_DISPLAY,
+ taskId = task.taskId,
+ )
- desktopTasksLimiter.getTransitionObserver().onTransitionReady(
+ desktopTasksLimiter
+ .getTransitionObserver()
+ .onTransitionReady(
transition,
TransitionInfoBuilder(TRANSIT_OPEN).build(),
StubTransaction() /* startTransaction */,
- StubTransaction() /* finishTransaction */)
+ StubTransaction(), /* finishTransaction */
+ )
assertThat(desktopTaskRepo.isMinimizedTask(taskId = task.taskId)).isTrue()
}
@@ -234,13 +277,19 @@ class DesktopTasksLimiterTest : ShellTestCase() {
val transition = Binder()
val task = setUpFreeformTask()
desktopTasksLimiter.addPendingMinimizeChange(
- transition, displayId = DEFAULT_DISPLAY, taskId = task.taskId)
+ transition,
+ displayId = DEFAULT_DISPLAY,
+ taskId = task.taskId,
+ )
- desktopTasksLimiter.getTransitionObserver().onTransitionReady(
+ desktopTasksLimiter
+ .getTransitionObserver()
+ .onTransitionReady(
transition,
TransitionInfoBuilder(TRANSIT_OPEN).addChange(TRANSIT_TO_BACK, task).build(),
StubTransaction() /* startTransaction */,
- StubTransaction() /* finishTransaction */)
+ StubTransaction(), /* finishTransaction */
+ )
assertThat(desktopTaskRepo.isMinimizedTask(taskId = task.taskId)).isTrue()
}
@@ -251,22 +300,29 @@ class DesktopTasksLimiterTest : ShellTestCase() {
val transition = Binder()
val task = setUpFreeformTask()
desktopTasksLimiter.addPendingMinimizeChange(
- transition, displayId = DEFAULT_DISPLAY, taskId = task.taskId)
-
- val change = TransitionInfo.Change(task.token, mock(SurfaceControl::class.java)).apply {
- mode = TRANSIT_TO_BACK
- taskInfo = task
- setStartAbsBounds(bounds)
- }
- desktopTasksLimiter.getTransitionObserver().onTransitionReady(
transition,
- TransitionInfo(TRANSIT_OPEN, TransitionInfo.FLAG_NONE).apply { addChange(change) },
- StubTransaction() /* startTransaction */,
- StubTransaction() /* finishTransaction */)
+ displayId = DEFAULT_DISPLAY,
+ taskId = task.taskId,
+ )
+
+ val change =
+ TransitionInfo.Change(task.token, mock(SurfaceControl::class.java)).apply {
+ mode = TRANSIT_TO_BACK
+ taskInfo = task
+ setStartAbsBounds(bounds)
+ }
+ desktopTasksLimiter
+ .getTransitionObserver()
+ .onTransitionReady(
+ transition,
+ TransitionInfo(TRANSIT_OPEN, TransitionInfo.FLAG_NONE).apply { addChange(change) },
+ StubTransaction() /* startTransaction */,
+ StubTransaction(), /* finishTransaction */
+ )
assertThat(desktopTaskRepo.isMinimizedTask(taskId = task.taskId)).isTrue()
- assertThat(desktopTaskRepo.removeBoundsBeforeMinimize(taskId = task.taskId)).isEqualTo(
- bounds)
+ assertThat(desktopTaskRepo.removeBoundsBeforeMinimize(taskId = task.taskId))
+ .isEqualTo(bounds)
}
@Test
@@ -275,15 +331,22 @@ class DesktopTasksLimiterTest : ShellTestCase() {
val newTransition = Binder()
val task = setUpFreeformTask()
desktopTasksLimiter.addPendingMinimizeChange(
- mergedTransition, displayId = DEFAULT_DISPLAY, taskId = task.taskId)
- desktopTasksLimiter.getTransitionObserver().onTransitionMerged(
- mergedTransition, newTransition)
-
- desktopTasksLimiter.getTransitionObserver().onTransitionReady(
- newTransition,
- TransitionInfoBuilder(TRANSIT_OPEN).addChange(TRANSIT_TO_BACK, task).build(),
- StubTransaction() /* startTransaction */,
- StubTransaction() /* finishTransaction */)
+ mergedTransition,
+ displayId = DEFAULT_DISPLAY,
+ taskId = task.taskId,
+ )
+ desktopTasksLimiter
+ .getTransitionObserver()
+ .onTransitionMerged(mergedTransition, newTransition)
+
+ desktopTasksLimiter
+ .getTransitionObserver()
+ .onTransitionReady(
+ newTransition,
+ TransitionInfoBuilder(TRANSIT_OPEN).addChange(TRANSIT_TO_BACK, task).build(),
+ StubTransaction() /* startTransaction */,
+ StubTransaction(), /* finishTransaction */
+ )
assertThat(desktopTaskRepo.isMinimizedTask(taskId = task.taskId)).isTrue()
}
@@ -297,7 +360,9 @@ class DesktopTasksLimiterTest : ShellTestCase() {
val wct = WindowContainerTransaction()
desktopTasksLimiter.leftoverMinimizedTasksRemover.removeLeftoverMinimizedTasks(
- DEFAULT_DISPLAY, wct)
+ DEFAULT_DISPLAY,
+ wct,
+ )
assertThat(wct.isEmpty).isTrue()
}
@@ -307,7 +372,9 @@ class DesktopTasksLimiterTest : ShellTestCase() {
fun removeLeftoverMinimizedTasks_noMinimizedTasks_doesNothing() {
val wct = WindowContainerTransaction()
desktopTasksLimiter.leftoverMinimizedTasksRemover.removeLeftoverMinimizedTasks(
- DEFAULT_DISPLAY, wct)
+ DEFAULT_DISPLAY,
+ wct,
+ )
assertThat(wct.isEmpty).isTrue()
}
@@ -322,7 +389,9 @@ class DesktopTasksLimiterTest : ShellTestCase() {
val wct = WindowContainerTransaction()
desktopTasksLimiter.leftoverMinimizedTasksRemover.removeLeftoverMinimizedTasks(
- DEFAULT_DISPLAY, wct)
+ DEFAULT_DISPLAY,
+ wct,
+ )
assertThat(wct.hierarchyOps).hasSize(2)
assertThat(wct.hierarchyOps[0].type).isEqualTo(HIERARCHY_OP_TYPE_REMOVE_TASK)
@@ -351,10 +420,11 @@ class DesktopTasksLimiterTest : ShellTestCase() {
val wct = WindowContainerTransaction()
val minimizedTaskId =
- desktopTasksLimiter.addAndGetMinimizeTaskChanges(
- displayId = DEFAULT_DISPLAY,
- wct = wct,
- newFrontTaskId = setUpFreeformTask().taskId)
+ desktopTasksLimiter.addAndGetMinimizeTaskChanges(
+ displayId = DEFAULT_DISPLAY,
+ wct = wct,
+ newFrontTaskId = setUpFreeformTask().taskId,
+ )
assertThat(minimizedTaskId).isNull()
assertThat(wct.hierarchyOps).isEmpty() // No reordering operations added
@@ -367,10 +437,11 @@ class DesktopTasksLimiterTest : ShellTestCase() {
val wct = WindowContainerTransaction()
val minimizedTaskId =
- desktopTasksLimiter.addAndGetMinimizeTaskChanges(
- displayId = DEFAULT_DISPLAY,
- wct = wct,
- newFrontTaskId = setUpFreeformTask().taskId)
+ desktopTasksLimiter.addAndGetMinimizeTaskChanges(
+ displayId = DEFAULT_DISPLAY,
+ wct = wct,
+ newFrontTaskId = setUpFreeformTask().taskId,
+ )
assertThat(minimizedTaskId).isEqualTo(tasks.first().taskId)
assertThat(wct.hierarchyOps.size).isEqualTo(1)
@@ -385,10 +456,11 @@ class DesktopTasksLimiterTest : ShellTestCase() {
val wct = WindowContainerTransaction()
val minimizedTaskId =
- desktopTasksLimiter.addAndGetMinimizeTaskChanges(
- displayId = 0,
- wct = wct,
- newFrontTaskId = setUpFreeformTask().taskId)
+ desktopTasksLimiter.addAndGetMinimizeTaskChanges(
+ displayId = 0,
+ wct = wct,
+ newFrontTaskId = setUpFreeformTask().taskId,
+ )
assertThat(minimizedTaskId).isNull()
assertThat(wct.hierarchyOps).isEmpty() // No reordering operations added
@@ -398,8 +470,8 @@ class DesktopTasksLimiterTest : ShellTestCase() {
fun getTaskToMinimize_tasksWithinLimit_returnsNull() {
val tasks = (1..MAX_TASK_LIMIT).map { setUpFreeformTask() }
- val minimizedTask = desktopTasksLimiter.getTaskIdToMinimize(
- visibleOrderedTasks = tasks.map { it.taskId })
+ val minimizedTask =
+ desktopTasksLimiter.getTaskIdToMinimize(visibleOrderedTasks = tasks.map { it.taskId })
assertThat(minimizedTask).isNull()
}
@@ -408,8 +480,8 @@ class DesktopTasksLimiterTest : ShellTestCase() {
fun getTaskToMinimize_tasksAboveLimit_returnsBackTask() {
val tasks = (1..MAX_TASK_LIMIT + 1).map { setUpFreeformTask() }
- val minimizedTask = desktopTasksLimiter.getTaskIdToMinimize(
- visibleOrderedTasks = tasks.map { it.taskId })
+ val minimizedTask =
+ desktopTasksLimiter.getTaskIdToMinimize(visibleOrderedTasks = tasks.map { it.taskId })
// first == front, last == back
assertThat(minimizedTask).isEqualTo(tasks.last().taskId)
@@ -418,12 +490,19 @@ class DesktopTasksLimiterTest : ShellTestCase() {
@Test
fun getTaskToMinimize_tasksAboveLimit_otherLimit_returnsBackTask() {
desktopTasksLimiter =
- DesktopTasksLimiter(transitions, userRepositories, shellTaskOrganizer, MAX_TASK_LIMIT2,
- interactionJankMonitor, mContext, handler)
+ DesktopTasksLimiter(
+ transitions,
+ userRepositories,
+ shellTaskOrganizer,
+ MAX_TASK_LIMIT2,
+ interactionJankMonitor,
+ mContext,
+ handler,
+ )
val tasks = (1..MAX_TASK_LIMIT2 + 1).map { setUpFreeformTask() }
- val minimizedTask = desktopTasksLimiter.getTaskIdToMinimize(
- visibleOrderedTasks = tasks.map { it.taskId })
+ val minimizedTask =
+ desktopTasksLimiter.getTaskIdToMinimize(visibleOrderedTasks = tasks.map { it.taskId })
// first == front, last == back
assertThat(minimizedTask).isEqualTo(tasks.last().taskId)
@@ -433,9 +512,11 @@ class DesktopTasksLimiterTest : ShellTestCase() {
fun getTaskToMinimize_withNewTask_tasksAboveLimit_returnsBackTask() {
val tasks = (1..MAX_TASK_LIMIT).map { setUpFreeformTask() }
- val minimizedTask = desktopTasksLimiter.getTaskIdToMinimize(
+ val minimizedTask =
+ desktopTasksLimiter.getTaskIdToMinimize(
visibleOrderedTasks = tasks.map { it.taskId },
- newTaskIdInFront = setUpFreeformTask().taskId)
+ newTaskIdInFront = setUpFreeformTask().taskId,
+ )
// first == front, last == back
assertThat(minimizedTask).isEqualTo(tasks.last().taskId)
@@ -444,10 +525,12 @@ class DesktopTasksLimiterTest : ShellTestCase() {
@Test
fun getTaskToMinimize_tasksAtLimit_newIntentReturnsBackTask() {
val tasks = (1..MAX_TASK_LIMIT).map { setUpFreeformTask() }
- val minimizedTask = desktopTasksLimiter.getTaskIdToMinimize(
- visibleOrderedTasks = tasks.map { it.taskId },
- newTaskIdInFront = null,
- launchingNewIntent = true)
+ val minimizedTask =
+ desktopTasksLimiter.getTaskIdToMinimize(
+ visibleOrderedTasks = tasks.map { it.taskId },
+ newTaskIdInFront = null,
+ launchingNewIntent = true,
+ )
// first == front, last == back
assertThat(minimizedTask).isEqualTo(tasks.last().taskId)
@@ -459,25 +542,28 @@ class DesktopTasksLimiterTest : ShellTestCase() {
val transition = Binder()
val task = setUpFreeformTask()
desktopTasksLimiter.addPendingMinimizeChange(
- transition, displayId = DEFAULT_DISPLAY, taskId = task.taskId)
-
- desktopTasksLimiter.getTransitionObserver().onTransitionReady(
transition,
- TransitionInfoBuilder(TRANSIT_OPEN).build(),
- StubTransaction() /* startTransaction */,
- StubTransaction() /* finishTransaction */)
+ displayId = DEFAULT_DISPLAY,
+ taskId = task.taskId,
+ )
+
+ desktopTasksLimiter
+ .getTransitionObserver()
+ .onTransitionReady(
+ transition,
+ TransitionInfoBuilder(TRANSIT_OPEN).build(),
+ StubTransaction() /* startTransaction */,
+ StubTransaction(), /* finishTransaction */
+ )
desktopTasksLimiter.getTransitionObserver().onTransitionStarting(transition)
- verify(interactionJankMonitor).begin(
- any(),
- eq(mContext),
- eq(handler),
- eq(CUJ_DESKTOP_MODE_MINIMIZE_WINDOW))
+ verify(interactionJankMonitor)
+ .begin(any(), eq(mContext), eq(handler), eq(CUJ_DESKTOP_MODE_MINIMIZE_WINDOW))
- desktopTasksLimiter.getTransitionObserver().onTransitionFinished(
- transition,
- /* aborted = */ false)
+ desktopTasksLimiter
+ .getTransitionObserver()
+ .onTransitionFinished(transition, /* aborted= */ false)
verify(interactionJankMonitor).end(eq(CUJ_DESKTOP_MODE_MINIMIZE_WINDOW))
}
@@ -488,26 +574,28 @@ class DesktopTasksLimiterTest : ShellTestCase() {
val transition = Binder()
val task = setUpFreeformTask()
desktopTasksLimiter.addPendingMinimizeChange(
- transition, displayId = DEFAULT_DISPLAY, taskId = task.taskId)
-
- desktopTasksLimiter.getTransitionObserver().onTransitionReady(
transition,
- TransitionInfoBuilder(TRANSIT_OPEN).build(),
- StubTransaction() /* startTransaction */,
- StubTransaction() /* finishTransaction */)
+ displayId = DEFAULT_DISPLAY,
+ taskId = task.taskId,
+ )
+
+ desktopTasksLimiter
+ .getTransitionObserver()
+ .onTransitionReady(
+ transition,
+ TransitionInfoBuilder(TRANSIT_OPEN).build(),
+ StubTransaction() /* startTransaction */,
+ StubTransaction(), /* finishTransaction */
+ )
desktopTasksLimiter.getTransitionObserver().onTransitionStarting(transition)
- verify(interactionJankMonitor).begin(
- any(),
- eq(mContext),
- eq(handler),
- eq(CUJ_DESKTOP_MODE_MINIMIZE_WINDOW),
- )
+ verify(interactionJankMonitor)
+ .begin(any(), eq(mContext), eq(handler), eq(CUJ_DESKTOP_MODE_MINIMIZE_WINDOW))
- desktopTasksLimiter.getTransitionObserver().onTransitionFinished(
- transition,
- /* aborted = */ true)
+ desktopTasksLimiter
+ .getTransitionObserver()
+ .onTransitionFinished(transition, /* aborted= */ true)
verify(interactionJankMonitor).cancel(eq(CUJ_DESKTOP_MODE_MINIMIZE_WINDOW))
}
@@ -519,25 +607,28 @@ class DesktopTasksLimiterTest : ShellTestCase() {
val newTransition = Binder()
val task = setUpFreeformTask()
desktopTasksLimiter.addPendingMinimizeChange(
- mergedTransition, displayId = DEFAULT_DISPLAY, taskId = task.taskId)
-
- desktopTasksLimiter.getTransitionObserver().onTransitionReady(
mergedTransition,
- TransitionInfoBuilder(TRANSIT_OPEN).build(),
- StubTransaction() /* startTransaction */,
- StubTransaction() /* finishTransaction */)
+ displayId = DEFAULT_DISPLAY,
+ taskId = task.taskId,
+ )
+
+ desktopTasksLimiter
+ .getTransitionObserver()
+ .onTransitionReady(
+ mergedTransition,
+ TransitionInfoBuilder(TRANSIT_OPEN).build(),
+ StubTransaction() /* startTransaction */,
+ StubTransaction(), /* finishTransaction */
+ )
desktopTasksLimiter.getTransitionObserver().onTransitionStarting(mergedTransition)
- verify(interactionJankMonitor).begin(
- any(),
- eq(mContext),
- eq(handler),
- eq(CUJ_DESKTOP_MODE_MINIMIZE_WINDOW))
+ verify(interactionJankMonitor)
+ .begin(any(), eq(mContext), eq(handler), eq(CUJ_DESKTOP_MODE_MINIMIZE_WINDOW))
- desktopTasksLimiter.getTransitionObserver().onTransitionMerged(
- mergedTransition,
- newTransition)
+ desktopTasksLimiter
+ .getTransitionObserver()
+ .onTransitionMerged(mergedTransition, newTransition)
verify(interactionJankMonitor).end(eq(CUJ_DESKTOP_MODE_MINIMIZE_WINDOW))
}
@@ -550,19 +641,11 @@ class DesktopTasksLimiterTest : ShellTestCase() {
}
private fun markTaskVisible(task: RunningTaskInfo) {
- desktopTaskRepo.updateTask(
- task.displayId,
- task.taskId,
- isVisible = true
- )
+ desktopTaskRepo.updateTask(task.displayId, task.taskId, isVisible = true)
}
private fun markTaskHidden(task: RunningTaskInfo) {
- desktopTaskRepo.updateTask(
- task.displayId,
- task.taskId,
- isVisible = false
- )
+ desktopTaskRepo.updateTask(task.displayId, task.taskId, isVisible = false)
}
private companion object {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt
index b31a3f5fa642..c9623bcd5c16 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt
@@ -99,7 +99,7 @@ class DesktopTasksTransitionObserverTest {
shellTaskOrganizer,
mixedHandler,
backAnimationController,
- shellInit
+ shellInit,
)
}
@@ -139,7 +139,10 @@ class DesktopTasksTransitionObserverTest {
verify(taskRepository).minimizeTask(task.displayId, task.taskId)
val pendingTransition =
DesktopMixedTransitionHandler.PendingMixedTransition.Minimize(
- transition, task.taskId, isLastTask = false)
+ transition,
+ task.taskId,
+ isLastTask = false,
+ )
verify(mixedHandler).addPendingMixedTransition(pendingTransition)
}
@@ -162,7 +165,10 @@ class DesktopTasksTransitionObserverTest {
verify(taskRepository).minimizeTask(task.displayId, task.taskId)
val pendingTransition =
DesktopMixedTransitionHandler.PendingMixedTransition.Minimize(
- transition, task.taskId, isLastTask = true)
+ transition,
+ task.taskId,
+ isLastTask = true,
+ )
verify(mixedHandler).addPendingMixedTransition(pendingTransition)
}
@@ -251,7 +257,8 @@ class DesktopTasksTransitionObserverTest {
parent = null
taskInfo = task
flags = flags
- })
+ }
+ )
if (withWallpaper) {
addChange(
Change(mock(), mock()).apply {
@@ -259,14 +266,15 @@ class DesktopTasksTransitionObserverTest {
parent = null
taskInfo = createWallpaperTaskInfo()
flags = flags
- })
+ }
+ )
}
}
}
private fun createOpenChangeTransition(
task: RunningTaskInfo?,
- type: Int = TRANSIT_OPEN
+ type: Int = TRANSIT_OPEN,
): TransitionInfo {
return TransitionInfo(TRANSIT_OPEN, 0 /* flags */).apply {
addChange(
@@ -275,7 +283,8 @@ class DesktopTasksTransitionObserverTest {
parent = null
taskInfo = task
flags = flags
- })
+ }
+ )
}
}
@@ -287,13 +296,14 @@ class DesktopTasksTransitionObserverTest {
parent = null
taskInfo = task
flags = flags
- })
+ }
+ )
}
}
private fun getLatestWct(
@WindowManager.TransitionType type: Int = TRANSIT_OPEN,
- handlerClass: Class<out Transitions.TransitionHandler>? = null
+ handlerClass: Class<out Transitions.TransitionHandler>? = null,
): WindowContainerTransaction {
val arg = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
if (handlerClass == null) {
@@ -330,8 +340,6 @@ class DesktopTasksTransitionObserverTest {
RunningTaskInfo().apply {
token = mock<WindowContainerToken>()
baseIntent =
- Intent().apply {
- component = DesktopWallpaperActivity.wallpaperActivityComponent
- }
+ Intent().apply { component = DesktopWallpaperActivity.wallpaperActivityComponent }
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopUserRepositoriesTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopUserRepositoriesTest.kt
index a2e939d86adb..b9e307fa5973 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopUserRepositoriesTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopUserRepositoriesTest.kt
@@ -82,20 +82,20 @@ class DesktopUserRepositoriesTest : ShellTestCase() {
datastoreScope = CoroutineScope(Dispatchers.Unconfined + SupervisorJob())
shellInit = spy(ShellInit(testExecutor))
- val profiles: MutableList<UserInfo> = mutableListOf(
- UserInfo(USER_ID_1, "User 1", 0),
- UserInfo(PROFILE_ID_2, "Profile 2", 0))
+ val profiles: MutableList<UserInfo> =
+ mutableListOf(UserInfo(USER_ID_1, "User 1", 0), UserInfo(PROFILE_ID_2, "Profile 2", 0))
whenever(userManager.getProfiles(USER_ID_1)).thenReturn(profiles)
- userRepositories = DesktopUserRepositories(
- context,
- shellInit,
- shellController,
- persistentRepository,
- repositoryInitializer,
- datastoreScope,
- userManager
- )
+ userRepositories =
+ DesktopUserRepositories(
+ context,
+ shellInit,
+ shellController,
+ persistentRepository,
+ repositoryInitializer,
+ datastoreScope,
+ userManager,
+ )
}
@After
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt
index 13528b947609..e4eff9f1d592 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt
@@ -61,9 +61,7 @@ import org.mockito.quality.Strictness
@RunWithLooper
@RunWith(AndroidTestingRunner::class)
class DragToDesktopTransitionHandlerTest : ShellTestCase() {
- @JvmField
- @Rule
- val mAnimatorTestRule = AnimatorTestRule(this)
+ @JvmField @Rule val mAnimatorTestRule = AnimatorTestRule(this)
@Mock private lateinit var transitions: Transitions
@Mock private lateinit var taskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer
@@ -123,11 +121,11 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() {
info =
createTransitionInfo(
type = TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP,
- draggedTask = task
+ draggedTask = task,
),
startTransaction = mock(),
finishTransaction = mock(),
- finishCallback = {}
+ finishCallback = {},
)
verify(dragAnimator).startAnimation()
@@ -137,13 +135,13 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() {
fun startDragToDesktop_cancelledBeforeReady_startCancelTransition() {
performEarlyCancel(
defaultHandler,
- DragToDesktopTransitionHandler.CancelState.STANDARD_CANCEL
+ DragToDesktopTransitionHandler.CancelState.STANDARD_CANCEL,
)
verify(transitions)
.startTransition(
eq(TRANSIT_DESKTOP_MODE_CANCEL_DRAG_TO_DESKTOP),
any(),
- eq(defaultHandler)
+ eq(defaultHandler),
)
}
@@ -151,7 +149,7 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() {
fun startDragToDesktop_cancelledBeforeReady_verifySplitLeftCancel() {
performEarlyCancel(
defaultHandler,
- DragToDesktopTransitionHandler.CancelState.CANCEL_SPLIT_LEFT
+ DragToDesktopTransitionHandler.CancelState.CANCEL_SPLIT_LEFT,
)
verify(splitScreenController)
.requestEnterSplitSelect(any(), any(), eq(SPLIT_POSITION_TOP_OR_LEFT), any())
@@ -161,7 +159,7 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() {
fun startDragToDesktop_cancelledBeforeReady_verifySplitRightCancel() {
performEarlyCancel(
defaultHandler,
- DragToDesktopTransitionHandler.CancelState.CANCEL_SPLIT_RIGHT
+ DragToDesktopTransitionHandler.CancelState.CANCEL_SPLIT_RIGHT,
)
verify(splitScreenController)
.requestEnterSplitSelect(any(), any(), eq(SPLIT_POSITION_BOTTOM_OR_RIGHT), any())
@@ -214,7 +212,7 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() {
.startTransition(
eq(TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP),
any(),
- eq(defaultHandler)
+ eq(defaultHandler),
)
}
@@ -277,14 +275,18 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() {
val startToken = startDrag(defaultHandler)
// Then user cancelled after it had already started.
- val cancelToken = cancelDragToDesktopTransition(
- defaultHandler, DragToDesktopTransitionHandler.CancelState.STANDARD_CANCEL)
+ val cancelToken =
+ cancelDragToDesktopTransition(
+ defaultHandler,
+ DragToDesktopTransitionHandler.CancelState.STANDARD_CANCEL,
+ )
defaultHandler.mergeAnimation(
cancelToken,
TransitionInfo(TRANSIT_DESKTOP_MODE_CANCEL_DRAG_TO_DESKTOP, 0),
mock<SurfaceControl.Transaction>(),
startToken,
- mock<Transitions.TransitionFinishCallback>())
+ mock<Transitions.TransitionFinishCallback>(),
+ )
// Cancel animation should run since it had already started.
verify(dragAnimator).cancelAnimator()
@@ -296,8 +298,11 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() {
val startToken = startDrag(defaultHandler)
// Then user cancelled after it had already started.
- val cancelToken = cancelDragToDesktopTransition(
- defaultHandler, DragToDesktopTransitionHandler.CancelState.STANDARD_CANCEL)
+ val cancelToken =
+ cancelDragToDesktopTransition(
+ defaultHandler,
+ DragToDesktopTransitionHandler.CancelState.STANDARD_CANCEL,
+ )
defaultHandler.onTransitionConsumed(cancelToken, aborted = true, null)
// Cancel animation should run since it had already started.
@@ -360,7 +365,7 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() {
.startTransition(
eq(TRANSIT_DESKTOP_MODE_CANCEL_DRAG_TO_DESKTOP),
any(),
- eq(defaultHandler)
+ eq(defaultHandler),
)
}
@@ -374,7 +379,7 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() {
.startTransition(
eq(TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP),
any(),
- eq(defaultHandler)
+ eq(defaultHandler),
)
}
@@ -390,7 +395,7 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() {
info = createTransitionInfo(type = TRANSIT_OPEN, draggedTask = task),
t = transaction,
mergeTarget = mock(),
- finishCallback = finishCallback
+ finishCallback = finishCallback,
)
// Should NOT have any transaction changes
@@ -414,11 +419,11 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() {
info =
createTransitionInfo(
type = TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP,
- draggedTask = task
+ draggedTask = task,
),
t = mergedStartTransaction,
mergeTarget = startTransition,
- finishCallback = finishCallback
+ finishCallback = finishCallback,
)
// Should show dragged task layer in start and finish transaction
@@ -446,11 +451,11 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() {
info =
createTransitionInfo(
type = TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP,
- draggedTask = task
+ draggedTask = task,
),
t = mergedStartTransaction,
mergeTarget = startTransition,
- finishCallback = finishCallback
+ finishCallback = finishCallback,
)
// Should show dragged task layer in start and finish transaction
@@ -475,7 +480,7 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() {
assertEquals(
"Expects to return system properties stored value",
/* expected= */ value,
- /* actual= */ SpringDragToDesktopTransitionHandler.propertyValue(name)
+ /* actual= */ SpringDragToDesktopTransitionHandler.propertyValue(name),
)
}
@@ -491,7 +496,7 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() {
assertEquals(
"Expects to return scaled system properties stored value",
/* expected= */ value / scale,
- /* actual= */ SpringDragToDesktopTransitionHandler.propertyValue(name, scale = scale)
+ /* actual= */ SpringDragToDesktopTransitionHandler.propertyValue(name, scale = scale),
)
}
@@ -508,8 +513,8 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() {
/* expected= */ defaultValue,
/* actual= */ SpringDragToDesktopTransitionHandler.propertyValue(
name,
- default = defaultValue
- )
+ default = defaultValue,
+ ),
)
}
@@ -530,8 +535,8 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() {
/* actual= */ SpringDragToDesktopTransitionHandler.propertyValue(
name,
default = defaultValue,
- scale = scale
- )
+ scale = scale,
+ ),
)
}
@@ -542,8 +547,8 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() {
defaultHandler.onTransitionConsumed(transition, aborted = true, mock())
verify(mockInteractionJankMonitor).cancel(eq(CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_HOLD))
- verify(mockInteractionJankMonitor, times(0)).cancel(
- eq(CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_RELEASE))
+ verify(mockInteractionJankMonitor, times(0))
+ .cancel(eq(CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_RELEASE))
}
@Test
@@ -554,13 +559,14 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() {
defaultHandler.onTaskResizeAnimationListener = mock()
defaultHandler.mergeAnimation(
transition = endTransition,
- info = createTransitionInfo(
- type = TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP,
- draggedTask = task
- ),
+ info =
+ createTransitionInfo(
+ type = TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP,
+ draggedTask = task,
+ ),
t = mock<SurfaceControl.Transaction>(),
mergeTarget = startTransition,
- finishCallback = mock<Transitions.TransitionFinishCallback>()
+ finishCallback = mock<Transitions.TransitionFinishCallback>(),
)
defaultHandler.onTransitionConsumed(endTransition, aborted = true, mock())
@@ -574,7 +580,7 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() {
private fun startDrag(
handler: DragToDesktopTransitionHandler,
task: RunningTaskInfo = createTask(),
- finishTransaction: SurfaceControl.Transaction = mock()
+ finishTransaction: SurfaceControl.Transaction = mock(),
): IBinder {
whenever(dragAnimator.position).thenReturn(PointF())
// Simulate transition is started and is ready to animate.
@@ -584,11 +590,11 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() {
info =
createTransitionInfo(
type = TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP,
- draggedTask = task
+ draggedTask = task,
),
startTransaction = mock(),
finishTransaction = finishTransaction,
- finishCallback = {}
+ finishCallback = {},
)
return transition
}
@@ -596,14 +602,14 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() {
private fun startDragToDesktopTransition(
handler: DragToDesktopTransitionHandler,
task: RunningTaskInfo,
- dragAnimator: MoveToDesktopAnimator
+ dragAnimator: MoveToDesktopAnimator,
): IBinder {
val token = mock<IBinder>()
whenever(
transitions.startTransition(
eq(TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP),
any(),
- eq(handler)
+ eq(handler),
)
)
.thenReturn(token)
@@ -613,13 +619,14 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() {
private fun cancelDragToDesktopTransition(
handler: DragToDesktopTransitionHandler,
- cancelState: DragToDesktopTransitionHandler.CancelState): IBinder {
+ cancelState: DragToDesktopTransitionHandler.CancelState,
+ ): IBinder {
val token = mock<IBinder>()
whenever(
transitions.startTransition(
eq(TRANSIT_DESKTOP_MODE_CANCEL_DRAG_TO_DESKTOP),
any(),
- eq(handler)
+ eq(handler),
)
)
.thenReturn(token)
@@ -630,7 +637,7 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() {
private fun performEarlyCancel(
handler: DragToDesktopTransitionHandler,
- cancelState: DragToDesktopTransitionHandler.CancelState
+ cancelState: DragToDesktopTransitionHandler.CancelState,
) {
val task = createTask()
// Simulate transition is started and is ready to animate.
@@ -643,11 +650,11 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() {
info =
createTransitionInfo(
type = TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP,
- draggedTask = task
+ draggedTask = task,
),
startTransaction = mock(),
finishTransaction = mock(),
- finishCallback = {}
+ finishCallback = {},
)
// Don't even animate the "drag" since it was already cancelled.
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/WindowDecorCaptionHandleRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/WindowDecorCaptionHandleRepositoryTest.kt
index 38c6ed90241c..e10253992bb5 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/WindowDecorCaptionHandleRepositoryTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/WindowDecorCaptionHandleRepositoryTest.kt
@@ -31,67 +31,71 @@ import org.junit.runner.RunWith
@SmallTest
@RunWith(AndroidTestingRunner::class)
class WindowDecorCaptionHandleRepositoryTest {
- private lateinit var captionHandleRepository: WindowDecorCaptionHandleRepository
+ private lateinit var captionHandleRepository: WindowDecorCaptionHandleRepository
- @Before
- fun setUp() {
- captionHandleRepository = WindowDecorCaptionHandleRepository()
- }
+ @Before
+ fun setUp() {
+ captionHandleRepository = WindowDecorCaptionHandleRepository()
+ }
- @Test
- fun initialState_noAction_returnsNoCaption() {
- // Check the initial value of `captionStateFlow`.
- assertThat(captionHandleRepository.captionStateFlow.value).isEqualTo(CaptionState.NoCaption)
- }
+ @Test
+ fun initialState_noAction_returnsNoCaption() {
+ // Check the initial value of `captionStateFlow`.
+ assertThat(captionHandleRepository.captionStateFlow.value).isEqualTo(CaptionState.NoCaption)
+ }
- @Test
- fun notifyCaptionChange_toAppHandleVisible_updatesStateWithCorrectData() {
- val taskInfo = createTaskInfo(WINDOWING_MODE_FULLSCREEN, GMAIL_PACKAGE_NAME)
- val appHandleCaptionState =
- CaptionState.AppHandle(
- runningTaskInfo = taskInfo,
- isHandleMenuExpanded = false,
- globalAppHandleBounds = Rect(/* left= */ 0, /* top= */ 1, /* right= */ 2, /* bottom= */ 3),
- isCapturedLinkAvailable = false)
+ @Test
+ fun notifyCaptionChange_toAppHandleVisible_updatesStateWithCorrectData() {
+ val taskInfo = createTaskInfo(WINDOWING_MODE_FULLSCREEN, GMAIL_PACKAGE_NAME)
+ val appHandleCaptionState =
+ CaptionState.AppHandle(
+ runningTaskInfo = taskInfo,
+ isHandleMenuExpanded = false,
+ globalAppHandleBounds =
+ Rect(/* left= */ 0, /* top= */ 1, /* right= */ 2, /* bottom= */ 3),
+ isCapturedLinkAvailable = false,
+ )
- captionHandleRepository.notifyCaptionChanged(appHandleCaptionState)
+ captionHandleRepository.notifyCaptionChanged(appHandleCaptionState)
- assertThat(captionHandleRepository.captionStateFlow.value).isEqualTo(appHandleCaptionState)
- }
+ assertThat(captionHandleRepository.captionStateFlow.value).isEqualTo(appHandleCaptionState)
+ }
- @Test
- fun notifyCaptionChange_toAppChipVisible_updatesStateWithCorrectData() {
- val taskInfo = createTaskInfo(WINDOWING_MODE_FREEFORM, GMAIL_PACKAGE_NAME)
- val appHeaderCaptionState =
- CaptionState.AppHeader(
- runningTaskInfo = taskInfo,
- isHeaderMenuExpanded = true,
- globalAppChipBounds = Rect(/* left= */ 0, /* top= */ 1, /* right= */ 2, /* bottom= */ 3),
- isCapturedLinkAvailable = false)
+ @Test
+ fun notifyCaptionChange_toAppChipVisible_updatesStateWithCorrectData() {
+ val taskInfo = createTaskInfo(WINDOWING_MODE_FREEFORM, GMAIL_PACKAGE_NAME)
+ val appHeaderCaptionState =
+ CaptionState.AppHeader(
+ runningTaskInfo = taskInfo,
+ isHeaderMenuExpanded = true,
+ globalAppChipBounds =
+ Rect(/* left= */ 0, /* top= */ 1, /* right= */ 2, /* bottom= */ 3),
+ isCapturedLinkAvailable = false,
+ )
- captionHandleRepository.notifyCaptionChanged(appHeaderCaptionState)
+ captionHandleRepository.notifyCaptionChanged(appHeaderCaptionState)
- assertThat(captionHandleRepository.captionStateFlow.value).isEqualTo(appHeaderCaptionState)
- }
+ assertThat(captionHandleRepository.captionStateFlow.value).isEqualTo(appHeaderCaptionState)
+ }
- @Test
- fun notifyCaptionChange_toNoCaption_updatesState() {
- captionHandleRepository.notifyCaptionChanged(CaptionState.NoCaption)
+ @Test
+ fun notifyCaptionChange_toNoCaption_updatesState() {
+ captionHandleRepository.notifyCaptionChanged(CaptionState.NoCaption)
- assertThat(captionHandleRepository.captionStateFlow.value).isEqualTo(CaptionState.NoCaption)
- }
+ assertThat(captionHandleRepository.captionStateFlow.value).isEqualTo(CaptionState.NoCaption)
+ }
- private fun createTaskInfo(
- deviceWindowingMode: Int = WINDOWING_MODE_UNDEFINED,
- runningTaskPackageName: String = LAUNCHER_PACKAGE_NAME
- ): RunningTaskInfo =
- RunningTaskInfo().apply {
- configuration.windowConfiguration.apply { windowingMode = deviceWindowingMode }
- topActivityInfo?.apply { packageName = runningTaskPackageName }
- }
+ private fun createTaskInfo(
+ deviceWindowingMode: Int = WINDOWING_MODE_UNDEFINED,
+ runningTaskPackageName: String = LAUNCHER_PACKAGE_NAME,
+ ): RunningTaskInfo =
+ RunningTaskInfo().apply {
+ configuration.windowConfiguration.apply { windowingMode = deviceWindowingMode }
+ topActivityInfo?.apply { packageName = runningTaskPackageName }
+ }
- private companion object {
- const val GMAIL_PACKAGE_NAME = "com.google.android.gm"
- const val LAUNCHER_PACKAGE_NAME = "com.google.android.apps.nexuslauncher"
- }
+ private companion object {
+ const val GMAIL_PACKAGE_NAME = "com.google.android.gm"
+ const val LAUNCHER_PACKAGE_NAME = "com.google.android.apps.nexuslauncher"
+ }
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/compatui/SystemModalsTransitionHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/compatui/SystemModalsTransitionHandlerTest.kt
index c33005e7cfcc..1569f9dc9b10 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/compatui/SystemModalsTransitionHandlerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/compatui/SystemModalsTransitionHandlerTest.kt
@@ -25,10 +25,10 @@ import android.view.WindowManager.TRANSIT_OPEN
import androidx.test.filters.SmallTest
import com.android.wm.shell.ShellTestCase
import com.android.wm.shell.common.ShellExecutor
+import com.android.wm.shell.desktopmode.DesktopRepository
import com.android.wm.shell.desktopmode.DesktopTestHelpers.createFullscreenTask
import com.android.wm.shell.desktopmode.DesktopTestHelpers.createFullscreenTaskBuilder
import com.android.wm.shell.desktopmode.DesktopTestHelpers.createSystemModalTask
-import com.android.wm.shell.desktopmode.DesktopRepository
import com.android.wm.shell.desktopmode.DesktopUserRepositories
import com.android.wm.shell.sysui.ShellInit
import com.android.wm.shell.transition.TransitionInfoBuilder
@@ -44,8 +44,8 @@ import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
/**
- * Tests for {@link SystemModalsTransitionHandler}
- * Usage: atest WMShellUnitTests:SystemModalsTransitionHandlerTest
+ * Tests for {@link SystemModalsTransitionHandler} Usage: atest
+ * WMShellUnitTests:SystemModalsTransitionHandlerTest
*/
@SmallTest
@RunWith(AndroidTestingRunner::class)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/education/AppHandleEducationControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/education/AppHandleEducationControllerTest.kt
index 9c00c0cee8b1..5475032f35a9 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/education/AppHandleEducationControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/education/AppHandleEducationControllerTest.kt
@@ -69,431 +69,448 @@ import org.mockito.kotlin.whenever
@RunWith(AndroidTestingRunner::class)
@OptIn(ExperimentalCoroutinesApi::class)
class AppHandleEducationControllerTest : ShellTestCase() {
- @JvmField
- @Rule
- val extendedMockitoRule =
- ExtendedMockitoRule.Builder(this)
- .mockStatic(DesktopModeStatus::class.java)
- .mockStatic(SystemProperties::class.java)
- .build()!!
- @JvmField @Rule val setFlagsRule = SetFlagsRule()
-
- private lateinit var educationController: AppHandleEducationController
- private lateinit var testableContext: TestableContext
- private val testScope = TestScope()
- private val testDataStoreFlow = MutableStateFlow(createWindowingEducationProto())
- private val testCaptionStateFlow = MutableStateFlow<CaptionState>(CaptionState.NoCaption)
- private val educationConfigCaptor =
- argumentCaptor<DesktopWindowingEducationTooltipController.TooltipEducationViewConfig>()
- @Mock private lateinit var mockEducationFilter: AppHandleEducationFilter
- @Mock private lateinit var mockDataStoreRepository: AppHandleEducationDatastoreRepository
- @Mock private lateinit var mockCaptionHandleRepository: WindowDecorCaptionHandleRepository
- @Mock private lateinit var mockTooltipController: DesktopWindowingEducationTooltipController
-
- @Before
- fun setUp() {
- MockitoAnnotations.initMocks(this)
- Dispatchers.setMain(StandardTestDispatcher(testScope.testScheduler))
- testableContext = TestableContext(mContext)
- whenever(mockDataStoreRepository.dataStoreFlow).thenReturn(testDataStoreFlow)
- whenever(mockCaptionHandleRepository.captionStateFlow).thenReturn(testCaptionStateFlow)
- whenever(DesktopModeStatus.canEnterDesktopMode(any())).thenReturn(true)
-
- educationController =
- AppHandleEducationController(
- testableContext,
- mockEducationFilter,
- mockDataStoreRepository,
- mockCaptionHandleRepository,
- mockTooltipController,
- testScope.backgroundScope,
- Dispatchers.Main)
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION)
- fun init_appHandleVisible_shouldCallShowEducationTooltip() =
- testScope.runTest {
- // App handle is visible. Should show education tooltip.
+ @JvmField
+ @Rule
+ val extendedMockitoRule =
+ ExtendedMockitoRule.Builder(this)
+ .mockStatic(DesktopModeStatus::class.java)
+ .mockStatic(SystemProperties::class.java)
+ .build()!!
+ @JvmField @Rule val setFlagsRule = SetFlagsRule()
+
+ private lateinit var educationController: AppHandleEducationController
+ private lateinit var testableContext: TestableContext
+ private val testScope = TestScope()
+ private val testDataStoreFlow = MutableStateFlow(createWindowingEducationProto())
+ private val testCaptionStateFlow = MutableStateFlow<CaptionState>(CaptionState.NoCaption)
+ private val educationConfigCaptor =
+ argumentCaptor<DesktopWindowingEducationTooltipController.TooltipEducationViewConfig>()
+ @Mock private lateinit var mockEducationFilter: AppHandleEducationFilter
+ @Mock private lateinit var mockDataStoreRepository: AppHandleEducationDatastoreRepository
+ @Mock private lateinit var mockCaptionHandleRepository: WindowDecorCaptionHandleRepository
+ @Mock private lateinit var mockTooltipController: DesktopWindowingEducationTooltipController
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ Dispatchers.setMain(StandardTestDispatcher(testScope.testScheduler))
+ testableContext = TestableContext(mContext)
+ whenever(mockDataStoreRepository.dataStoreFlow).thenReturn(testDataStoreFlow)
+ whenever(mockCaptionHandleRepository.captionStateFlow).thenReturn(testCaptionStateFlow)
+ whenever(DesktopModeStatus.canEnterDesktopMode(any())).thenReturn(true)
+
+ educationController =
+ AppHandleEducationController(
+ testableContext,
+ mockEducationFilter,
+ mockDataStoreRepository,
+ mockCaptionHandleRepository,
+ mockTooltipController,
+ testScope.backgroundScope,
+ Dispatchers.Main,
+ )
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION)
+ fun init_appHandleVisible_shouldCallShowEducationTooltip() =
+ testScope.runTest {
+ // App handle is visible. Should show education tooltip.
+ setShouldShowAppHandleEducation(true)
+
+ // Simulate app handle visible.
+ testCaptionStateFlow.value = createAppHandleState()
+ // Wait for first tooltip to showup.
+ waitForBufferDelay()
+
+ verify(mockTooltipController, times(1)).showEducationTooltip(any(), any())
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION)
+ fun init_flagDisabled_shouldNotCallShowEducationTooltip() =
+ testScope.runTest {
+ // App handle visible but education aconfig flag disabled, should not show education
+ // tooltip.
+ whenever(DesktopModeStatus.canEnterDesktopMode(any())).thenReturn(false)
+ setShouldShowAppHandleEducation(true)
+
+ // Simulate app handle visible.
+ testCaptionStateFlow.value = createAppHandleState()
+ // Wait for first tooltip to showup.
+ waitForBufferDelay()
+
+ verify(mockTooltipController, never()).showEducationTooltip(any(), any())
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION)
+ fun init_shouldShowAppHandleEducationReturnsFalse_shouldNotCallShowEducationTooltip() =
+ testScope.runTest {
+ // App handle is visible but [shouldShowAppHandleEducation] api returns false, should
+ // not
+ // show education tooltip.
+ setShouldShowAppHandleEducation(false)
+
+ // Simulate app handle visible.
+ testCaptionStateFlow.value = createAppHandleState()
+ // Wait for first tooltip to showup.
+ waitForBufferDelay()
+
+ verify(mockTooltipController, never()).showEducationTooltip(any(), any())
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION)
+ fun init_appHandleNotVisible_shouldNotCallShowEducationTooltip() =
+ testScope.runTest {
+ // App handle is not visible, should not show education tooltip.
+ setShouldShowAppHandleEducation(true)
+
+ // Simulate app handle is not visible.
+ testCaptionStateFlow.value = CaptionState.NoCaption
+ // Wait for first tooltip to showup.
+ waitForBufferDelay()
+
+ verify(mockTooltipController, never()).showEducationTooltip(any(), any())
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION)
+ fun init_appHandleHintViewedAlready_shouldNotCallShowEducationTooltip() =
+ testScope.runTest {
+ // App handle is visible but app handle hint has been viewed before,
+ // should not show education tooltip.
+ // Mark app handle hint viewed.
+ testDataStoreFlow.value =
+ createWindowingEducationProto(appHandleHintViewedTimestampMillis = 123L)
+ setShouldShowAppHandleEducation(true)
+
+ // Simulate app handle visible.
+ testCaptionStateFlow.value = createAppHandleState(isHandleMenuExpanded = false)
+ // Wait for first tooltip to showup.
+ waitForBufferDelay()
+
+ verify(mockTooltipController, never()).showEducationTooltip(any(), any())
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION)
+ fun overridePrerequisite_appHandleHintViewedAlready_shouldCallShowEducationTooltip() =
+ testScope.runTest {
+ // App handle is visible but app handle hint has been viewed before.
+ // But as we are overriding prerequisite conditions, we should show app
+ // handle tooltip.
+ // Mark app handle hint viewed.
+ testDataStoreFlow.value =
+ createWindowingEducationProto(appHandleHintViewedTimestampMillis = 123L)
+ val systemPropertiesKey =
+ "persist.desktop_windowing_app_handle_education_override_conditions"
+ whenever(SystemProperties.getBoolean(eq(systemPropertiesKey), anyBoolean()))
+ .thenReturn(true)
+ setShouldShowAppHandleEducation(true)
+
+ // Simulate app handle visible.
+ testCaptionStateFlow.value = createAppHandleState(isHandleMenuExpanded = false)
+ // Wait for first tooltip to showup.
+ waitForBufferDelay()
+
+ verify(mockTooltipController, times(1)).showEducationTooltip(any(), any())
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION)
+ fun init_appHandleExpanded_shouldMarkAppHandleHintUsed() =
+ testScope.runTest {
+ setShouldShowAppHandleEducation(false)
+
+ // Simulate app handle visible and expanded.
+ testCaptionStateFlow.value = createAppHandleState(isHandleMenuExpanded = true)
+ // Wait for some time before verifying
+ waitForBufferDelay()
+
+ verify(mockDataStoreRepository, times(1))
+ .updateAppHandleHintUsedTimestampMillis(eq(true))
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION)
+ fun init_showFirstTooltip_shouldMarkAppHandleHintViewed() =
+ testScope.runTest {
+ // App handle is visible. Should show education tooltip.
+ setShouldShowAppHandleEducation(true)
+
+ // Simulate app handle visible.
+ testCaptionStateFlow.value = createAppHandleState()
+ // Wait for first tooltip to showup.
+ waitForBufferDelay()
+
+ verify(mockDataStoreRepository, times(1))
+ .updateAppHandleHintViewedTimestampMillis(eq(true))
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION)
+ @Ignore("b/371527084: revisit testcase after refactoring original logic")
+ fun showWindowingImageButtonTooltip_appHandleExpanded_shouldCallShowEducationTooltipTwice() =
+ testScope.runTest {
+ // After first tooltip is dismissed, app handle is expanded. Should show second
+ // education
+ // tooltip.
+ showAndDismissFirstTooltip()
+
+ // Simulate app handle expanded.
+ testCaptionStateFlow.value = createAppHandleState(isHandleMenuExpanded = true)
+ // Wait for next tooltip to showup.
+ waitForBufferDelay()
+
+ // [showEducationTooltip] should be called twice, once for each tooltip.
+ verify(mockTooltipController, times(2)).showEducationTooltip(any(), any())
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION)
+ @Ignore("b/371527084: revisit testcase after refactoring original logic")
+ fun showWindowingImageButtonTooltip_appHandleExpandedAfterTimeout_shouldCallShowEducationTooltipOnce() =
+ testScope.runTest {
+ // After first tooltip is dismissed, app handle is expanded after timeout. Should not
+ // show
+ // second education tooltip.
+ showAndDismissFirstTooltip()
+
+ // Wait for timeout to occur, after this timeout we should not listen for further
+ // triggers
+ // anymore.
+ advanceTimeBy(APP_HANDLE_EDUCATION_TIMEOUT_BUFFER_MILLIS)
+ runCurrent()
+ // Simulate app handle expanded.
+ testCaptionStateFlow.value = createAppHandleState(isHandleMenuExpanded = true)
+ // Wait for next tooltip to showup.
+ waitForBufferDelay()
+
+ // [showEducationTooltip] should be called once, just for the first tooltip.
+ verify(mockTooltipController, times(1)).showEducationTooltip(any(), any())
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION)
+ @Ignore("b/371527084: revisit testcase after refactoring original logic")
+ fun showWindowingImageButtonTooltip_appHandleExpandedTwice_shouldCallShowEducationTooltipTwice() =
+ testScope.runTest {
+ // After first tooltip is dismissed, app handle is expanded twice. Should show second
+ // education tooltip only once.
+ showAndDismissFirstTooltip()
+
+ // Simulate app handle expanded.
+ testCaptionStateFlow.value = createAppHandleState(isHandleMenuExpanded = true)
+ // Wait for next tooltip to showup.
+ waitForBufferDelay()
+ // Simulate app handle being expanded twice.
+ testCaptionStateFlow.value = createAppHandleState(isHandleMenuExpanded = true)
+ waitForBufferDelay()
+
+ // [showEducationTooltip] should not be called thrice, even if app handle was expanded
+ // twice. Should be called twice, once for each tooltip.
+ verify(mockTooltipController, times(2)).showEducationTooltip(any(), any())
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION)
+ @Ignore("b/371527084: revisit testcase after refactoring original logic")
+ fun showWindowingImageButtonTooltip_appHandleNotExpanded_shouldCallShowEducationTooltipOnce() =
+ testScope.runTest {
+ // After first tooltip is dismissed, app handle is not expanded. Should not show second
+ // education tooltip.
+ showAndDismissFirstTooltip()
+
+ // Simulate app handle visible but not expanded.
+ testCaptionStateFlow.value = createAppHandleState(isHandleMenuExpanded = false)
+ // Wait for next tooltip to showup.
+ waitForBufferDelay()
+
+ // [showEducationTooltip] should be called once, just for the first tooltip.
+ verify(mockTooltipController, times(1)).showEducationTooltip(any(), any())
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION)
+ @Ignore("b/371527084: revisit testcase after refactoring original logic")
+ fun showExitWindowingButtonTooltip_appHeaderVisible_shouldCallShowEducationTooltipThrice() =
+ testScope.runTest {
+ // After first two tooltips are dismissed, app header is visible. Should show third
+ // education tooltip.
+ showAndDismissFirstTooltip()
+ showAndDismissSecondTooltip()
+
+ // Simulate app header visible.
+ testCaptionStateFlow.value = createAppHeaderState()
+ // Wait for next tooltip to showup.
+ waitForBufferDelay()
+
+ verify(mockTooltipController, times(3)).showEducationTooltip(any(), any())
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION)
+ @Ignore("b/371527084: revisit testcase after refactoring original logic")
+ fun showExitWindowingButtonTooltip_appHeaderVisibleAfterTimeout_shouldCallShowEducationTooltipTwice() =
+ testScope.runTest {
+ // After first two tooltips are dismissed, app header is visible after timeout. Should
+ // not
+ // show third education tooltip.
+ showAndDismissFirstTooltip()
+ showAndDismissSecondTooltip()
+
+ // Wait for timeout to occur, after this timeout we should not listen for further
+ // triggers
+ // anymore.
+ advanceTimeBy(APP_HANDLE_EDUCATION_TIMEOUT_BUFFER_MILLIS)
+ runCurrent()
+ // Simulate app header visible.
+ testCaptionStateFlow.value = createAppHeaderState()
+ // Wait for next tooltip to showup.
+ waitForBufferDelay()
+
+ verify(mockTooltipController, times(2)).showEducationTooltip(any(), any())
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION)
+ @Ignore("b/371527084: revisit testcase after refactoring original logic")
+ fun showExitWindowingButtonTooltip_appHeaderVisibleTwice_shouldCallShowEducationTooltipThrice() =
+ testScope.runTest {
+ // After first two tooltips are dismissed, app header is visible twice. Should show
+ // third
+ // education tooltip only once.
+ showAndDismissFirstTooltip()
+ showAndDismissSecondTooltip()
+
+ // Simulate app header visible.
+ testCaptionStateFlow.value = createAppHeaderState()
+ // Wait for next tooltip to showup.
+ waitForBufferDelay()
+ testCaptionStateFlow.value = createAppHeaderState()
+ // Wait for next tooltip to showup.
+ waitForBufferDelay()
+
+ verify(mockTooltipController, times(3)).showEducationTooltip(any(), any())
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION)
+ @Ignore("b/371527084: revisit testcase after refactoring original logic")
+ fun showExitWindowingButtonTooltip_appHeaderExpanded_shouldCallShowEducationTooltipTwice() =
+ testScope.runTest {
+ // After first two tooltips are dismissed, app header is visible but expanded. Should
+ // not
+ // show third education tooltip.
+ showAndDismissFirstTooltip()
+ showAndDismissSecondTooltip()
+
+ // Simulate app header visible.
+ testCaptionStateFlow.value = createAppHeaderState(isHeaderMenuExpanded = true)
+ // Wait for next tooltip to showup.
+ waitForBufferDelay()
+
+ verify(mockTooltipController, times(2)).showEducationTooltip(any(), any())
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION)
+ fun setAppHandleEducationTooltipCallbacks_onAppHandleTooltipClicked_callbackInvoked() =
+ testScope.runTest {
+ // App handle is visible. Should show education tooltip.
+ setShouldShowAppHandleEducation(true)
+ val mockOpenHandleMenuCallback: (Int) -> Unit = mock()
+ val mockToDesktopModeCallback: (Int, DesktopModeTransitionSource) -> Unit = mock()
+ educationController.setAppHandleEducationTooltipCallbacks(
+ mockOpenHandleMenuCallback,
+ mockToDesktopModeCallback,
+ )
+ // Simulate app handle visible.
+ testCaptionStateFlow.value = createAppHandleState()
+ // Wait for first tooltip to showup.
+ waitForBufferDelay()
+
+ verify(mockTooltipController, atLeastOnce())
+ .showEducationTooltip(educationConfigCaptor.capture(), any())
+ educationConfigCaptor.lastValue.onEducationClickAction.invoke()
+
+ verify(mockOpenHandleMenuCallback, times(1)).invoke(any())
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION)
+ @Ignore("b/371527084: revisit testcase after refactoring original logic")
+ fun setAppHandleEducationTooltipCallbacks_onWindowingImageButtonTooltipClicked_callbackInvoked() =
+ testScope.runTest {
+ // After first tooltip is dismissed, app handle is expanded. Should show second
+ // education
+ // tooltip.
+ showAndDismissFirstTooltip()
+ val mockOpenHandleMenuCallback: (Int) -> Unit = mock()
+ val mockToDesktopModeCallback: (Int, DesktopModeTransitionSource) -> Unit = mock()
+ educationController.setAppHandleEducationTooltipCallbacks(
+ mockOpenHandleMenuCallback,
+ mockToDesktopModeCallback,
+ )
+ // Simulate app handle expanded.
+ testCaptionStateFlow.value = createAppHandleState(isHandleMenuExpanded = true)
+ // Wait for next tooltip to showup.
+ waitForBufferDelay()
+
+ verify(mockTooltipController, atLeastOnce())
+ .showEducationTooltip(educationConfigCaptor.capture(), any())
+ educationConfigCaptor.lastValue.onEducationClickAction.invoke()
+
+ verify(mockToDesktopModeCallback, times(1)).invoke(any(), any())
+ }
+
+ private suspend fun TestScope.showAndDismissFirstTooltip() {
setShouldShowAppHandleEducation(true)
-
- // Simulate app handle visible.
- testCaptionStateFlow.value = createAppHandleState()
- // Wait for first tooltip to showup.
- waitForBufferDelay()
-
- verify(mockTooltipController, times(1)).showEducationTooltip(any(), any())
- }
-
- @Test
- @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION)
- fun init_flagDisabled_shouldNotCallShowEducationTooltip() =
- testScope.runTest {
- // App handle visible but education aconfig flag disabled, should not show education
- // tooltip.
- whenever(DesktopModeStatus.canEnterDesktopMode(any())).thenReturn(false)
- setShouldShowAppHandleEducation(true)
-
- // Simulate app handle visible.
- testCaptionStateFlow.value = createAppHandleState()
- // Wait for first tooltip to showup.
- waitForBufferDelay()
-
- verify(mockTooltipController, never()).showEducationTooltip(any(), any())
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION)
- fun init_shouldShowAppHandleEducationReturnsFalse_shouldNotCallShowEducationTooltip() =
- testScope.runTest {
- // App handle is visible but [shouldShowAppHandleEducation] api returns false, should not
- // show education tooltip.
- setShouldShowAppHandleEducation(false)
-
- // Simulate app handle visible.
- testCaptionStateFlow.value = createAppHandleState()
- // Wait for first tooltip to showup.
- waitForBufferDelay()
-
- verify(mockTooltipController, never()).showEducationTooltip(any(), any())
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION)
- fun init_appHandleNotVisible_shouldNotCallShowEducationTooltip() =
- testScope.runTest {
- // App handle is not visible, should not show education tooltip.
- setShouldShowAppHandleEducation(true)
-
- // Simulate app handle is not visible.
- testCaptionStateFlow.value = CaptionState.NoCaption
- // Wait for first tooltip to showup.
- waitForBufferDelay()
-
- verify(mockTooltipController, never()).showEducationTooltip(any(), any())
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION)
- fun init_appHandleHintViewedAlready_shouldNotCallShowEducationTooltip() =
- testScope.runTest {
- // App handle is visible but app handle hint has been viewed before,
- // should not show education tooltip.
- // Mark app handle hint viewed.
- testDataStoreFlow.value =
- createWindowingEducationProto(appHandleHintViewedTimestampMillis = 123L)
- setShouldShowAppHandleEducation(true)
-
- // Simulate app handle visible.
- testCaptionStateFlow.value = createAppHandleState(isHandleMenuExpanded = false)
- // Wait for first tooltip to showup.
- waitForBufferDelay()
-
- verify(mockTooltipController, never()).showEducationTooltip(any(), any())
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION)
- fun overridePrerequisite_appHandleHintViewedAlready_shouldCallShowEducationTooltip() =
- testScope.runTest {
- // App handle is visible but app handle hint has been viewed before.
- // But as we are overriding prerequisite conditions, we should show app
- // handle tooltip.
- // Mark app handle hint viewed.
- testDataStoreFlow.value =
- createWindowingEducationProto(appHandleHintViewedTimestampMillis = 123L)
- val systemPropertiesKey =
- "persist.desktop_windowing_app_handle_education_override_conditions"
- whenever(SystemProperties.getBoolean(eq(systemPropertiesKey), anyBoolean()))
- .thenReturn(true)
- setShouldShowAppHandleEducation(true)
-
// Simulate app handle visible.
testCaptionStateFlow.value = createAppHandleState(isHandleMenuExpanded = false)
// Wait for first tooltip to showup.
waitForBufferDelay()
-
- verify(mockTooltipController, times(1)).showEducationTooltip(any(), any())
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION)
- fun init_appHandleExpanded_shouldMarkAppHandleHintUsed() =
- testScope.runTest {
+ // [shouldShowAppHandleEducation] should return false as education has been viewed
+ // before.
setShouldShowAppHandleEducation(false)
+ // Dismiss previous tooltip, after this we should listen for next tooltip's trigger.
+ captureAndInvokeOnDismissAction()
+ }
- // Simulate app handle visible and expanded.
- testCaptionStateFlow.value = createAppHandleState(isHandleMenuExpanded = true)
- // Wait for some time before verifying
- waitForBufferDelay()
-
- verify(mockDataStoreRepository, times(1)).updateAppHandleHintUsedTimestampMillis(eq(true))
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION)
- fun init_showFirstTooltip_shouldMarkAppHandleHintViewed() =
- testScope.runTest {
- // App handle is visible. Should show education tooltip.
- setShouldShowAppHandleEducation(true)
-
- // Simulate app handle visible.
- testCaptionStateFlow.value = createAppHandleState()
- // Wait for first tooltip to showup.
- waitForBufferDelay()
-
- verify(mockDataStoreRepository, times(1)).updateAppHandleHintViewedTimestampMillis(eq(true))
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION)
- @Ignore("b/371527084: revisit testcase after refactoring original logic")
- fun showWindowingImageButtonTooltip_appHandleExpanded_shouldCallShowEducationTooltipTwice() =
- testScope.runTest {
- // After first tooltip is dismissed, app handle is expanded. Should show second education
- // tooltip.
- showAndDismissFirstTooltip()
-
- // Simulate app handle expanded.
- testCaptionStateFlow.value = createAppHandleState(isHandleMenuExpanded = true)
- // Wait for next tooltip to showup.
- waitForBufferDelay()
-
- // [showEducationTooltip] should be called twice, once for each tooltip.
- verify(mockTooltipController, times(2)).showEducationTooltip(any(), any())
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION)
- @Ignore("b/371527084: revisit testcase after refactoring original logic")
- fun showWindowingImageButtonTooltip_appHandleExpandedAfterTimeout_shouldCallShowEducationTooltipOnce() =
- testScope.runTest {
- // After first tooltip is dismissed, app handle is expanded after timeout. Should not show
- // second education tooltip.
- showAndDismissFirstTooltip()
-
- // Wait for timeout to occur, after this timeout we should not listen for further triggers
- // anymore.
- advanceTimeBy(APP_HANDLE_EDUCATION_TIMEOUT_BUFFER_MILLIS)
- runCurrent()
- // Simulate app handle expanded.
- testCaptionStateFlow.value = createAppHandleState(isHandleMenuExpanded = true)
- // Wait for next tooltip to showup.
- waitForBufferDelay()
-
- // [showEducationTooltip] should be called once, just for the first tooltip.
- verify(mockTooltipController, times(1)).showEducationTooltip(any(), any())
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION)
- @Ignore("b/371527084: revisit testcase after refactoring original logic")
- fun showWindowingImageButtonTooltip_appHandleExpandedTwice_shouldCallShowEducationTooltipTwice() =
- testScope.runTest {
- // After first tooltip is dismissed, app handle is expanded twice. Should show second
- // education tooltip only once.
- showAndDismissFirstTooltip()
-
- // Simulate app handle expanded.
- testCaptionStateFlow.value = createAppHandleState(isHandleMenuExpanded = true)
- // Wait for next tooltip to showup.
- waitForBufferDelay()
- // Simulate app handle being expanded twice.
- testCaptionStateFlow.value = createAppHandleState(isHandleMenuExpanded = true)
- waitForBufferDelay()
-
- // [showEducationTooltip] should not be called thrice, even if app handle was expanded
- // twice. Should be called twice, once for each tooltip.
- verify(mockTooltipController, times(2)).showEducationTooltip(any(), any())
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION)
- @Ignore("b/371527084: revisit testcase after refactoring original logic")
- fun showWindowingImageButtonTooltip_appHandleNotExpanded_shouldCallShowEducationTooltipOnce() =
- testScope.runTest {
- // After first tooltip is dismissed, app handle is not expanded. Should not show second
- // education tooltip.
- showAndDismissFirstTooltip()
-
- // Simulate app handle visible but not expanded.
- testCaptionStateFlow.value = createAppHandleState(isHandleMenuExpanded = false)
- // Wait for next tooltip to showup.
- waitForBufferDelay()
-
- // [showEducationTooltip] should be called once, just for the first tooltip.
- verify(mockTooltipController, times(1)).showEducationTooltip(any(), any())
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION)
- @Ignore("b/371527084: revisit testcase after refactoring original logic")
- fun showExitWindowingButtonTooltip_appHeaderVisible_shouldCallShowEducationTooltipThrice() =
- testScope.runTest {
- // After first two tooltips are dismissed, app header is visible. Should show third
- // education tooltip.
- showAndDismissFirstTooltip()
- showAndDismissSecondTooltip()
-
- // Simulate app header visible.
- testCaptionStateFlow.value = createAppHeaderState()
- // Wait for next tooltip to showup.
- waitForBufferDelay()
-
- verify(mockTooltipController, times(3)).showEducationTooltip(any(), any())
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION)
- @Ignore("b/371527084: revisit testcase after refactoring original logic")
- fun showExitWindowingButtonTooltip_appHeaderVisibleAfterTimeout_shouldCallShowEducationTooltipTwice() =
- testScope.runTest {
- // After first two tooltips are dismissed, app header is visible after timeout. Should not
- // show third education tooltip.
- showAndDismissFirstTooltip()
- showAndDismissSecondTooltip()
-
- // Wait for timeout to occur, after this timeout we should not listen for further triggers
- // anymore.
- advanceTimeBy(APP_HANDLE_EDUCATION_TIMEOUT_BUFFER_MILLIS)
- runCurrent()
- // Simulate app header visible.
- testCaptionStateFlow.value = createAppHeaderState()
- // Wait for next tooltip to showup.
- waitForBufferDelay()
-
- verify(mockTooltipController, times(2)).showEducationTooltip(any(), any())
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION)
- @Ignore("b/371527084: revisit testcase after refactoring original logic")
- fun showExitWindowingButtonTooltip_appHeaderVisibleTwice_shouldCallShowEducationTooltipThrice() =
- testScope.runTest {
- // After first two tooltips are dismissed, app header is visible twice. Should show third
- // education tooltip only once.
- showAndDismissFirstTooltip()
- showAndDismissSecondTooltip()
-
- // Simulate app header visible.
- testCaptionStateFlow.value = createAppHeaderState()
- // Wait for next tooltip to showup.
- waitForBufferDelay()
- testCaptionStateFlow.value = createAppHeaderState()
- // Wait for next tooltip to showup.
- waitForBufferDelay()
-
- verify(mockTooltipController, times(3)).showEducationTooltip(any(), any())
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION)
- @Ignore("b/371527084: revisit testcase after refactoring original logic")
- fun showExitWindowingButtonTooltip_appHeaderExpanded_shouldCallShowEducationTooltipTwice() =
- testScope.runTest {
- // After first two tooltips are dismissed, app header is visible but expanded. Should not
- // show third education tooltip.
- showAndDismissFirstTooltip()
- showAndDismissSecondTooltip()
-
- // Simulate app header visible.
- testCaptionStateFlow.value = createAppHeaderState(isHeaderMenuExpanded = true)
- // Wait for next tooltip to showup.
- waitForBufferDelay()
-
- verify(mockTooltipController, times(2)).showEducationTooltip(any(), any())
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION)
- fun setAppHandleEducationTooltipCallbacks_onAppHandleTooltipClicked_callbackInvoked() =
- testScope.runTest {
- // App handle is visible. Should show education tooltip.
- setShouldShowAppHandleEducation(true)
- val mockOpenHandleMenuCallback: (Int) -> Unit = mock()
- val mockToDesktopModeCallback: (Int, DesktopModeTransitionSource) -> Unit = mock()
- educationController.setAppHandleEducationTooltipCallbacks(
- mockOpenHandleMenuCallback, mockToDesktopModeCallback)
- // Simulate app handle visible.
- testCaptionStateFlow.value = createAppHandleState()
- // Wait for first tooltip to showup.
- waitForBufferDelay()
-
- verify(mockTooltipController, atLeastOnce())
- .showEducationTooltip(educationConfigCaptor.capture(), any())
- educationConfigCaptor.lastValue.onEducationClickAction.invoke()
-
- verify(mockOpenHandleMenuCallback, times(1)).invoke(any())
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION)
- @Ignore("b/371527084: revisit testcase after refactoring original logic")
- fun setAppHandleEducationTooltipCallbacks_onWindowingImageButtonTooltipClicked_callbackInvoked() =
- testScope.runTest {
- // After first tooltip is dismissed, app handle is expanded. Should show second education
- // tooltip.
- showAndDismissFirstTooltip()
- val mockOpenHandleMenuCallback: (Int) -> Unit = mock()
- val mockToDesktopModeCallback: (Int, DesktopModeTransitionSource) -> Unit = mock()
- educationController.setAppHandleEducationTooltipCallbacks(
- mockOpenHandleMenuCallback, mockToDesktopModeCallback)
+ private fun TestScope.showAndDismissSecondTooltip() {
// Simulate app handle expanded.
testCaptionStateFlow.value = createAppHandleState(isHandleMenuExpanded = true)
// Wait for next tooltip to showup.
waitForBufferDelay()
+ // Dismiss previous tooltip, after this we should listen for next tooltip's trigger.
+ captureAndInvokeOnDismissAction()
+ }
+ private fun captureAndInvokeOnDismissAction() {
verify(mockTooltipController, atLeastOnce())
.showEducationTooltip(educationConfigCaptor.capture(), any())
- educationConfigCaptor.lastValue.onEducationClickAction.invoke()
-
- verify(mockToDesktopModeCallback, times(1)).invoke(any(), any())
- }
-
- private suspend fun TestScope.showAndDismissFirstTooltip() {
- setShouldShowAppHandleEducation(true)
- // Simulate app handle visible.
- testCaptionStateFlow.value = createAppHandleState(isHandleMenuExpanded = false)
- // Wait for first tooltip to showup.
- waitForBufferDelay()
- // [shouldShowAppHandleEducation] should return false as education has been viewed
- // before.
- setShouldShowAppHandleEducation(false)
- // Dismiss previous tooltip, after this we should listen for next tooltip's trigger.
- captureAndInvokeOnDismissAction()
- }
-
- private fun TestScope.showAndDismissSecondTooltip() {
- // Simulate app handle expanded.
- testCaptionStateFlow.value = createAppHandleState(isHandleMenuExpanded = true)
- // Wait for next tooltip to showup.
- waitForBufferDelay()
- // Dismiss previous tooltip, after this we should listen for next tooltip's trigger.
- captureAndInvokeOnDismissAction()
- }
-
- private fun captureAndInvokeOnDismissAction() {
- verify(mockTooltipController, atLeastOnce())
- .showEducationTooltip(educationConfigCaptor.capture(), any())
- educationConfigCaptor.lastValue.onDismissAction.invoke()
- }
-
- private suspend fun setShouldShowAppHandleEducation(shouldShowAppHandleEducation: Boolean) =
- whenever(mockEducationFilter.shouldShowAppHandleEducation(any()))
- .thenReturn(shouldShowAppHandleEducation)
-
- /**
- * Class under test waits for some time before showing education, simulate advance time before
- * verifying or moving forward
- */
- private fun TestScope.waitForBufferDelay() {
- advanceTimeBy(APP_HANDLE_EDUCATION_DELAY_BUFFER_MILLIS)
- runCurrent()
- }
-
- private companion object {
- val APP_HANDLE_EDUCATION_DELAY_BUFFER_MILLIS: Long = APP_HANDLE_EDUCATION_DELAY_MILLIS + 1000L
- val APP_HANDLE_EDUCATION_TIMEOUT_BUFFER_MILLIS: Long =
- APP_HANDLE_EDUCATION_TIMEOUT_MILLIS + 1000L
- }
+ educationConfigCaptor.lastValue.onDismissAction.invoke()
+ }
+
+ private suspend fun setShouldShowAppHandleEducation(shouldShowAppHandleEducation: Boolean) =
+ whenever(mockEducationFilter.shouldShowAppHandleEducation(any()))
+ .thenReturn(shouldShowAppHandleEducation)
+
+ /**
+ * Class under test waits for some time before showing education, simulate advance time before
+ * verifying or moving forward
+ */
+ private fun TestScope.waitForBufferDelay() {
+ advanceTimeBy(APP_HANDLE_EDUCATION_DELAY_BUFFER_MILLIS)
+ runCurrent()
+ }
+
+ private companion object {
+ val APP_HANDLE_EDUCATION_DELAY_BUFFER_MILLIS: Long =
+ APP_HANDLE_EDUCATION_DELAY_MILLIS + 1000L
+ val APP_HANDLE_EDUCATION_TIMEOUT_BUFFER_MILLIS: Long =
+ APP_HANDLE_EDUCATION_TIMEOUT_MILLIS + 1000L
+ }
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/education/AppHandleEducationDatastoreRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/education/AppHandleEducationDatastoreRepositoryTest.kt
index 963890d1caa4..4db883d13551 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/education/AppHandleEducationDatastoreRepositoryTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/education/AppHandleEducationDatastoreRepositoryTest.kt
@@ -49,85 +49,89 @@ import org.junit.runner.RunWith
@RunWith(AndroidTestingRunner::class)
@ExperimentalCoroutinesApi
class AppHandleEducationDatastoreRepositoryTest {
- private val testContext: Context = InstrumentationRegistry.getInstrumentation().targetContext
- private lateinit var testDatastore: DataStore<WindowingEducationProto>
- private lateinit var datastoreRepository: AppHandleEducationDatastoreRepository
- private lateinit var datastoreScope: CoroutineScope
-
- @Before
- fun setUp() {
- Dispatchers.setMain(StandardTestDispatcher())
- datastoreScope = CoroutineScope(Dispatchers.Unconfined + SupervisorJob())
- testDatastore =
- DataStoreFactory.create(
- serializer =
- AppHandleEducationDatastoreRepository.Companion.WindowingEducationProtoSerializer,
- scope = datastoreScope) {
- testContext.dataStoreFile(APP_HANDLE_EDUCATION_DATASTORE_TEST_FILE)
+ private val testContext: Context = InstrumentationRegistry.getInstrumentation().targetContext
+ private lateinit var testDatastore: DataStore<WindowingEducationProto>
+ private lateinit var datastoreRepository: AppHandleEducationDatastoreRepository
+ private lateinit var datastoreScope: CoroutineScope
+
+ @Before
+ fun setUp() {
+ Dispatchers.setMain(StandardTestDispatcher())
+ datastoreScope = CoroutineScope(Dispatchers.Unconfined + SupervisorJob())
+ testDatastore =
+ DataStoreFactory.create(
+ serializer =
+ AppHandleEducationDatastoreRepository.Companion
+ .WindowingEducationProtoSerializer,
+ scope = datastoreScope,
+ ) {
+ testContext.dataStoreFile(APP_HANDLE_EDUCATION_DATASTORE_TEST_FILE)
}
- datastoreRepository = AppHandleEducationDatastoreRepository(testDatastore)
- }
-
- @After
- fun tearDown() {
- File(ApplicationProvider.getApplicationContext<Context>().filesDir, "datastore")
- .deleteRecursively()
-
- datastoreScope.cancel()
- }
-
- @Test
- fun getWindowingEducationProto_returnsCorrectProto() =
- runTest(StandardTestDispatcher()) {
- val windowingEducationProto =
- createWindowingEducationProto(
- appHandleHintViewedTimestampMillis = 123L,
- appHandleHintUsedTimestampMillis = 124L,
- appUsageStats = mapOf(GMAIL_PACKAGE_NAME to 2),
- appUsageStatsLastUpdateTimestampMillis = 125L)
- testDatastore.updateData { windowingEducationProto }
-
- val resultProto = datastoreRepository.windowingEducationProto()
-
- assertThat(resultProto).isEqualTo(windowingEducationProto)
- }
-
- @Test
- fun updateAppUsageStats_updatesDatastoreProto() =
- runTest(StandardTestDispatcher()) {
- val appUsageStats = mapOf(GMAIL_PACKAGE_NAME to 3)
- val appUsageStatsLastUpdateTimestamp = Duration.ofMillis(123L)
- val windowingEducationProto =
- createWindowingEducationProto(
- appUsageStats = appUsageStats,
- appUsageStatsLastUpdateTimestampMillis =
- appUsageStatsLastUpdateTimestamp.toMillis())
-
- datastoreRepository.updateAppUsageStats(appUsageStats, appUsageStatsLastUpdateTimestamp)
-
- val result = testDatastore.data.first()
- assertThat(result).isEqualTo(windowingEducationProto)
- }
-
- @Test
- fun updateAppHandleHintViewedTimestampMillis_updatesDatastoreProto() =
- runTest(StandardTestDispatcher()) {
- datastoreRepository.updateAppHandleHintViewedTimestampMillis(true)
-
- val result = testDatastore.data.first().hasAppHandleHintViewedTimestampMillis()
- assertThat(result).isEqualTo(true)
- }
-
- @Test
- fun updateAppHandleHintUsedTimestampMillis_updatesDatastoreProto() =
- runTest(StandardTestDispatcher()) {
- datastoreRepository.updateAppHandleHintUsedTimestampMillis(true)
-
- val result = testDatastore.data.first().hasAppHandleHintUsedTimestampMillis()
- assertThat(result).isEqualTo(true)
- }
-
- companion object {
- private const val APP_HANDLE_EDUCATION_DATASTORE_TEST_FILE = "app_handle_education_test.pb"
- }
+ datastoreRepository = AppHandleEducationDatastoreRepository(testDatastore)
+ }
+
+ @After
+ fun tearDown() {
+ File(ApplicationProvider.getApplicationContext<Context>().filesDir, "datastore")
+ .deleteRecursively()
+
+ datastoreScope.cancel()
+ }
+
+ @Test
+ fun getWindowingEducationProto_returnsCorrectProto() =
+ runTest(StandardTestDispatcher()) {
+ val windowingEducationProto =
+ createWindowingEducationProto(
+ appHandleHintViewedTimestampMillis = 123L,
+ appHandleHintUsedTimestampMillis = 124L,
+ appUsageStats = mapOf(GMAIL_PACKAGE_NAME to 2),
+ appUsageStatsLastUpdateTimestampMillis = 125L,
+ )
+ testDatastore.updateData { windowingEducationProto }
+
+ val resultProto = datastoreRepository.windowingEducationProto()
+
+ assertThat(resultProto).isEqualTo(windowingEducationProto)
+ }
+
+ @Test
+ fun updateAppUsageStats_updatesDatastoreProto() =
+ runTest(StandardTestDispatcher()) {
+ val appUsageStats = mapOf(GMAIL_PACKAGE_NAME to 3)
+ val appUsageStatsLastUpdateTimestamp = Duration.ofMillis(123L)
+ val windowingEducationProto =
+ createWindowingEducationProto(
+ appUsageStats = appUsageStats,
+ appUsageStatsLastUpdateTimestampMillis =
+ appUsageStatsLastUpdateTimestamp.toMillis(),
+ )
+
+ datastoreRepository.updateAppUsageStats(appUsageStats, appUsageStatsLastUpdateTimestamp)
+
+ val result = testDatastore.data.first()
+ assertThat(result).isEqualTo(windowingEducationProto)
+ }
+
+ @Test
+ fun updateAppHandleHintViewedTimestampMillis_updatesDatastoreProto() =
+ runTest(StandardTestDispatcher()) {
+ datastoreRepository.updateAppHandleHintViewedTimestampMillis(true)
+
+ val result = testDatastore.data.first().hasAppHandleHintViewedTimestampMillis()
+ assertThat(result).isEqualTo(true)
+ }
+
+ @Test
+ fun updateAppHandleHintUsedTimestampMillis_updatesDatastoreProto() =
+ runTest(StandardTestDispatcher()) {
+ datastoreRepository.updateAppHandleHintUsedTimestampMillis(true)
+
+ val result = testDatastore.data.first().hasAppHandleHintUsedTimestampMillis()
+ assertThat(result).isEqualTo(true)
+ }
+
+ companion object {
+ private const val APP_HANDLE_EDUCATION_DATASTORE_TEST_FILE = "app_handle_education_test.pb"
+ }
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/education/AppHandleEducationFilterTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/education/AppHandleEducationFilterTest.kt
index e5edd69155b5..2fc36efb1a41 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/education/AppHandleEducationFilterTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/education/AppHandleEducationFilterTest.kt
@@ -53,189 +53,221 @@ import org.mockito.kotlin.whenever
@RunWith(AndroidTestingRunner::class)
@kotlinx.coroutines.ExperimentalCoroutinesApi
class AppHandleEducationFilterTest : ShellTestCase() {
- @JvmField
- @Rule
- val extendedMockitoRule =
- ExtendedMockitoRule.Builder(this).mockStatic(SystemProperties::class.java).build()!!
- @Mock private lateinit var datastoreRepository: AppHandleEducationDatastoreRepository
- @Mock private lateinit var mockUsageStatsManager: UsageStatsManager
- private lateinit var educationFilter: AppHandleEducationFilter
- private lateinit var testableResources: TestableResources
- private lateinit var testableContext: TestableContext
-
- @Before
- fun setup() {
- MockitoAnnotations.initMocks(this)
- testableContext = TestableContext(mContext)
- testableResources =
- testableContext.orCreateTestableResources.apply {
- addOverride(
- R.array.desktop_windowing_app_handle_education_allowlist_apps,
- arrayOf(GMAIL_PACKAGE_NAME))
- addOverride(R.integer.desktop_windowing_education_required_time_since_setup_seconds, 0)
- addOverride(R.integer.desktop_windowing_education_min_app_launch_count, 3)
- addOverride(
- R.integer.desktop_windowing_education_app_usage_cache_interval_seconds, MAX_VALUE)
- addOverride(R.integer.desktop_windowing_education_app_launch_interval_seconds, 100)
- }
- testableContext.addMockSystemService(Context.USAGE_STATS_SERVICE, mockUsageStatsManager)
- educationFilter = AppHandleEducationFilter(testableContext, datastoreRepository)
- }
-
- @Test
- fun shouldShowAppHandleEducation_isTriggerValid_returnsTrue() = runTest {
- // setup() makes sure that all of the conditions satisfy and #shouldShowAppHandleEducation
- // should return true
- val windowingEducationProto =
- createWindowingEducationProto(
- appUsageStats = mapOf(GMAIL_PACKAGE_NAME to 4),
- appUsageStatsLastUpdateTimestampMillis = Long.MAX_VALUE)
- `when`(datastoreRepository.windowingEducationProto()).thenReturn(windowingEducationProto)
-
- val result = educationFilter.shouldShowAppHandleEducation(createAppHandleState())
-
- assertThat(result).isTrue()
- }
-
- @Test
- fun shouldShowAppHandleEducation_focusAppNotInAllowlist_returnsFalse() = runTest {
- // Pass Youtube as current focus app, it is not in allowlist hence #shouldShowAppHandleEducation
- // should return false
- testableResources.addOverride(
- R.array.desktop_windowing_app_handle_education_allowlist_apps, arrayOf(GMAIL_PACKAGE_NAME))
- val windowingEducationProto =
- createWindowingEducationProto(
- appUsageStats = mapOf(YOUTUBE_PACKAGE_NAME to 4),
- appUsageStatsLastUpdateTimestampMillis = Long.MAX_VALUE)
- val captionState =
- createAppHandleState(createTaskInfo(runningTaskPackageName = YOUTUBE_PACKAGE_NAME))
- `when`(datastoreRepository.windowingEducationProto()).thenReturn(windowingEducationProto)
-
- val result = educationFilter.shouldShowAppHandleEducation(captionState)
-
- assertThat(result).isFalse()
- }
-
- @Test
- fun shouldShowAppHandleEducation_timeSinceSetupIsNotSufficient_returnsFalse() = runTest {
- // Time required to have passed setup is > 100 years, hence #shouldShowAppHandleEducation should
- // return false
- testableResources.addOverride(
- R.integer.desktop_windowing_education_required_time_since_setup_seconds, MAX_VALUE)
- val windowingEducationProto =
- createWindowingEducationProto(
- appUsageStats = mapOf(GMAIL_PACKAGE_NAME to 4),
- appUsageStatsLastUpdateTimestampMillis = Long.MAX_VALUE)
- `when`(datastoreRepository.windowingEducationProto()).thenReturn(windowingEducationProto)
-
- val result = educationFilter.shouldShowAppHandleEducation(createAppHandleState())
-
- assertThat(result).isFalse()
- }
-
- @Test
- fun shouldShowAppHandleEducation_appHandleHintViewedBefore_returnsFalse() = runTest {
- // App handle hint has been viewed before, hence #shouldShowAppHandleEducation should return false
- val windowingEducationProto =
- createWindowingEducationProto(
- appUsageStats = mapOf(GMAIL_PACKAGE_NAME to 4),
- appHandleHintViewedTimestampMillis = 123L,
- appUsageStatsLastUpdateTimestampMillis = Long.MAX_VALUE)
- `when`(datastoreRepository.windowingEducationProto()).thenReturn(windowingEducationProto)
-
- val result = educationFilter.shouldShowAppHandleEducation(createAppHandleState())
-
- assertThat(result).isFalse()
- }
-
- @Test
- fun shouldShowAppHandleEducation_appHandleHintUsedBefore_returnsFalse() = runTest {
- // App handle hint has been used before, hence #shouldShowAppHandleEducation should return false
- val windowingEducationProto =
- createWindowingEducationProto(
- appUsageStats = mapOf(GMAIL_PACKAGE_NAME to 4),
- appHandleHintUsedTimestampMillis = 123L,
- appUsageStatsLastUpdateTimestampMillis = Long.MAX_VALUE)
- `when`(datastoreRepository.windowingEducationProto()).thenReturn(windowingEducationProto)
-
- val result = educationFilter.shouldShowAppHandleEducation(createAppHandleState())
-
- assertThat(result).isFalse()
- }
-
- @Test
- fun shouldShowAppHandleEducation_doesNotHaveMinAppUsage_returnsFalse() = runTest {
- // Simulate that gmail app has been launched twice before, minimum app launch count is 3, hence
- // #shouldShowAppHandleEducation should return false
- testableResources.addOverride(R.integer.desktop_windowing_education_min_app_launch_count, 3)
- val windowingEducationProto =
- createWindowingEducationProto(
- appUsageStats = mapOf(GMAIL_PACKAGE_NAME to 2),
- appUsageStatsLastUpdateTimestampMillis = Long.MAX_VALUE)
- `when`(datastoreRepository.windowingEducationProto()).thenReturn(windowingEducationProto)
-
- val result = educationFilter.shouldShowAppHandleEducation(createAppHandleState())
-
- assertThat(result).isFalse()
- }
-
- @Test
- fun shouldShowAppHandleEducation_appUsageStatsStale_queryAppUsageStats() = runTest {
- // UsageStats caching interval is set to 0ms, that means caching should happen very frequently
- testableResources.addOverride(
- R.integer.desktop_windowing_education_app_usage_cache_interval_seconds, 0)
- // The DataStore currently holds a proto object where Gmail's app launch count is recorded as 4.
- // This value exceeds the minimum required count of 3.
- testableResources.addOverride(R.integer.desktop_windowing_education_min_app_launch_count, 3)
- val windowingEducationProto =
- createWindowingEducationProto(
- appUsageStats = mapOf(GMAIL_PACKAGE_NAME to 4),
- appUsageStatsLastUpdateTimestampMillis = 0)
- // The mocked UsageStatsManager is configured to return a launch count of 2 for Gmail.
- // This value is below the minimum required count of 3.
- `when`(mockUsageStatsManager.queryAndAggregateUsageStats(anyLong(), anyLong()))
- .thenReturn(mapOf(GMAIL_PACKAGE_NAME to UsageStats().apply { mAppLaunchCount = 2 }))
- `when`(datastoreRepository.windowingEducationProto()).thenReturn(windowingEducationProto)
-
- val result = educationFilter.shouldShowAppHandleEducation(createAppHandleState())
-
- // Result should be false as queried usage stats should be considered to determine the result
- // instead of cached stats
- assertThat(result).isFalse()
- }
-
- @Test
- fun shouldShowAppHandleEducation_appHandleMenuExpanded_returnsFalse() = runTest {
- val windowingEducationProto =
- createWindowingEducationProto(
- appUsageStats = mapOf(GMAIL_PACKAGE_NAME to 4),
- appUsageStatsLastUpdateTimestampMillis = Long.MAX_VALUE)
- // Simulate app handle menu is expanded
- val captionState = createAppHandleState(isHandleMenuExpanded = true)
- `when`(datastoreRepository.windowingEducationProto()).thenReturn(windowingEducationProto)
-
- val result = educationFilter.shouldShowAppHandleEducation(captionState)
-
- // We should not show app handle education if app menu is expanded
- assertThat(result).isFalse()
- }
-
- @Test
- fun shouldShowAppHandleEducation_overridePrerequisite_returnsTrue() = runTest {
- // Simulate that gmail app has been launched twice before, minimum app launch count is 3, hence
- // #shouldShowAppHandleEducation should return false. But as we are overriding prerequisite
- // conditions, #shouldShowAppHandleEducation should return true.
- testableResources.addOverride(R.integer.desktop_windowing_education_min_app_launch_count, 3)
- val systemPropertiesKey = "persist.desktop_windowing_app_handle_education_override_conditions"
- whenever(SystemProperties.getBoolean(eq(systemPropertiesKey), anyBoolean())).thenReturn(true)
- val windowingEducationProto =
- createWindowingEducationProto(
- appUsageStats = mapOf(GMAIL_PACKAGE_NAME to 2),
- appUsageStatsLastUpdateTimestampMillis = Long.MAX_VALUE)
- `when`(datastoreRepository.windowingEducationProto()).thenReturn(windowingEducationProto)
-
- val result = educationFilter.shouldShowAppHandleEducation(createAppHandleState())
-
- assertThat(result).isTrue()
- }
+ @JvmField
+ @Rule
+ val extendedMockitoRule =
+ ExtendedMockitoRule.Builder(this).mockStatic(SystemProperties::class.java).build()!!
+ @Mock private lateinit var datastoreRepository: AppHandleEducationDatastoreRepository
+ @Mock private lateinit var mockUsageStatsManager: UsageStatsManager
+ private lateinit var educationFilter: AppHandleEducationFilter
+ private lateinit var testableResources: TestableResources
+ private lateinit var testableContext: TestableContext
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+ testableContext = TestableContext(mContext)
+ testableResources =
+ testableContext.orCreateTestableResources.apply {
+ addOverride(
+ R.array.desktop_windowing_app_handle_education_allowlist_apps,
+ arrayOf(GMAIL_PACKAGE_NAME),
+ )
+ addOverride(
+ R.integer.desktop_windowing_education_required_time_since_setup_seconds,
+ 0,
+ )
+ addOverride(R.integer.desktop_windowing_education_min_app_launch_count, 3)
+ addOverride(
+ R.integer.desktop_windowing_education_app_usage_cache_interval_seconds,
+ MAX_VALUE,
+ )
+ addOverride(R.integer.desktop_windowing_education_app_launch_interval_seconds, 100)
+ }
+ testableContext.addMockSystemService(Context.USAGE_STATS_SERVICE, mockUsageStatsManager)
+ educationFilter = AppHandleEducationFilter(testableContext, datastoreRepository)
+ }
+
+ @Test
+ fun shouldShowAppHandleEducation_isTriggerValid_returnsTrue() = runTest {
+ // setup() makes sure that all of the conditions satisfy and #shouldShowAppHandleEducation
+ // should return true
+ val windowingEducationProto =
+ createWindowingEducationProto(
+ appUsageStats = mapOf(GMAIL_PACKAGE_NAME to 4),
+ appUsageStatsLastUpdateTimestampMillis = Long.MAX_VALUE,
+ )
+ `when`(datastoreRepository.windowingEducationProto()).thenReturn(windowingEducationProto)
+
+ val result = educationFilter.shouldShowAppHandleEducation(createAppHandleState())
+
+ assertThat(result).isTrue()
+ }
+
+ @Test
+ fun shouldShowAppHandleEducation_focusAppNotInAllowlist_returnsFalse() = runTest {
+ // Pass Youtube as current focus app, it is not in allowlist hence
+ // #shouldShowAppHandleEducation
+ // should return false
+ testableResources.addOverride(
+ R.array.desktop_windowing_app_handle_education_allowlist_apps,
+ arrayOf(GMAIL_PACKAGE_NAME),
+ )
+ val windowingEducationProto =
+ createWindowingEducationProto(
+ appUsageStats = mapOf(YOUTUBE_PACKAGE_NAME to 4),
+ appUsageStatsLastUpdateTimestampMillis = Long.MAX_VALUE,
+ )
+ val captionState =
+ createAppHandleState(createTaskInfo(runningTaskPackageName = YOUTUBE_PACKAGE_NAME))
+ `when`(datastoreRepository.windowingEducationProto()).thenReturn(windowingEducationProto)
+
+ val result = educationFilter.shouldShowAppHandleEducation(captionState)
+
+ assertThat(result).isFalse()
+ }
+
+ @Test
+ fun shouldShowAppHandleEducation_timeSinceSetupIsNotSufficient_returnsFalse() = runTest {
+ // Time required to have passed setup is > 100 years, hence #shouldShowAppHandleEducation
+ // should
+ // return false
+ testableResources.addOverride(
+ R.integer.desktop_windowing_education_required_time_since_setup_seconds,
+ MAX_VALUE,
+ )
+ val windowingEducationProto =
+ createWindowingEducationProto(
+ appUsageStats = mapOf(GMAIL_PACKAGE_NAME to 4),
+ appUsageStatsLastUpdateTimestampMillis = Long.MAX_VALUE,
+ )
+ `when`(datastoreRepository.windowingEducationProto()).thenReturn(windowingEducationProto)
+
+ val result = educationFilter.shouldShowAppHandleEducation(createAppHandleState())
+
+ assertThat(result).isFalse()
+ }
+
+ @Test
+ fun shouldShowAppHandleEducation_appHandleHintViewedBefore_returnsFalse() = runTest {
+ // App handle hint has been viewed before, hence #shouldShowAppHandleEducation should return
+ // false
+ val windowingEducationProto =
+ createWindowingEducationProto(
+ appUsageStats = mapOf(GMAIL_PACKAGE_NAME to 4),
+ appHandleHintViewedTimestampMillis = 123L,
+ appUsageStatsLastUpdateTimestampMillis = Long.MAX_VALUE,
+ )
+ `when`(datastoreRepository.windowingEducationProto()).thenReturn(windowingEducationProto)
+
+ val result = educationFilter.shouldShowAppHandleEducation(createAppHandleState())
+
+ assertThat(result).isFalse()
+ }
+
+ @Test
+ fun shouldShowAppHandleEducation_appHandleHintUsedBefore_returnsFalse() = runTest {
+ // App handle hint has been used before, hence #shouldShowAppHandleEducation should return
+ // false
+ val windowingEducationProto =
+ createWindowingEducationProto(
+ appUsageStats = mapOf(GMAIL_PACKAGE_NAME to 4),
+ appHandleHintUsedTimestampMillis = 123L,
+ appUsageStatsLastUpdateTimestampMillis = Long.MAX_VALUE,
+ )
+ `when`(datastoreRepository.windowingEducationProto()).thenReturn(windowingEducationProto)
+
+ val result = educationFilter.shouldShowAppHandleEducation(createAppHandleState())
+
+ assertThat(result).isFalse()
+ }
+
+ @Test
+ fun shouldShowAppHandleEducation_doesNotHaveMinAppUsage_returnsFalse() = runTest {
+ // Simulate that gmail app has been launched twice before, minimum app launch count is 3,
+ // hence
+ // #shouldShowAppHandleEducation should return false
+ testableResources.addOverride(R.integer.desktop_windowing_education_min_app_launch_count, 3)
+ val windowingEducationProto =
+ createWindowingEducationProto(
+ appUsageStats = mapOf(GMAIL_PACKAGE_NAME to 2),
+ appUsageStatsLastUpdateTimestampMillis = Long.MAX_VALUE,
+ )
+ `when`(datastoreRepository.windowingEducationProto()).thenReturn(windowingEducationProto)
+
+ val result = educationFilter.shouldShowAppHandleEducation(createAppHandleState())
+
+ assertThat(result).isFalse()
+ }
+
+ @Test
+ fun shouldShowAppHandleEducation_appUsageStatsStale_queryAppUsageStats() = runTest {
+ // UsageStats caching interval is set to 0ms, that means caching should happen very
+ // frequently
+ testableResources.addOverride(
+ R.integer.desktop_windowing_education_app_usage_cache_interval_seconds,
+ 0,
+ )
+ // The DataStore currently holds a proto object where Gmail's app launch count is recorded
+ // as 4.
+ // This value exceeds the minimum required count of 3.
+ testableResources.addOverride(R.integer.desktop_windowing_education_min_app_launch_count, 3)
+ val windowingEducationProto =
+ createWindowingEducationProto(
+ appUsageStats = mapOf(GMAIL_PACKAGE_NAME to 4),
+ appUsageStatsLastUpdateTimestampMillis = 0,
+ )
+ // The mocked UsageStatsManager is configured to return a launch count of 2 for Gmail.
+ // This value is below the minimum required count of 3.
+ `when`(mockUsageStatsManager.queryAndAggregateUsageStats(anyLong(), anyLong()))
+ .thenReturn(mapOf(GMAIL_PACKAGE_NAME to UsageStats().apply { mAppLaunchCount = 2 }))
+ `when`(datastoreRepository.windowingEducationProto()).thenReturn(windowingEducationProto)
+
+ val result = educationFilter.shouldShowAppHandleEducation(createAppHandleState())
+
+ // Result should be false as queried usage stats should be considered to determine the
+ // result
+ // instead of cached stats
+ assertThat(result).isFalse()
+ }
+
+ @Test
+ fun shouldShowAppHandleEducation_appHandleMenuExpanded_returnsFalse() = runTest {
+ val windowingEducationProto =
+ createWindowingEducationProto(
+ appUsageStats = mapOf(GMAIL_PACKAGE_NAME to 4),
+ appUsageStatsLastUpdateTimestampMillis = Long.MAX_VALUE,
+ )
+ // Simulate app handle menu is expanded
+ val captionState = createAppHandleState(isHandleMenuExpanded = true)
+ `when`(datastoreRepository.windowingEducationProto()).thenReturn(windowingEducationProto)
+
+ val result = educationFilter.shouldShowAppHandleEducation(captionState)
+
+ // We should not show app handle education if app menu is expanded
+ assertThat(result).isFalse()
+ }
+
+ @Test
+ fun shouldShowAppHandleEducation_overridePrerequisite_returnsTrue() = runTest {
+ // Simulate that gmail app has been launched twice before, minimum app launch count is 3,
+ // hence
+ // #shouldShowAppHandleEducation should return false. But as we are overriding prerequisite
+ // conditions, #shouldShowAppHandleEducation should return true.
+ testableResources.addOverride(R.integer.desktop_windowing_education_min_app_launch_count, 3)
+ val systemPropertiesKey =
+ "persist.desktop_windowing_app_handle_education_override_conditions"
+ whenever(SystemProperties.getBoolean(eq(systemPropertiesKey), anyBoolean()))
+ .thenReturn(true)
+ val windowingEducationProto =
+ createWindowingEducationProto(
+ appUsageStats = mapOf(GMAIL_PACKAGE_NAME to 2),
+ appUsageStatsLastUpdateTimestampMillis = Long.MAX_VALUE,
+ )
+ `when`(datastoreRepository.windowingEducationProto()).thenReturn(windowingEducationProto)
+
+ val result = educationFilter.shouldShowAppHandleEducation(createAppHandleState())
+
+ assertThat(result).isTrue()
+ }
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/minimize/DesktopWindowLimitRemoteHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/minimize/DesktopWindowLimitRemoteHandlerTest.kt
index 6a5d9f67e4a7..8d7fb5d7af85 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/minimize/DesktopWindowLimitRemoteHandlerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/minimize/DesktopWindowLimitRemoteHandlerTest.kt
@@ -66,16 +66,27 @@ class DesktopWindowLimitRemoteHandlerTest {
private fun createRemoteHandler(taskIdToMinimize: Int) =
DesktopWindowLimitRemoteHandler(
- shellExecutor, rootTaskDisplayAreaOrganizer, remoteTransition, taskIdToMinimize)
+ shellExecutor,
+ rootTaskDisplayAreaOrganizer,
+ remoteTransition,
+ taskIdToMinimize,
+ )
@Test
fun startAnimation_dontSetTransition_returnsFalse() {
val minimizeTask = createDesktopTask()
val remoteHandler = createRemoteHandler(taskIdToMinimize = minimizeTask.taskId)
- assertThat(remoteHandler.startAnimation(transition,
- createMinimizeTransitionInfo(minimizeTask), startT, finishT, finishCallback)
- ).isFalse()
+ assertThat(
+ remoteHandler.startAnimation(
+ transition,
+ createMinimizeTransitionInfo(minimizeTask),
+ startT,
+ finishT,
+ finishCallback,
+ )
+ )
+ .isFalse()
}
@Test
@@ -84,9 +95,8 @@ class DesktopWindowLimitRemoteHandlerTest {
remoteHandler.setTransition(transition)
val info = createToFrontTransitionInfo()
- assertThat(
- remoteHandler.startAnimation(transition, info, startT, finishT, finishCallback)
- ).isFalse()
+ assertThat(remoteHandler.startAnimation(transition, info, startT, finishT, finishCallback))
+ .isFalse()
}
@Test
@@ -96,9 +106,8 @@ class DesktopWindowLimitRemoteHandlerTest {
remoteHandler.setTransition(transition)
val info = createMinimizeTransitionInfo(minimizeTask)
- assertThat(
- remoteHandler.startAnimation(transition, info, startT, finishT, finishCallback)
- ).isTrue()
+ assertThat(remoteHandler.startAnimation(transition, info, startT, finishT, finishCallback))
+ .isTrue()
}
@Test
@@ -109,8 +118,7 @@ class DesktopWindowLimitRemoteHandlerTest {
remoteHandler.startAnimation(transition, info, startT, finishT, finishCallback)
- verify(rootTaskDisplayAreaOrganizer, times(0))
- .reparentToDisplayArea(anyInt(), any(), any())
+ verify(rootTaskDisplayAreaOrganizer, times(0)).reparentToDisplayArea(anyInt(), any(), any())
}
@Test
@@ -154,14 +162,18 @@ class DesktopWindowLimitRemoteHandlerTest {
private fun createToFrontTransitionInfo() =
TransitionInfoBuilder(TRANSIT_TO_FRONT)
- .addChange(TRANSIT_TO_FRONT,
- TestRunningTaskInfoBuilder().setWindowingMode(WINDOWING_MODE_FREEFORM).build())
+ .addChange(
+ TRANSIT_TO_FRONT,
+ TestRunningTaskInfoBuilder().setWindowingMode(WINDOWING_MODE_FREEFORM).build(),
+ )
.build()
private fun createMinimizeTransitionInfo(minimizeTask: ActivityManager.RunningTaskInfo) =
TransitionInfoBuilder(TRANSIT_TO_FRONT)
- .addChange(TRANSIT_TO_FRONT,
- TestRunningTaskInfoBuilder().setWindowingMode(WINDOWING_MODE_FREEFORM).build())
+ .addChange(
+ TRANSIT_TO_FRONT,
+ TestRunningTaskInfoBuilder().setWindowingMode(WINDOWING_MODE_FREEFORM).build(),
+ )
.addChange(TRANSIT_TO_BACK, minimizeTask)
.build()
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/persistence/DesktopPersistentRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/persistence/DesktopPersistentRepositoryTest.kt
index 4f7e80cf8330..eae206664021 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/persistence/DesktopPersistentRepositoryTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/persistence/DesktopPersistentRepositoryTest.kt
@@ -63,9 +63,10 @@ class DesktopPersistentRepositoryTest : ShellTestCase() {
DataStoreFactory.create(
serializer =
DesktopPersistentRepository.Companion.DesktopPersistentRepositoriesSerializer,
- scope = datastoreScope) {
- testContext.dataStoreFile(DESKTOP_REPOSITORY_STATES_DATASTORE_TEST_FILE)
- }
+ scope = datastoreScope,
+ ) {
+ testContext.dataStoreFile(DESKTOP_REPOSITORY_STATES_DATASTORE_TEST_FILE)
+ }
datastoreRepository = DesktopPersistentRepository(testDatastore)
}
@@ -113,7 +114,8 @@ class DesktopPersistentRepositoryTest : ShellTestCase() {
visibleTasks = visibleTasks,
minimizedTasks = minimizedTasks,
freeformTasksInZOrder = freeformTasksInZOrder,
- userId = DEFAULT_USER_ID)
+ userId = DEFAULT_USER_ID,
+ )
val actualDesktop = datastoreRepository.readDesktop(DEFAULT_USER_ID, DEFAULT_DESKTOP_ID)
assertThat(actualDesktop?.tasksByTaskIdMap).hasSize(2)
@@ -137,7 +139,8 @@ class DesktopPersistentRepositoryTest : ShellTestCase() {
visibleTasks = visibleTasks,
minimizedTasks = minimizedTasks,
freeformTasksInZOrder = freeformTasksInZOrder,
- userId = DEFAULT_USER_ID)
+ userId = DEFAULT_USER_ID,
+ )
val actualDesktop = datastoreRepository.readDesktop(DEFAULT_USER_ID, DEFAULT_DESKTOP_ID)
assertThat(actualDesktop?.tasksByTaskIdMap?.get(task.taskId)?.desktopTaskState)
@@ -161,7 +164,8 @@ class DesktopPersistentRepositoryTest : ShellTestCase() {
visibleTasks = visibleTasks,
minimizedTasks = minimizedTasks,
freeformTasksInZOrder = freeformTasksInZOrder,
- userId = DEFAULT_USER_ID)
+ userId = DEFAULT_USER_ID,
+ )
val actualDesktop = datastoreRepository.readDesktop(DEFAULT_USER_ID, DEFAULT_DESKTOP_ID)
assertThat(actualDesktop?.tasksByTaskIdMap).isEmpty()
@@ -194,7 +198,7 @@ class DesktopPersistentRepositoryTest : ShellTestCase() {
fun createDesktopTask(
taskId: Int,
- state: DesktopTaskState = DesktopTaskState.VISIBLE
+ state: DesktopTaskState = DesktopTaskState.VISIBLE,
): DesktopTask =
DesktopTask.newBuilder().setTaskId(taskId).setDesktopTaskState(state).build()
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/persistence/DesktopRepositoryInitializerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/persistence/DesktopRepositoryInitializerTest.kt
index cdf064b075a1..a3c441698905 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/persistence/DesktopRepositoryInitializerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/persistence/DesktopRepositoryInitializerTest.kt
@@ -47,15 +47,12 @@ import org.mockito.Mockito.spy
import org.mockito.kotlin.mock
import org.mockito.kotlin.whenever
-
@SmallTest
@RunWith(AndroidTestingRunner::class)
@ExperimentalCoroutinesApi
class DesktopRepositoryInitializerTest : ShellTestCase() {
- @JvmField
- @Rule
- val setFlagsRule = SetFlagsRule()
+ @JvmField @Rule val setFlagsRule = SetFlagsRule()
private lateinit var repositoryInitializer: DesktopRepositoryInitializer
private lateinit var shellInit: ShellInit
@@ -82,7 +79,7 @@ class DesktopRepositoryInitializerTest : ShellTestCase() {
persistentRepository,
repositoryInitializer,
datastoreScope,
- userManager
+ userManager,
)
}
@@ -90,101 +87,94 @@ class DesktopRepositoryInitializerTest : ShellTestCase() {
@EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_PERSISTENCE, FLAG_ENABLE_DESKTOP_WINDOWING_HSUM)
fun initWithPersistence_multipleUsers_addedCorrectly() =
runTest(StandardTestDispatcher()) {
- whenever(persistentRepository.getUserDesktopRepositoryMap()).thenReturn(
- mapOf(
- USER_ID_1 to desktopRepositoryState1,
- USER_ID_2 to desktopRepositoryState2
+ whenever(persistentRepository.getUserDesktopRepositoryMap())
+ .thenReturn(
+ mapOf(
+ USER_ID_1 to desktopRepositoryState1,
+ USER_ID_2 to desktopRepositoryState2,
+ )
)
- )
whenever(persistentRepository.getDesktopRepositoryState(USER_ID_1))
.thenReturn(desktopRepositoryState1)
whenever(persistentRepository.getDesktopRepositoryState(USER_ID_2))
.thenReturn(desktopRepositoryState2)
- whenever(persistentRepository.readDesktop(USER_ID_1, DESKTOP_ID_1))
- .thenReturn(desktop1)
- whenever(persistentRepository.readDesktop(USER_ID_1, DESKTOP_ID_2))
- .thenReturn(desktop2)
- whenever(persistentRepository.readDesktop(USER_ID_2, DESKTOP_ID_3))
- .thenReturn(desktop3)
+ whenever(persistentRepository.readDesktop(USER_ID_1, DESKTOP_ID_1)).thenReturn(desktop1)
+ whenever(persistentRepository.readDesktop(USER_ID_1, DESKTOP_ID_2)).thenReturn(desktop2)
+ whenever(persistentRepository.readDesktop(USER_ID_2, DESKTOP_ID_3)).thenReturn(desktop3)
repositoryInitializer.initialize(desktopUserRepositories)
// Desktop Repository currently returns all tasks across desktops for a specific user
- // since the repository currently doesn't handle desktops. This test logic should be updated
+ // since the repository currently doesn't handle desktops. This test logic should be
+ // updated
// once the repository handles multiple desktops.
assertThat(
- desktopUserRepositories.getProfile(USER_ID_1)
- .getActiveTasks(DEFAULT_DISPLAY)
- )
+ desktopUserRepositories.getProfile(USER_ID_1).getActiveTasks(DEFAULT_DISPLAY)
+ )
.containsExactly(1, 3, 4, 5)
.inOrder()
assertThat(
- desktopUserRepositories.getProfile(USER_ID_1)
- .getExpandedTasksOrdered(DEFAULT_DISPLAY)
- )
+ desktopUserRepositories
+ .getProfile(USER_ID_1)
+ .getExpandedTasksOrdered(DEFAULT_DISPLAY)
+ )
.containsExactly(5, 1)
.inOrder()
assertThat(
- desktopUserRepositories.getProfile(USER_ID_1)
- .getMinimizedTasks(DEFAULT_DISPLAY)
- )
+ desktopUserRepositories.getProfile(USER_ID_1).getMinimizedTasks(DEFAULT_DISPLAY)
+ )
.containsExactly(3, 4)
.inOrder()
assertThat(
- desktopUserRepositories.getProfile(USER_ID_2)
- .getActiveTasks(DEFAULT_DISPLAY)
- )
+ desktopUserRepositories.getProfile(USER_ID_2).getActiveTasks(DEFAULT_DISPLAY)
+ )
.containsExactly(7, 8)
.inOrder()
assertThat(
- desktopUserRepositories.getProfile(USER_ID_2)
- .getExpandedTasksOrdered(DEFAULT_DISPLAY)
- )
+ desktopUserRepositories
+ .getProfile(USER_ID_2)
+ .getExpandedTasksOrdered(DEFAULT_DISPLAY)
+ )
.contains(7)
assertThat(
- desktopUserRepositories.getProfile(USER_ID_2)
- .getMinimizedTasks(DEFAULT_DISPLAY)
- ).containsExactly(8)
+ desktopUserRepositories.getProfile(USER_ID_2).getMinimizedTasks(DEFAULT_DISPLAY)
+ )
+ .containsExactly(8)
}
@Test
@EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_PERSISTENCE)
fun initWithPersistence_singleUser_addedCorrectly() =
runTest(StandardTestDispatcher()) {
- whenever(persistentRepository.getUserDesktopRepositoryMap()).thenReturn(
- mapOf(
- USER_ID_1 to desktopRepositoryState1,
- )
- )
+ whenever(persistentRepository.getUserDesktopRepositoryMap())
+ .thenReturn(mapOf(USER_ID_1 to desktopRepositoryState1))
whenever(persistentRepository.getDesktopRepositoryState(USER_ID_1))
.thenReturn(desktopRepositoryState1)
- whenever(persistentRepository.readDesktop(USER_ID_1, DESKTOP_ID_1))
- .thenReturn(desktop1)
- whenever(persistentRepository.readDesktop(USER_ID_1, DESKTOP_ID_2))
- .thenReturn(desktop2)
+ whenever(persistentRepository.readDesktop(USER_ID_1, DESKTOP_ID_1)).thenReturn(desktop1)
+ whenever(persistentRepository.readDesktop(USER_ID_1, DESKTOP_ID_2)).thenReturn(desktop2)
repositoryInitializer.initialize(desktopUserRepositories)
// Desktop Repository currently returns all tasks across desktops for a specific user
- // since the repository currently doesn't handle desktops. This test logic should be updated
+ // since the repository currently doesn't handle desktops. This test logic should be
+ // updated
// once the repository handles multiple desktops.
assertThat(
- desktopUserRepositories.getProfile(USER_ID_1)
- .getActiveTasks(DEFAULT_DISPLAY)
- )
+ desktopUserRepositories.getProfile(USER_ID_1).getActiveTasks(DEFAULT_DISPLAY)
+ )
.containsExactly(1, 3, 4, 5)
.inOrder()
assertThat(
- desktopUserRepositories.getProfile(USER_ID_1)
- .getExpandedTasksOrdered(DEFAULT_DISPLAY)
- )
+ desktopUserRepositories
+ .getProfile(USER_ID_1)
+ .getExpandedTasksOrdered(DEFAULT_DISPLAY)
+ )
.containsExactly(5, 1)
.inOrder()
assertThat(
- desktopUserRepositories.getProfile(USER_ID_1)
- .getMinimizedTasks(DEFAULT_DISPLAY)
- )
+ desktopUserRepositories.getProfile(USER_ID_1).getMinimizedTasks(DEFAULT_DISPLAY)
+ )
.containsExactly(3, 4)
.inOrder()
}
@@ -202,70 +192,73 @@ class DesktopRepositoryInitializerTest : ShellTestCase() {
const val DESKTOP_ID_3 = 4
val freeformTasksInZOrder1 = listOf(1, 3)
- val desktop1: Desktop = Desktop.newBuilder()
- .setDesktopId(DESKTOP_ID_1)
- .addAllZOrderedTasks(freeformTasksInZOrder1)
- .putTasksByTaskId(
- 1,
- DesktopTask.newBuilder()
- .setTaskId(1)
- .setDesktopTaskState(DesktopTaskState.VISIBLE)
- .build()
- )
- .putTasksByTaskId(
- 3,
- DesktopTask.newBuilder()
- .setTaskId(3)
- .setDesktopTaskState(DesktopTaskState.MINIMIZED)
- .build()
- )
- .build()
+ val desktop1: Desktop =
+ Desktop.newBuilder()
+ .setDesktopId(DESKTOP_ID_1)
+ .addAllZOrderedTasks(freeformTasksInZOrder1)
+ .putTasksByTaskId(
+ 1,
+ DesktopTask.newBuilder()
+ .setTaskId(1)
+ .setDesktopTaskState(DesktopTaskState.VISIBLE)
+ .build(),
+ )
+ .putTasksByTaskId(
+ 3,
+ DesktopTask.newBuilder()
+ .setTaskId(3)
+ .setDesktopTaskState(DesktopTaskState.MINIMIZED)
+ .build(),
+ )
+ .build()
val freeformTasksInZOrder2 = listOf(4, 5)
- val desktop2: Desktop = Desktop.newBuilder()
- .setDesktopId(DESKTOP_ID_2)
- .addAllZOrderedTasks(freeformTasksInZOrder2)
- .putTasksByTaskId(
- 4,
- DesktopTask.newBuilder()
- .setTaskId(4)
- .setDesktopTaskState(DesktopTaskState.MINIMIZED)
- .build()
- )
- .putTasksByTaskId(
- 5,
- DesktopTask.newBuilder()
- .setTaskId(5)
- .setDesktopTaskState(DesktopTaskState.VISIBLE)
- .build()
- )
- .build()
+ val desktop2: Desktop =
+ Desktop.newBuilder()
+ .setDesktopId(DESKTOP_ID_2)
+ .addAllZOrderedTasks(freeformTasksInZOrder2)
+ .putTasksByTaskId(
+ 4,
+ DesktopTask.newBuilder()
+ .setTaskId(4)
+ .setDesktopTaskState(DesktopTaskState.MINIMIZED)
+ .build(),
+ )
+ .putTasksByTaskId(
+ 5,
+ DesktopTask.newBuilder()
+ .setTaskId(5)
+ .setDesktopTaskState(DesktopTaskState.VISIBLE)
+ .build(),
+ )
+ .build()
val freeformTasksInZOrder3 = listOf(7, 8)
- val desktop3: Desktop = Desktop.newBuilder()
- .setDesktopId(DESKTOP_ID_3)
- .addAllZOrderedTasks(freeformTasksInZOrder3)
- .putTasksByTaskId(
- 7,
- DesktopTask.newBuilder()
- .setTaskId(7)
- .setDesktopTaskState(DesktopTaskState.VISIBLE)
- .build()
- )
- .putTasksByTaskId(
- 8,
- DesktopTask.newBuilder()
- .setTaskId(8)
- .setDesktopTaskState(DesktopTaskState.MINIMIZED)
- .build()
- )
- .build()
- val desktopRepositoryState1: DesktopRepositoryState = DesktopRepositoryState.newBuilder()
- .putDesktop(DESKTOP_ID_1, desktop1)
- .putDesktop(DESKTOP_ID_2, desktop2)
- .build()
- val desktopRepositoryState2: DesktopRepositoryState = DesktopRepositoryState.newBuilder()
- .putDesktop(DESKTOP_ID_3, desktop3)
- .build()
+ val desktop3: Desktop =
+ Desktop.newBuilder()
+ .setDesktopId(DESKTOP_ID_3)
+ .addAllZOrderedTasks(freeformTasksInZOrder3)
+ .putTasksByTaskId(
+ 7,
+ DesktopTask.newBuilder()
+ .setTaskId(7)
+ .setDesktopTaskState(DesktopTaskState.VISIBLE)
+ .build(),
+ )
+ .putTasksByTaskId(
+ 8,
+ DesktopTask.newBuilder()
+ .setTaskId(8)
+ .setDesktopTaskState(DesktopTaskState.MINIMIZED)
+ .build(),
+ )
+ .build()
+ val desktopRepositoryState1: DesktopRepositoryState =
+ DesktopRepositoryState.newBuilder()
+ .putDesktop(DESKTOP_ID_1, desktop1)
+ .putDesktop(DESKTOP_ID_2, desktop2)
+ .build()
+ val desktopRepositoryState2: DesktopRepositoryState =
+ DesktopRepositoryState.newBuilder().putDesktop(DESKTOP_ID_3, desktop3).build()
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTestUtils.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTestUtils.java
index 232ae0750c3a..ada7b4aff37d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTestUtils.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTestUtils.java
@@ -36,6 +36,7 @@ import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.split.SplitLayout;
import com.android.wm.shell.common.split.SplitState;
+import com.android.wm.shell.desktopmode.DesktopTasksController;
import com.android.wm.shell.recents.RecentTasksController;
import com.android.wm.shell.shared.TransactionPool;
import com.android.wm.shell.transition.Transitions;
@@ -81,11 +82,13 @@ public class SplitTestUtils {
ShellExecutor mainExecutor, Handler mainHandler, ShellExecutor bgExecutor,
Optional<RecentTasksController> recentTasks,
LaunchAdjacentController launchAdjacentController,
- Optional<WindowDecorViewModel> windowDecorViewModel, SplitState splitState) {
+ Optional<WindowDecorViewModel> windowDecorViewModel, SplitState splitState,
+ Optional<DesktopTasksController> desktopTasksController) {
super(context, displayId, syncQueue, taskOrganizer, mainStage,
sideStage, displayController, imeController, insetsController, splitLayout,
transitions, transactionPool, mainExecutor, mainHandler, bgExecutor,
- recentTasks, launchAdjacentController, windowDecorViewModel, splitState);
+ recentTasks, launchAdjacentController, windowDecorViewModel, splitState,
+ desktopTasksController);
// Prepare root task for testing.
mRootTask = new TestRunningTaskInfoBuilder().build();
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
index 0d612c17c462..ffa8b6089660 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
@@ -149,7 +149,7 @@ public class SplitTransitionTests extends ShellTestCase {
mSyncQueue, mTaskOrganizer, mMainStage, mSideStage, mDisplayController,
mDisplayImeController, mDisplayInsetsController, mSplitLayout, mTransitions,
mTransactionPool, mMainExecutor, mMainHandler, mBgExecutor, Optional.empty(),
- mLaunchAdjacentController, Optional.empty(), mSplitState);
+ mLaunchAdjacentController, Optional.empty(), mSplitState, Optional.empty());
mStageCoordinator.setMixedHandler(mMixedHandler);
mSplitScreenTransitions = mStageCoordinator.getSplitTransitions();
doAnswer((Answer<IBinder>) invocation -> mock(IBinder.class))
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 a6aeabd5bd19..9d1df864764f 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
@@ -143,8 +143,9 @@ public class StageCoordinatorTests extends ShellTestCase {
mStageCoordinator = spy(new StageCoordinator(mContext, DEFAULT_DISPLAY, mSyncQueue,
mTaskOrganizer, mMainStage, mSideStage, mDisplayController, mDisplayImeController,
mDisplayInsetsController, mSplitLayout, mTransitions, mTransactionPool,
- mMainExecutor, mMainHandler, mBgExecutor, Optional.empty(),
- mLaunchAdjacentController, Optional.empty(), mSplitState));
+ mMainExecutor, mMainHandler, mBgExecutor, Optional.empty(),
+ mLaunchAdjacentController, Optional.empty(), mSplitState,
+ Optional.empty()));
mDividerLeash = new SurfaceControl.Builder().setName("fakeDivider").build();
when(mSplitLayout.getTopLeftBounds()).thenReturn(mBounds1);
diff --git a/media/java/android/media/AudioDevicePort.java b/media/java/android/media/AudioDevicePort.java
index 4b3962e6dd74..9e9bbeea0635 100644
--- a/media/java/android/media/AudioDevicePort.java
+++ b/media/java/android/media/AudioDevicePort.java
@@ -210,6 +210,27 @@ public class AudioDevicePort extends AudioPort {
return super.equals(o);
}
+ /**
+ * Returns true if the AudioDevicePort passed as argument represents the same device (same
+ * type and same address). This is different from equals() in that the port IDs are not compared
+ * which allows matching devices across native audio server restarts.
+ * @param other the other audio device port to compare to.
+ * @return true if both device port correspond to the same audio device, false otherwise.
+ * @hide
+ */
+ public boolean isSameAs(AudioDevicePort other) {
+ if (mType != other.type()) {
+ return false;
+ }
+ if (mAddress == null && other.address() != null) {
+ return false;
+ }
+ if (!mAddress.equals(other.address())) {
+ return false;
+ }
+ return true;
+ }
+
@Override
public String toString() {
String type = (mRole == ROLE_SOURCE ?
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 9beeef4c160f..04fa90514dc9 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -8068,15 +8068,15 @@ public class AudioManager {
ArrayList<AudioDevicePort> ports_A, ArrayList<AudioDevicePort> ports_B, int flags) {
ArrayList<AudioDevicePort> delta_ports = new ArrayList<AudioDevicePort>();
-
- AudioDevicePort cur_port = null;
for (int cur_index = 0; cur_index < ports_B.size(); cur_index++) {
boolean cur_port_found = false;
- cur_port = ports_B.get(cur_index);
+ AudioDevicePort cur_port = ports_B.get(cur_index);
for (int prev_index = 0;
prev_index < ports_A.size() && !cur_port_found;
prev_index++) {
- cur_port_found = (cur_port.id() == ports_A.get(prev_index).id());
+ // Do not compare devices by port ID as these change when the native
+ // audio server restarts
+ cur_port_found = cur_port.isSameAs(ports_A.get(prev_index));
}
if (!cur_port_found) {
@@ -8422,13 +8422,10 @@ public class AudioManager {
* Callback method called when the mediaserver dies
*/
public void onServiceDied() {
- synchronized (mDeviceCallbacks) {
- broadcastDeviceListChange_sync(null);
- }
+ // Nothing to do here
}
}
-
/**
* @hide
* Abstract class to receive event notification about audioserver process state.
diff --git a/media/java/android/media/AudioPortEventHandler.java b/media/java/android/media/AudioPortEventHandler.java
index 763eb2944d19..5685710e5f15 100644
--- a/media/java/android/media/AudioPortEventHandler.java
+++ b/media/java/android/media/AudioPortEventHandler.java
@@ -97,17 +97,15 @@ class AudioPortEventHandler {
ArrayList<AudioPort> ports = new ArrayList<AudioPort>();
ArrayList<AudioPatch> patches = new ArrayList<AudioPatch>();
- if (msg.what != AUDIOPORT_EVENT_SERVICE_DIED) {
- int status = AudioManager.updateAudioPortCache(ports, patches, null);
- if (status != AudioManager.SUCCESS) {
- // Since audio ports and audio patches are not null, the return
- // value could be ERROR due to inconsistency between port generation
- // and patch generation. In this case, we need to reschedule the
- // message to make sure the native callback is done.
- sendMessageDelayed(obtainMessage(msg.what, msg.obj),
- RESCHEDULE_MESSAGE_DELAY_MS);
- return;
- }
+ int status = AudioManager.updateAudioPortCache(ports, patches, null);
+ if (status != AudioManager.SUCCESS) {
+ // Since audio ports and audio patches are not null, the return
+ // value could be ERROR due to inconsistency between port generation
+ // and patch generation. In this case, we need to reschedule the
+ // message to make sure the native callback is done.
+ sendMessageDelayed(obtainMessage(msg.what, msg.obj),
+ RESCHEDULE_MESSAGE_DELAY_MS);
+ return;
}
switch (msg.what) {
diff --git a/packages/SettingsLib/SettingsTheme/src/com/android/settingslib/widget/SettingsBasePreferenceFragment.kt b/packages/SettingsLib/SettingsTheme/src/com/android/settingslib/widget/SettingsBasePreferenceFragment.kt
index 265c065e924e..bfaeb42d5a31 100644
--- a/packages/SettingsLib/SettingsTheme/src/com/android/settingslib/widget/SettingsBasePreferenceFragment.kt
+++ b/packages/SettingsLib/SettingsTheme/src/com/android/settingslib/widget/SettingsBasePreferenceFragment.kt
@@ -16,6 +16,9 @@
package com.android.settingslib.widget
+import android.os.Bundle
+import android.view.View
+import androidx.annotation.CallSuper
import androidx.preference.PreferenceFragmentCompat
import androidx.preference.PreferenceScreen
import androidx.recyclerview.widget.RecyclerView
@@ -23,9 +26,18 @@ import androidx.recyclerview.widget.RecyclerView
/** Base class for Settings to use PreferenceFragmentCompat */
abstract class SettingsBasePreferenceFragment : PreferenceFragmentCompat() {
+ @CallSuper
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ if (SettingsThemeHelper.isExpressiveTheme(requireContext())) {
+ // Don't allow any divider in between the preferences in expressive design.
+ setDivider(null)
+ }
+ }
+
override fun onCreateAdapter(preferenceScreen: PreferenceScreen): RecyclerView.Adapter<*> {
if (SettingsThemeHelper.isExpressiveTheme(requireContext()))
return SettingsPreferenceGroupAdapter(preferenceScreen)
return super.onCreateAdapter(preferenceScreen)
}
-} \ No newline at end of file
+}
diff --git a/packages/SettingsLib/res/values/arrays.xml b/packages/SettingsLib/res/values/arrays.xml
index 63c8929ef652..3d3dad379417 100644
--- a/packages/SettingsLib/res/values/arrays.xml
+++ b/packages/SettingsLib/res/values/arrays.xml
@@ -683,9 +683,9 @@
<!-- Values for showing shade on external display for developers -->
<string-array name="shade_display_awareness_values" >
- <item>device-display</item>
- <item>external-display</item>
- <item>focus-based</item>
+ <item>default_display</item>
+ <item>any_external_display</item>
+ <item>status_bar_latest_touch</item>
</string-array>
</resources>
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index 6128d45831fb..55f48e3e367f 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -2089,7 +2089,33 @@ public class SettingsProvider extends ContentProvider {
// setting.
return false;
}
- final String mimeType = getContext().getContentResolver().getType(audioUri);
+
+ // If the audioUri comes from FileProvider, the security check will fail. Currently, it
+ // should not have too many FileProvider Uri usage, using a workaround fix here.
+ // Only allow for caller is privileged apps
+ ApplicationInfo aInfo = null;
+ try {
+ aInfo = getCallingApplicationInfoOrThrow();
+ } catch (IllegalStateException ignored) {
+ Slog.w(LOG_TAG, "isValidMediaUri: cannot get calling app info for setting: "
+ + name + " URI: " + audioUri);
+ return false;
+ }
+ final boolean isPrivilegedApp = aInfo != null ? aInfo.isPrivilegedApp() : false;
+ String mimeType = null;
+ if (isPrivilegedApp) {
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ mimeType = getContext().getContentResolver().getType(audioUri);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ } else {
+ mimeType = getContext().getContentResolver().getType(audioUri);
+ }
+ if (DEBUG) {
+ Slog.v(LOG_TAG, "isValidMediaUri mimeType: " + mimeType);
+ }
if (mimeType == null) {
Slog.e(LOG_TAG,
"mutateSystemSetting for setting: " + name + " URI: " + audioUri
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
index d5eaf829b746..f36fd34d0445 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
@@ -1020,7 +1020,7 @@ private fun EmptyStateCta(contentPadding: PaddingValues, viewModel: BaseCommunal
text = titleForEmptyStateCTA,
style = MaterialTheme.typography.displaySmall,
textAlign = TextAlign.Center,
- color = colors.primary,
+ color = colors.onPrimary,
modifier =
Modifier.focusable().semantics(mergeDescendants = true) {
contentDescription = titleForEmptyStateCTA
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt
index e78862e0e922..5c7ca97474b7 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt
@@ -29,11 +29,8 @@ import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
-import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
-import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.dimensionResource
-import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.viewinterop.AndroidView
import androidx.lifecycle.compose.collectAsStateWithLifecycle
@@ -52,7 +49,6 @@ import com.android.systemui.lifecycle.rememberViewModel
import com.android.systemui.notifications.ui.composable.ConstrainedNotificationStack
import com.android.systemui.notifications.ui.composable.SnoozeableHeadsUpNotificationSpace
import com.android.systemui.res.R
-import com.android.systemui.shade.LargeScreenHeaderHelper
import com.android.systemui.shade.ShadeDisplayAware
import com.android.systemui.statusbar.notification.icon.ui.viewbinder.AlwaysOnDisplayNotificationIconViewStore
import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerViewBinder
@@ -179,16 +175,13 @@ constructor(
return
}
- val splitShadeTopMargin: Dp =
- LargeScreenHeaderHelper.getLargeScreenHeaderHeight(LocalContext.current).dp
-
ConstrainedNotificationStack(
stackScrollView = stackScrollView.get(),
viewModel = rememberViewModel("Notifications") { viewModelFactory.create() },
modifier =
modifier
.fillMaxWidth()
- .thenIf(isShadeLayoutWide) { Modifier.padding(top = splitShadeTopMargin) }
+ .thenIf(isShadeLayoutWide) { Modifier.padding(top = 12.dp) }
.let {
if (burnInParams == null) {
it
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/common/ui/data/repository/ConfigurationRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/common/ui/data/repository/ConfigurationRepositoryImplTest.kt
index a308c8ee38ca..3f4d3f8ba12a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/common/ui/data/repository/ConfigurationRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/common/ui/data/repository/ConfigurationRepositoryImplTest.kt
@@ -98,6 +98,21 @@ class ConfigurationRepositoryImplTest : SysuiTestCase() {
}
@Test
+ fun onMovedToDisplays_updatesOnMovedToDisplay() =
+ testScope.runTest {
+ val lastOnMovedToDisplay by collectLastValue(underTest.onMovedToDisplay)
+ assertThat(lastOnMovedToDisplay).isNull()
+
+ val configurationCallback = withArgCaptor {
+ verify(configurationController).addCallback(capture())
+ }
+
+ configurationCallback.onMovedToDisplay(1, Configuration())
+ runCurrent()
+ assertThat(lastOnMovedToDisplay).isEqualTo(1)
+ }
+
+ @Test
fun onAnyConfigurationChange_updatesOnConfigChanged() =
testScope.runTest {
val lastAnyConfigurationChange by collectLastValue(underTest.onAnyConfigurationChange)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/AppLaunchDataRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/AppLaunchDataRepositoryTest.kt
new file mode 100644
index 000000000000..477e31e8a66c
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/AppLaunchDataRepositoryTest.kt
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2024 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.systemui.keyboard.shortcut.data.repository
+
+import android.hardware.input.AppLaunchData
+import android.hardware.input.AppLaunchData.RoleData
+import android.hardware.input.InputGestureData
+import android.hardware.input.InputGestureData.createKeyTrigger
+import android.hardware.input.fakeInputManager
+import android.view.KeyEvent.KEYCODE_A
+import android.view.KeyEvent.META_ALT_ON
+import android.view.KeyEvent.META_CTRL_ON
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.keyboard.shortcut.appLaunchDataRepository
+import com.android.systemui.keyboard.shortcut.shared.model.shortcutCommand
+import com.android.systemui.keyboard.shortcut.shortcutHelperTestHelper
+import com.android.systemui.kosmos.runTest
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.whenever
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class AppLaunchDataRepositoryTest : SysuiTestCase() {
+ private val kosmos = testKosmos().useUnconfinedTestDispatcher()
+ private val inputManager = kosmos.fakeInputManager.inputManager
+ private val testHelper = kosmos.shortcutHelperTestHelper
+ private val repo = kosmos.appLaunchDataRepository
+
+ @Test
+ fun appLaunchData_returnsDataRetrievedFromApiBasedOnShortcutCommand() =
+ kosmos.runTest {
+ val inputGesture = simpleInputGestureDataForAppLaunchShortcut()
+ setApiAppLaunchBookmarks(listOf(inputGesture))
+
+ testHelper.toggle(TEST_DEVICE_ID)
+
+ val appLaunchData =
+ repo.getAppLaunchDataForShortcutWithCommand(
+ shortcutCommand =
+ shortcutCommand {
+ key("Ctrl")
+ key("Alt")
+ key("A")
+ }
+ )
+
+ assertThat(appLaunchData).isEqualTo(inputGesture.action.appLaunchData())
+ }
+
+ @Test
+ fun appLaunchData_returnsSameDataForAnyOrderOfShortcutCommandKeys() =
+ kosmos.runTest {
+ val inputGesture = simpleInputGestureDataForAppLaunchShortcut()
+ setApiAppLaunchBookmarks(listOf(inputGesture))
+
+ testHelper.toggle(TEST_DEVICE_ID)
+
+ val shortcutCommandCtrlAltA = shortcutCommand {
+ key("Ctrl")
+ key("Alt")
+ key("A")
+ }
+
+ val shortcutCommandCtrlAAlt = shortcutCommand {
+ key("Ctrl")
+ key("A")
+ key("Alt")
+ }
+
+ val shortcutCommandAltCtrlA = shortcutCommand {
+ key("Alt")
+ key("Ctrl")
+ key("A")
+ }
+
+ assertThat(repo.getAppLaunchDataForShortcutWithCommand(shortcutCommandCtrlAltA))
+ .isEqualTo(inputGesture.action.appLaunchData())
+
+ assertThat(repo.getAppLaunchDataForShortcutWithCommand(shortcutCommandCtrlAAlt))
+ .isEqualTo(inputGesture.action.appLaunchData())
+
+ assertThat(repo.getAppLaunchDataForShortcutWithCommand(shortcutCommandAltCtrlA))
+ .isEqualTo(inputGesture.action.appLaunchData())
+ }
+
+ private fun setApiAppLaunchBookmarks(appLaunchBookmarks: List<InputGestureData>) {
+ whenever(inputManager.appLaunchBookmarks).thenReturn(appLaunchBookmarks)
+ }
+
+ private fun simpleInputGestureDataForAppLaunchShortcut(
+ keyCode: Int = KEYCODE_A,
+ modifiers: Int = META_CTRL_ON or META_ALT_ON,
+ appLaunchData: AppLaunchData = RoleData(TEST_ROLE),
+ ): InputGestureData {
+ return InputGestureData.Builder()
+ .setTrigger(createKeyTrigger(keyCode, modifiers))
+ .setAppLaunchData(appLaunchData)
+ .build()
+ }
+
+ private companion object {
+ private const val TEST_ROLE = "Test role"
+ private const val TEST_DEVICE_ID = 123
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/CustomShortcutCategoriesRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/CustomShortcutCategoriesRepositoryTest.kt
index d12c04586ac2..4cfb26e6555b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/CustomShortcutCategoriesRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/CustomShortcutCategoriesRepositoryTest.kt
@@ -18,14 +18,21 @@ package com.android.systemui.keyboard.shortcut.data.repository
import android.content.Context
import android.content.Context.INPUT_SERVICE
+import android.hardware.input.AppLaunchData
+import android.hardware.input.AppLaunchData.RoleData
import android.hardware.input.InputGestureData
+import android.hardware.input.InputGestureData.createKeyTrigger
import android.hardware.input.InputManager.CUSTOM_INPUT_GESTURE_RESULT_ERROR_DOES_NOT_EXIST
import android.hardware.input.InputManager.CUSTOM_INPUT_GESTURE_RESULT_SUCCESS
+import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION
import android.hardware.input.fakeInputManager
import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
+import android.view.KeyEvent.KEYCODE_A
import android.view.KeyEvent.KEYCODE_SLASH
+import android.view.KeyEvent.META_ALT_ON
import android.view.KeyEvent.META_CAPS_LOCK_ON
+import android.view.KeyEvent.META_CTRL_ON
import android.view.KeyEvent.META_META_ON
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
@@ -44,14 +51,15 @@ import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.allCusto
import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.customizableInputGestureWithUnknownKeyGestureType
import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.expectedShortcutCategoriesWithSimpleShortcutCombination
import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.goHomeInputGestureData
+import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.launchCalendarShortcutAddRequest
import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.standardKeyCombination
import com.android.systemui.keyboard.shortcut.shared.model.KeyCombination
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCustomizationRequestInfo
-import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCustomizationRequestInfo.Add
-import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCustomizationRequestInfo.Delete
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCustomizationRequestInfo.SingleShortcutCustomization
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutKey
import com.android.systemui.keyboard.shortcut.shortcutHelperTestHelper
import com.android.systemui.kosmos.testScope
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
import com.android.systemui.res.R
import com.android.systemui.settings.FakeUserTracker
import com.android.systemui.settings.userTracker
@@ -72,7 +80,7 @@ class CustomShortcutCategoriesRepositoryTest : SysuiTestCase() {
private val mockUserContext: Context = mock()
private val kosmos =
- testKosmos().also {
+ testKosmos().useUnconfinedTestDispatcher().also {
it.userTracker = FakeUserTracker(onCreateCurrentUserContext = { mockUserContext })
}
@@ -242,6 +250,32 @@ class CustomShortcutCategoriesRepositoryTest : SysuiTestCase() {
}
@Test
+ fun buildInputGestureDataForAppLaunchShortcut_keyGestureTypeIsTypeLaunchApp() =
+ testScope.runTest {
+ setApiAppLaunchBookmarks(listOf(simpleInputGestureDataForAppLaunchShortcut()))
+ helper.toggle(deviceId = 123)
+ repo.onCustomizationRequested(launchCalendarShortcutAddRequest)
+ repo.updateUserKeyCombination(standardKeyCombination)
+
+ val inputGestureData = repo.buildInputGestureDataForShortcutBeingCustomized()
+
+ assertThat(inputGestureData?.action?.keyGestureType())
+ .isEqualTo(KEY_GESTURE_TYPE_LAUNCH_APPLICATION)
+ }
+
+ @Test
+ fun buildInputGestureDataForAppLaunchShortcut_appLaunchDataIsAdded() =
+ testScope.runTest {
+ setApiAppLaunchBookmarks(listOf(simpleInputGestureDataForAppLaunchShortcut()))
+ helper.toggle(deviceId = 123)
+ repo.onCustomizationRequested(launchCalendarShortcutAddRequest)
+ repo.updateUserKeyCombination(standardKeyCombination)
+
+ val inputGestureData = repo.buildInputGestureDataForShortcutBeingCustomized()
+ assertThat(inputGestureData?.action?.appLaunchData()).isNotNull()
+ }
+
+ @Test
@EnableFlags(FLAG_ENABLE_CUSTOMIZABLE_INPUT_GESTURES, FLAG_USE_KEY_GESTURE_EVENT_HANDLER)
fun deleteShortcut_successfullyRetrievesGestureDataAndDeletesShortcut() {
testScope.runTest {
@@ -304,17 +338,17 @@ class CustomShortcutCategoriesRepositoryTest : SysuiTestCase() {
private suspend fun customizeShortcut(
customizationRequest: ShortcutCustomizationRequestInfo,
- keyCombination: KeyCombination? = null
- ): ShortcutCustomizationRequestResult{
+ keyCombination: KeyCombination? = null,
+ ): ShortcutCustomizationRequestResult {
repo.onCustomizationRequested(customizationRequest)
repo.updateUserKeyCombination(keyCombination)
return when (customizationRequest) {
- is Add -> {
+ is SingleShortcutCustomization.Add -> {
repo.confirmAndSetShortcutCurrentlyBeingCustomized()
}
- is Delete -> {
+ is SingleShortcutCustomization.Delete -> {
repo.deleteShortcutCurrentlyBeingCustomized()
}
@@ -352,4 +386,19 @@ class CustomShortcutCategoriesRepositoryTest : SysuiTestCase() {
assertThat(categories).isEmpty()
}
}
+
+ private fun setApiAppLaunchBookmarks(appLaunchBookmarks: List<InputGestureData>) {
+ whenever(inputManager.appLaunchBookmarks).thenReturn(appLaunchBookmarks)
+ }
+
+ private fun simpleInputGestureDataForAppLaunchShortcut(
+ keyCode: Int = KEYCODE_A,
+ modifiers: Int = META_CTRL_ON or META_ALT_ON,
+ appLaunchData: AppLaunchData = RoleData("Test role"),
+ ): InputGestureData {
+ return InputGestureData.Builder()
+ .setTrigger(createKeyTrigger(keyCode, modifiers))
+ .setAppLaunchData(appLaunchData)
+ .build()
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/InputGestureDataAdapterTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/InputGestureDataAdapterTest.kt
index f78c692ee4c2..96410597e20c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/InputGestureDataAdapterTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/InputGestureDataAdapterTest.kt
@@ -28,6 +28,7 @@ import android.hardware.input.AppLaunchData
import android.hardware.input.AppLaunchData.RoleData
import android.hardware.input.InputGestureData
import android.hardware.input.InputGestureData.createKeyTrigger
+import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION
import android.view.KeyEvent.KEYCODE_A
import android.view.KeyEvent.META_ALT_ON
import android.view.KeyEvent.META_CTRL_ON
@@ -55,14 +56,15 @@ import org.mockito.kotlin.eq
import org.mockito.kotlin.mock
import org.mockito.kotlin.whenever
-
@SmallTest
@RunWith(AndroidJUnit4::class)
class InputGestureDataAdapterTest : SysuiTestCase() {
- private val kosmos = testKosmos().also { kosmos ->
- kosmos.userTracker = FakeUserTracker(onCreateCurrentUserContext = { kosmos.mockedContext })
- }
+ private val kosmos =
+ testKosmos().also { kosmos ->
+ kosmos.userTracker =
+ FakeUserTracker(onCreateCurrentUserContext = { kosmos.mockedContext })
+ }
private val adapter = kosmos.inputGestureDataAdapter
private val roleManager = kosmos.roleManager
private val packageManager: PackageManager = kosmos.packageManager
@@ -139,24 +141,40 @@ class InputGestureDataAdapterTest : SysuiTestCase() {
val inputGestureData = buildInputGestureDataForAppLaunchShortcut()
val internalGroups = adapter.toInternalGroupSources(listOf(inputGestureData))
- assertThat(internalGroups).containsExactly(
- InternalGroupsSource(
- type = ShortcutCategoryType.AppCategories,
- groups = listOf(
- InternalKeyboardShortcutGroup(
- label = APPLICATION_SHORTCUT_GROUP_LABEL,
- items = listOf(
- InternalKeyboardShortcutInfo(
- label = expectedShortcutLabelForFirstAppMatchingIntent,
- keycode = KEYCODE_A,
- modifiers = META_CTRL_ON or META_ALT_ON,
- isCustomShortcut = true
+ assertThat(internalGroups)
+ .containsExactly(
+ InternalGroupsSource(
+ type = ShortcutCategoryType.AppCategories,
+ groups =
+ listOf(
+ InternalKeyboardShortcutGroup(
+ label = APPLICATION_SHORTCUT_GROUP_LABEL,
+ items =
+ listOf(
+ InternalKeyboardShortcutInfo(
+ label =
+ expectedShortcutLabelForFirstAppMatchingIntent,
+ keycode = KEYCODE_A,
+ modifiers = META_CTRL_ON or META_ALT_ON,
+ isCustomShortcut = true,
+ )
+ ),
)
- )
- )
+ ),
+ )
+ )
+ }
+
+ @Test
+ fun keyGestureType_returnsTypeLaunchApplicationForAppLaunchShortcutCategory() =
+ kosmos.runTest {
+ assertThat(
+ adapter.getKeyGestureTypeForShortcut(
+ shortcutLabel = "Test Shortcut label",
+ shortcutCategoryType = ShortcutCategoryType.AppCategories,
)
)
- )
+ .isEqualTo(KEY_GESTURE_TYPE_LAUNCH_APPLICATION)
}
private fun setApiToRetrieveResolverActivity() {
@@ -169,11 +187,10 @@ class InputGestureDataAdapterTest : SysuiTestCase() {
.thenReturn(fakeActivityInfo)
}
-
private fun buildInputGestureDataForAppLaunchShortcut(
keyCode: Int = KEYCODE_A,
modifiers: Int = META_CTRL_ON or META_ALT_ON,
- appLaunchData: AppLaunchData = RoleData(TEST_ROLE)
+ appLaunchData: AppLaunchData = RoleData(TEST_ROLE),
): InputGestureData {
return InputGestureData.Builder()
.setTrigger(createKeyTrigger(keyCode, modifiers))
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperInputDeviceRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperInputDeviceRepositoryTest.kt
new file mode 100644
index 000000000000..ded2d223aab4
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperInputDeviceRepositoryTest.kt
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2024 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.systemui.keyboard.shortcut.data.repository
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.keyboard.shortcut.shortcutHelperInputDeviceRepository
+import com.android.systemui.keyboard.shortcut.shortcutHelperTestHelper
+import com.android.systemui.kosmos.collectLastValue
+import com.android.systemui.kosmos.runTest
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class ShortcutHelperInputDeviceRepositoryTest : SysuiTestCase() {
+ private val kosmos = testKosmos().useUnconfinedTestDispatcher()
+ private val testHelper = kosmos.shortcutHelperTestHelper
+ private val repo = kosmos.shortcutHelperInputDeviceRepository
+
+ @Test
+ fun activeInputDevice_nullByDefault() =
+ kosmos.runTest {
+ val activeInputDevice by collectLastValue(repo.activeInputDevice)
+
+ assertThat(activeInputDevice).isNull()
+ }
+
+ @Test
+ fun activeInputDevice_nonNullWhenHelperIsShown() =
+ kosmos.runTest {
+ val activeInputDevice by collectLastValue(repo.activeInputDevice)
+
+ testHelper.showFromActivity()
+
+ assertThat(activeInputDevice).isNotNull()
+ }
+
+ @Test
+ fun activeInputDevice_nullWhenHelperIsClosed() =
+ kosmos.runTest {
+ val activeInputDevice by collectLastValue(repo.activeInputDevice)
+
+ testHelper.showFromActivity()
+ testHelper.hideFromActivity()
+
+ assertThat(activeInputDevice).isNull()
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/source/TestShortcuts.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/source/TestShortcuts.kt
index 7dc7016e5e74..7c88d76f28bd 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/source/TestShortcuts.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/source/TestShortcuts.kt
@@ -43,11 +43,12 @@ import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.MultiTasking
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.System
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCommand
-import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCustomizationRequestInfo
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCustomizationRequestInfo.SingleShortcutCustomization
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutKey
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutSubCategory
import com.android.systemui.keyboard.shortcut.shared.model.shortcut
import com.android.systemui.keyboard.shortcut.shared.model.shortcutCategory
+import com.android.systemui.keyboard.shortcut.shared.model.shortcutCommand
import com.android.systemui.res.R
object TestShortcuts {
@@ -596,14 +597,27 @@ object TestShortcuts {
)
val allAppsShortcutAddRequest =
- ShortcutCustomizationRequestInfo.Add(
+ SingleShortcutCustomization.Add(
label = "Open apps list",
categoryType = System,
subCategoryLabel = "System controls",
)
+ val launchCalendarShortcutAddRequest =
+ SingleShortcutCustomization.Add(
+ label = "Calendar",
+ categoryType = ShortcutCategoryType.AppCategories,
+ subCategoryLabel = "Applications",
+ shortcutCommand =
+ shortcutCommand {
+ key("Ctrl")
+ key("Alt")
+ key("A")
+ },
+ )
+
val allAppsShortcutDeleteRequest =
- ShortcutCustomizationRequestInfo.Delete(
+ SingleShortcutCustomization.Delete(
label = "Open apps list",
categoryType = System,
subCategoryLabel = "System controls",
@@ -698,7 +712,7 @@ object TestShortcuts {
)
val standardAddShortcutRequest =
- ShortcutCustomizationRequestInfo.Add(
+ SingleShortcutCustomization.Add(
label = "Standard shortcut",
categoryType = System,
subCategoryLabel = "Standard subcategory",
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
index 5d1ce7c5ca05..929537dcf757 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
@@ -253,6 +253,16 @@ class NotificationShadeWindowViewTest : SysuiTestCase() {
verify(configurationForwarder).onConfigurationChanged(eq(config))
}
+ @Test
+ @EnableFlags(AConfigFlags.FLAG_SHADE_WINDOW_GOES_AROUND)
+ fun onMovedToDisplay_configForwarderSet_propagatesConfig() {
+ val config = Configuration()
+
+ underTest.onMovedToDisplay(1, config)
+
+ verify(configurationForwarder).dispatchOnMovedToDisplay(eq(1), eq(config))
+ }
+
private fun captureInteractionEventHandler() {
verify(underTest).setInteractionEventHandler(interactionEventHandlerCaptor.capture())
interactionEventHandler = interactionEventHandlerCaptor.value
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/data/repository/ShadeDisplaysRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/data/repository/ShadeDisplaysRepositoryTest.kt
index 096675962d80..007a0fb87953 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/data/repository/ShadeDisplaysRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/data/repository/ShadeDisplaysRepositoryTest.kt
@@ -16,17 +16,20 @@
package com.android.systemui.shade.data.repository
+import android.provider.Settings.Global.DEVELOPMENT_SHADE_DISPLAY_AWARENESS
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.coroutines.collectValues
+import com.android.systemui.display.data.repository.displayRepository
import com.android.systemui.kosmos.testScope
-import com.android.systemui.shade.display.ShadeDisplayPolicy
-import com.android.systemui.shade.display.SpecificDisplayIdPolicy
+import com.android.systemui.shade.display.AnyExternalShadeDisplayPolicy
+import com.android.systemui.shade.display.DefaultDisplayShadePolicy
+import com.android.systemui.shade.display.StatusBarTouchShadeDisplayPolicy
import com.android.systemui.testKosmos
+import com.android.systemui.util.settings.fakeGlobalSettings
import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith
@@ -36,54 +39,72 @@ import org.junit.runner.RunWith
class ShadeDisplaysRepositoryTest : SysuiTestCase() {
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
- private val defaultPolicy = SpecificDisplayIdPolicy(0)
-
- private val shadeDisplaysRepository =
- ShadeDisplaysRepositoryImpl(defaultPolicy, testScope.backgroundScope)
+ private val globalSettings = kosmos.fakeGlobalSettings
+ private val displayRepository = kosmos.displayRepository
+ private val defaultPolicy = DefaultDisplayShadePolicy()
+ private val policies = kosmos.shadeDisplayPolicies
+
+ private val underTest =
+ ShadeDisplaysRepositoryImpl(
+ globalSettings,
+ defaultPolicy,
+ testScope.backgroundScope,
+ policies,
+ )
@Test
fun policy_changing_propagatedFromTheLatestPolicy() =
testScope.runTest {
- val displayIds by collectValues(shadeDisplaysRepository.displayId)
- val policy1 = MutablePolicy()
- val policy2 = MutablePolicy()
+ val displayIds by collectValues(underTest.displayId)
assertThat(displayIds).containsExactly(0)
- shadeDisplaysRepository.policy.value = policy1
+ globalSettings.putString(DEVELOPMENT_SHADE_DISPLAY_AWARENESS, "any_external_display")
- policy1.sendDisplayId(1)
+ displayRepository.addDisplay(displayId = 1)
assertThat(displayIds).containsExactly(0, 1)
- policy1.sendDisplayId(2)
+ displayRepository.addDisplay(displayId = 2)
- assertThat(displayIds).containsExactly(0, 1, 2)
+ assertThat(displayIds).containsExactly(0, 1)
- shadeDisplaysRepository.policy.value = policy2
+ displayRepository.removeDisplay(displayId = 1)
- assertThat(displayIds).containsExactly(0, 1, 2, 0)
+ assertThat(displayIds).containsExactly(0, 1, 2)
- policy1.sendDisplayId(4)
+ globalSettings.putString(DEVELOPMENT_SHADE_DISPLAY_AWARENESS, "default_display")
- // Changes to the first policy don't affect the output now
assertThat(displayIds).containsExactly(0, 1, 2, 0)
+ }
+
+ @Test
+ fun policy_updatesBasedOnSettingValue_defaultDisplay() =
+ testScope.runTest {
+ val policy by collectLastValue(underTest.policy)
- policy2.sendDisplayId(5)
+ globalSettings.putString(DEVELOPMENT_SHADE_DISPLAY_AWARENESS, "default_display")
- assertThat(displayIds).containsExactly(0, 1, 2, 0, 5)
+ assertThat(policy).isInstanceOf(DefaultDisplayShadePolicy::class.java)
}
- private class MutablePolicy : ShadeDisplayPolicy {
- fun sendDisplayId(id: Int) {
- _displayId.value = id
+ @Test
+ fun policy_updatesBasedOnSettingValue_anyExternal() =
+ testScope.runTest {
+ val policy by collectLastValue(underTest.policy)
+
+ globalSettings.putString(DEVELOPMENT_SHADE_DISPLAY_AWARENESS, "any_external_display")
+
+ assertThat(policy).isInstanceOf(AnyExternalShadeDisplayPolicy::class.java)
}
- private val _displayId = MutableStateFlow(0)
- override val name: String
- get() = "mutable_policy"
+ @Test
+ fun policy_updatesBasedOnSettingValue_focusBased() =
+ testScope.runTest {
+ val policy by collectLastValue(underTest.policy)
- override val displayId: StateFlow<Int>
- get() = _displayId
- }
+ globalSettings.putString(DEVELOPMENT_SHADE_DISPLAY_AWARENESS, "status_bar_latest_touch")
+
+ assertThat(policy).isInstanceOf(StatusBarTouchShadeDisplayPolicy::class.java)
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/data/repository/ShadePrimaryDisplayCommandTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/data/repository/ShadePrimaryDisplayCommandTest.kt
index d584dc9ceef2..eeb3e6b31c69 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/data/repository/ShadePrimaryDisplayCommandTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/data/repository/ShadePrimaryDisplayCommandTest.kt
@@ -28,6 +28,7 @@ import com.android.systemui.shade.ShadePrimaryDisplayCommand
import com.android.systemui.shade.display.ShadeDisplayPolicy
import com.android.systemui.statusbar.commandline.commandRegistry
import com.android.systemui.testKosmos
+import com.android.systemui.util.settings.fakeGlobalSettings
import com.google.common.truth.StringSubject
import com.google.common.truth.Truth.assertThat
import java.io.PrintWriter
@@ -44,18 +45,17 @@ import org.junit.runner.RunWith
class ShadePrimaryDisplayCommandTest : SysuiTestCase() {
private val kosmos = testKosmos().useUnconfinedTestDispatcher()
private val testScope = kosmos.testScope
+ private val globalSettings = kosmos.fakeGlobalSettings
private val commandRegistry = kosmos.commandRegistry
private val displayRepository = kosmos.displayRepository
private val defaultPolicy = kosmos.defaultShadeDisplayPolicy
- private val policy1 = makePolicy("policy_1")
private val shadeDisplaysRepository = kosmos.shadeDisplaysRepository
+ private val policies = kosmos.shadeDisplayPolicies
private val pw = PrintWriter(StringWriter())
- private val policies =
- setOf(defaultPolicy, policy1, makePolicy("policy_2"), makePolicy("policy_3"))
-
private val underTest =
ShadePrimaryDisplayCommand(
+ globalSettings,
commandRegistry,
displayRepository,
shadeDisplaysRepository,
@@ -69,30 +69,16 @@ class ShadePrimaryDisplayCommandTest : SysuiTestCase() {
}
@Test
- fun commandDisplayOverride_updatesDisplayId() =
- testScope.runTest {
- val displayId by collectLastValue(shadeDisplaysRepository.displayId)
- assertThat(displayId).isEqualTo(Display.DEFAULT_DISPLAY)
-
- val newDisplayId = 2
- commandRegistry.onShellCommand(
- pw,
- arrayOf("shade_display_override", newDisplayId.toString()),
- )
-
- assertThat(displayId).isEqualTo(newDisplayId)
- }
-
- @Test
fun commandShadeDisplayOverride_resetsDisplayId() =
testScope.runTest {
val displayId by collectLastValue(shadeDisplaysRepository.displayId)
assertThat(displayId).isEqualTo(Display.DEFAULT_DISPLAY)
val newDisplayId = 2
+ displayRepository.addDisplay(displayId = newDisplayId)
commandRegistry.onShellCommand(
pw,
- arrayOf("shade_display_override", newDisplayId.toString()),
+ arrayOf("shade_display_override", "any_external_display"),
)
assertThat(displayId).isEqualTo(newDisplayId)
@@ -108,7 +94,10 @@ class ShadePrimaryDisplayCommandTest : SysuiTestCase() {
val newDisplayId = 2
displayRepository.addDisplay(displayId = newDisplayId)
- commandRegistry.onShellCommand(pw, arrayOf("shade_display_override", "any_external"))
+ commandRegistry.onShellCommand(
+ pw,
+ arrayOf("shade_display_override", "any_external_display"),
+ )
assertThat(displayId).isEqualTo(newDisplayId)
}
@@ -127,13 +116,14 @@ class ShadePrimaryDisplayCommandTest : SysuiTestCase() {
}
@Test
- fun policies_setsSpecificPolicy() =
+ fun policies_setsNewPolicy() =
testScope.runTest {
val policy by collectLastValue(shadeDisplaysRepository.policy)
+ val newPolicy = policies.last().name
- commandRegistry.onShellCommand(pw, arrayOf("shade_display_override", policy1.name))
+ commandRegistry.onShellCommand(pw, arrayOf("shade_display_override", newPolicy))
- assertThat(policy!!.name).isEqualTo(policy1.name)
+ assertThat(policy!!.name).isEqualTo(newPolicy)
}
private fun makePolicy(policyName: String): ShadeDisplayPolicy {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ConfigurationControllerImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ConfigurationControllerImplTest.kt
index 942ea65ec49e..e87077db8e75 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ConfigurationControllerImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ConfigurationControllerImplTest.kt
@@ -21,11 +21,13 @@ import android.content.res.Configuration.UI_MODE_NIGHT_NO
import android.content.res.Configuration.UI_MODE_NIGHT_YES
import android.content.res.Configuration.UI_MODE_TYPE_CAR
import android.os.LocaleList
+import android.view.Display
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener
import com.google.common.truth.Truth.assertThat
+import java.util.Locale
import org.junit.Before
import org.junit.Ignore
import org.junit.Test
@@ -34,7 +36,6 @@ import org.mockito.Mockito.doAnswer
import org.mockito.Mockito.mock
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
-import java.util.Locale
@RunWith(AndroidJUnit4::class)
@SmallTest
@@ -64,9 +65,11 @@ class ConfigurationControllerImplTest : SysuiTestCase() {
mConfigurationController.addCallback(listener2)
doAnswer {
- mConfigurationController.removeCallback(listener2)
- null
- }.`when`(listener).onThemeChanged()
+ mConfigurationController.removeCallback(listener2)
+ null
+ }
+ .`when`(listener)
+ .onThemeChanged()
mConfigurationController.notifyThemeChanged()
verify(listener).onThemeChanged()
@@ -208,7 +211,6 @@ class ConfigurationControllerImplTest : SysuiTestCase() {
assertThat(listener.maxBoundsChanged).isTrue()
}
-
@Test
fun localeListChanged_listenerNotified() {
val config = mContext.resources.configuration
@@ -289,7 +291,6 @@ class ConfigurationControllerImplTest : SysuiTestCase() {
assertThat(listener.orientationChanged).isTrue()
}
-
@Test
fun multipleUpdates_listenerNotifiedOfAll() {
val config = mContext.resources.configuration
@@ -313,6 +314,17 @@ class ConfigurationControllerImplTest : SysuiTestCase() {
}
@Test
+ fun onMovedToDisplay_dispatchedToChildren() {
+ val config = mContext.resources.configuration
+ val listener = createAndAddListener()
+
+ mConfigurationController.dispatchOnMovedToDisplay(newDisplayId = 1, config)
+
+ assertThat(listener.display).isEqualTo(1)
+ assertThat(listener.changedConfig).isEqualTo(config)
+ }
+
+ @Test
@Ignore("b/261408895")
fun equivalentConfigObject_listenerNotNotified() {
val config = mContext.resources.configuration
@@ -343,35 +355,49 @@ class ConfigurationControllerImplTest : SysuiTestCase() {
var localeListChanged = false
var layoutDirectionChanged = false
var orientationChanged = false
+ var display = Display.DEFAULT_DISPLAY
override fun onConfigChanged(newConfig: Configuration?) {
changedConfig = newConfig
}
+
override fun onDensityOrFontScaleChanged() {
densityOrFontScaleChanged = true
}
+
override fun onSmallestScreenWidthChanged() {
smallestScreenWidthChanged = true
}
+
override fun onMaxBoundsChanged() {
maxBoundsChanged = true
}
+
override fun onUiModeChanged() {
uiModeChanged = true
}
+
override fun onThemeChanged() {
themeChanged = true
}
+
override fun onLocaleListChanged() {
localeListChanged = true
}
+
override fun onLayoutDirectionChanged(isLayoutRtl: Boolean) {
layoutDirectionChanged = true
}
+
override fun onOrientationChanged(orientation: Int) {
orientationChanged = true
}
+ override fun onMovedToDisplay(newDisplayId: Int, newConfiguration: Configuration?) {
+ display = newDisplayId
+ changedConfig = newConfiguration
+ }
+
fun assertNoMethodsCalled() {
assertThat(densityOrFontScaleChanged).isFalse()
assertThat(smallestScreenWidthChanged).isFalse()
@@ -391,6 +417,7 @@ class ConfigurationControllerImplTest : SysuiTestCase() {
themeChanged = false
localeListChanged = false
layoutDirectionChanged = false
+ display = Display.DEFAULT_DISPLAY
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/data/repository/ConfigurationRepository.kt b/packages/SystemUI/src/com/android/systemui/common/ui/data/repository/ConfigurationRepository.kt
index 4d804d06fe87..747a2a9bd887 100644
--- a/packages/SystemUI/src/com/android/systemui/common/ui/data/repository/ConfigurationRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/ui/data/repository/ConfigurationRepository.kt
@@ -53,8 +53,12 @@ interface ConfigurationRepository {
val onConfigurationChange: Flow<Unit>
val scaleForResolution: Flow<Float>
+
val configurationValues: Flow<Configuration>
+ /** Emits the latest display this configuration controller has been moved to. */
+ val onMovedToDisplay: Flow<Int>
+
fun getResolutionScale(): Float
/** Convenience to context.resources.getDimensionPixelSize() */
@@ -117,6 +121,20 @@ constructor(
configurationController.addCallback(callback)
awaitClose { configurationController.removeCallback(callback) }
}
+ override val onMovedToDisplay: Flow<Int>
+ get() = conflatedCallbackFlow {
+ val callback =
+ object : ConfigurationController.ConfigurationListener {
+ override fun onMovedToDisplay(
+ newDisplayId: Int,
+ newConfiguration: Configuration?,
+ ) {
+ trySend(newDisplayId)
+ }
+ }
+ configurationController.addCallback(callback)
+ awaitClose { configurationController.removeCallback(callback) }
+ }
override val scaleForResolution: StateFlow<Float> =
onConfigurationChange
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/model/InternalShortcutModels.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/model/InternalShortcutModels.kt
index 3020e5dedd17..b59713696055 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/model/InternalShortcutModels.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/model/InternalShortcutModels.kt
@@ -49,7 +49,7 @@ data class InternalKeyboardShortcutGroup(
* @param isCustomShortcut If Shortcut is user customized or system defined.
*/
data class InternalKeyboardShortcutInfo(
- val label: String,
+ val label: String = "",
val keycode: Int,
val modifiers: Int,
val baseCharacter: Char = Char.MIN_VALUE,
@@ -60,4 +60,4 @@ data class InternalKeyboardShortcutInfo(
data class InternalGroupsSource(
val groups: List<InternalKeyboardShortcutGroup>,
val type: ShortcutCategoryType,
-) \ No newline at end of file
+)
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/AppLaunchDataRepository.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/AppLaunchDataRepository.kt
new file mode 100644
index 000000000000..b029b03ec8b8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/AppLaunchDataRepository.kt
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2024 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.systemui.keyboard.shortcut.data.repository
+
+import android.hardware.input.AppLaunchData
+import android.hardware.input.InputGestureData.KeyTrigger
+import android.hardware.input.InputManager
+import android.util.Log
+import android.view.InputDevice
+import com.android.systemui.Flags.shortcutHelperKeyGlyph
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCommand
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutKey
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
+import javax.inject.Inject
+
+@SysUISingleton
+class AppLaunchDataRepository
+@Inject
+constructor(
+ private val inputManager: InputManager,
+ @Background private val backgroundScope: CoroutineScope,
+ private val shortcutCategoriesUtils: ShortcutCategoriesUtils,
+ inputDeviceRepository: ShortcutHelperInputDeviceRepository,
+) {
+
+ private val shortcutCommandToAppLaunchDataMap:
+ StateFlow<Map<ShortcutCommandKey, AppLaunchData>> =
+ inputDeviceRepository.activeInputDevice
+ .map { inputDevice ->
+ if (inputDevice == null) {
+ emptyMap()
+ }
+ else{
+ buildCommandToAppLaunchDataMap(inputDevice)
+ }
+ }
+ .stateIn(
+ scope = backgroundScope,
+ started = SharingStarted.Eagerly,
+ initialValue = mapOf(),
+ )
+
+ fun getAppLaunchDataForShortcutWithCommand(shortcutCommand: ShortcutCommand): AppLaunchData? {
+ val shortcutCommandAsKey = ShortcutCommandKey(shortcutCommand)
+ return shortcutCommandToAppLaunchDataMap.value[shortcutCommandAsKey]
+ }
+
+ private fun buildCommandToAppLaunchDataMap(inputDevice: InputDevice):
+ Map<ShortcutCommandKey, AppLaunchData> {
+ val commandToAppLaunchDataMap =
+ mutableMapOf<ShortcutCommandKey, AppLaunchData>()
+ val appLaunchInputGestures = inputManager.appLaunchBookmarks
+ appLaunchInputGestures.forEach { inputGesture ->
+ val keyGlyphMap =
+ if (shortcutHelperKeyGlyph()) {
+ inputManager.getKeyGlyphMap(inputDevice.id)
+ } else null
+
+ val shortcutCommand =
+ shortcutCategoriesUtils.toShortcutCommand(
+ keyGlyphMap,
+ inputDevice.keyCharacterMap,
+ inputGesture.trigger as KeyTrigger,
+ )
+
+ if (shortcutCommand != null) {
+ commandToAppLaunchDataMap[ShortcutCommandKey(shortcutCommand)] =
+ inputGesture.action.appLaunchData()!!
+ } else {
+ Log.w(
+ TAG,
+ "could not get Shortcut Command. inputGesture: $inputGesture",
+ )
+ }
+ }
+
+ return commandToAppLaunchDataMap
+ }
+
+ private data class ShortcutCommandKey(val keys: List<ShortcutKey>) {
+ constructor(
+ shortcutCommand: ShortcutCommand
+ ) : this(shortcutCommand.keys.sortedBy { it.toString() })
+ }
+
+ private companion object {
+ private const val TAG = "AppLaunchDataRepository"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/CustomShortcutCategoriesRepository.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/CustomShortcutCategoriesRepository.kt
index 8afec04a621c..18ca877775df 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/CustomShortcutCategoriesRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/CustomShortcutCategoriesRepository.kt
@@ -30,48 +30,37 @@ import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.keyboard.shared.model.ShortcutCustomizationRequestResult
import com.android.systemui.keyboard.shortcut.shared.model.KeyCombination
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategory
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCustomizationRequestInfo
-import com.android.systemui.keyboard.shortcut.shared.model.ShortcutHelperState.Active
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCustomizationRequestInfo.SingleShortcutCustomization
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutKey
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
-import kotlinx.coroutines.withContext
import javax.inject.Inject
-import kotlin.coroutines.CoroutineContext
@SysUISingleton
class CustomShortcutCategoriesRepository
@Inject
constructor(
- stateRepository: ShortcutHelperStateRepository,
+ inputDeviceRepository: ShortcutHelperInputDeviceRepository,
@Background private val backgroundScope: CoroutineScope,
- @Background private val bgCoroutineContext: CoroutineContext,
private val shortcutCategoriesUtils: ShortcutCategoriesUtils,
private val inputGestureDataAdapter: InputGestureDataAdapter,
private val customInputGesturesRepository: CustomInputGesturesRepository,
- private val inputManager: InputManager
+ private val inputManager: InputManager,
+ private val appLaunchDataRepository: AppLaunchDataRepository,
) : ShortcutCategoriesRepository {
private val _selectedKeyCombination = MutableStateFlow<KeyCombination?>(null)
private val _shortcutBeingCustomized = mutableStateOf<ShortcutCustomizationRequestInfo?>(null)
- private val activeInputDevice =
- stateRepository.state.map {
- if (it is Active) {
- withContext(bgCoroutineContext) { inputManager.getInputDevice(it.deviceId) }
- } else {
- null
- }
- }
-
val pressedKeys =
_selectedKeyCombination
- .combine(activeInputDevice) { keyCombination, inputDevice ->
+ .combine(inputDeviceRepository.activeInputDevice) { keyCombination, inputDevice ->
if (inputDevice == null || keyCombination == null) {
return@combine emptyList()
} else {
@@ -105,8 +94,10 @@ constructor(
)
override val categories: Flow<List<ShortcutCategory>> =
- combine(activeInputDevice, customInputGesturesRepository.customInputGestures)
- { inputDevice, inputGestures ->
+ combine(
+ inputDeviceRepository.activeInputDevice,
+ customInputGesturesRepository.customInputGestures,
+ ) { inputDevice, inputGestures ->
if (inputDevice == null) {
emptyList()
} else {
@@ -147,10 +138,10 @@ constructor(
fun buildInputGestureDataForShortcutBeingCustomized(): InputGestureData? {
try {
return Builder()
- .addKeyGestureTypeFromShortcutLabel()
+ .addKeyGestureTypeForShortcutBeingCustomized()
.addTriggerFromSelectedKeyCombination()
+ .addAppLaunchDataFromShortcutBeingCustomized()
.build()
- // TODO(b/379648200) add app launch data after dynamic label/icon mapping implementation
} catch (e: IllegalArgumentException) {
Log.w(TAG, "could not add custom shortcut: $e")
return null
@@ -158,9 +149,10 @@ constructor(
}
private fun retrieveInputGestureDataForShortcutBeingDeleted(): InputGestureData? {
- val keyGestureType = getKeyGestureTypeFromShortcutBeingDeletedLabel()
- return customInputGesturesRepository.retrieveCustomInputGestures()
- .firstOrNull { it.action.keyGestureType() == keyGestureType }
+ val keyGestureType = getKeyGestureTypeForShortcutBeingCustomized()
+ return customInputGesturesRepository.retrieveCustomInputGestures().firstOrNull {
+ it.action.keyGestureType() == keyGestureType
+ }
}
suspend fun confirmAndSetShortcutCurrentlyBeingCustomized():
@@ -183,8 +175,8 @@ constructor(
return customInputGesturesRepository.resetAllCustomInputGestures()
}
- private fun Builder.addKeyGestureTypeFromShortcutLabel(): Builder {
- val keyGestureType = getKeyGestureTypeFromShortcutBeingCustomizedLabel()
+ private fun Builder.addKeyGestureTypeForShortcutBeingCustomized(): Builder {
+ val keyGestureType = getKeyGestureTypeForShortcutBeingCustomized()
if (keyGestureType == null) {
Log.w(
@@ -193,31 +185,28 @@ constructor(
)
return this
}
-
return setKeyGestureType(keyGestureType)
}
- @KeyGestureType
- private fun getKeyGestureTypeFromShortcutBeingCustomizedLabel(): Int? {
+ private fun Builder.addAppLaunchDataFromShortcutBeingCustomized(): Builder {
val shortcutBeingCustomized =
- getShortcutBeingCustomized() as? ShortcutCustomizationRequestInfo.Add
+ (_shortcutBeingCustomized.value as? SingleShortcutCustomization) ?: return this
- if (shortcutBeingCustomized == null) {
- Log.w(
- TAG,
- "Requested key gesture type from label but shortcut being customized is null",
- )
- return null
+ if (shortcutBeingCustomized.categoryType != ShortcutCategoryType.AppCategories) {
+ return this
}
- return inputGestureDataAdapter
- .getKeyGestureTypeFromShortcutLabel(shortcutBeingCustomized.label)
+ val defaultShortcutCommand = shortcutBeingCustomized.shortcutCommand
+
+ val appLaunchData =
+ appLaunchDataRepository.getAppLaunchDataForShortcutWithCommand(defaultShortcutCommand)
+
+ return if (appLaunchData == null) this else this.setAppLaunchData(appLaunchData)
}
@KeyGestureType
- private fun getKeyGestureTypeFromShortcutBeingDeletedLabel(): Int? {
- val shortcutBeingCustomized =
- getShortcutBeingCustomized() as? ShortcutCustomizationRequestInfo.Delete
+ private fun getKeyGestureTypeForShortcutBeingCustomized(): Int? {
+ val shortcutBeingCustomized = getShortcutBeingCustomized() as? SingleShortcutCustomization
if (shortcutBeingCustomized == null) {
Log.w(
@@ -227,8 +216,10 @@ constructor(
return null
}
- return inputGestureDataAdapter
- .getKeyGestureTypeFromShortcutLabel(shortcutBeingCustomized.label)
+ return inputGestureDataAdapter.getKeyGestureTypeForShortcut(
+ shortcutLabel = shortcutBeingCustomized.label,
+ shortcutCategoryType = shortcutBeingCustomized.categoryType,
+ )
}
private fun Builder.addTriggerFromSelectedKeyCombination(): Builder {
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/DefaultShortcutCategoriesRepository.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/DefaultShortcutCategoriesRepository.kt
index 5bb7cdd03b8f..db35d49e7598 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/DefaultShortcutCategoriesRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/DefaultShortcutCategoriesRepository.kt
@@ -16,7 +16,6 @@
package com.android.systemui.keyboard.shortcut.data.repository
-import android.hardware.input.InputManager
import android.view.KeyboardShortcutGroup
import android.view.KeyboardShortcutInfo
import com.android.systemui.dagger.SysUISingleton
@@ -36,29 +35,24 @@ import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.InputMethodEditor
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.MultiTasking
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.System
-import com.android.systemui.keyboard.shortcut.shared.model.ShortcutHelperState.Active
import javax.inject.Inject
-import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
-import kotlinx.coroutines.withContext
@SysUISingleton
class DefaultShortcutCategoriesRepository
@Inject
constructor(
@Background private val backgroundScope: CoroutineScope,
- @Background private val backgroundDispatcher: CoroutineDispatcher,
@SystemShortcuts private val systemShortcutsSource: KeyboardShortcutGroupsSource,
@MultitaskingShortcuts private val multitaskingShortcutsSource: KeyboardShortcutGroupsSource,
@AppCategoriesShortcuts private val appCategoriesShortcutsSource: KeyboardShortcutGroupsSource,
@InputShortcuts private val inputShortcutsSource: KeyboardShortcutGroupsSource,
@CurrentAppShortcuts private val currentAppShortcutsSource: KeyboardShortcutGroupsSource,
- private val inputManager: InputManager,
- stateRepository: ShortcutHelperStateRepository,
+ inputDeviceRepository: ShortcutHelperInputDeviceRepository,
shortcutCategoriesUtils: ShortcutCategoriesUtils,
) : ShortcutCategoriesRepository {
@@ -83,17 +77,8 @@ constructor(
),
)
- private val activeInputDevice =
- stateRepository.state.map {
- if (it is Active) {
- withContext(backgroundDispatcher) { inputManager.getInputDevice(it.deviceId) }
- } else {
- null
- }
- }
-
override val categories: Flow<List<ShortcutCategory>> =
- activeInputDevice
+ inputDeviceRepository.activeInputDevice
.map { inputDevice ->
if (inputDevice == null) {
return@map emptyList()
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/InputGestureDataAdapter.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/InputGestureDataAdapter.kt
index df7101e21cce..6e754a37ebca 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/InputGestureDataAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/InputGestureDataAdapter.kt
@@ -48,49 +48,54 @@ import com.android.systemui.res.R
import com.android.systemui.settings.UserTracker
import javax.inject.Inject
-
+/**
+ * Serves as a bridge for converting InputGestureData API Models to Shortcut Helper Data Layer
+ * Models and vice versa.
+ */
class InputGestureDataAdapter
@Inject
constructor(
private val userTracker: UserTracker,
private val inputGestureMaps: InputGestureMaps,
- private val context: Context
+ private val context: Context,
) {
private val userContext: Context
get() = userTracker.createCurrentUserContext(userTracker.userContext)
- fun toInternalGroupSources(
- inputGestures: List<InputGestureData>
- ): List<InternalGroupsSource> {
+ fun toInternalGroupSources(inputGestures: List<InputGestureData>): List<InternalGroupsSource> {
val ungroupedInternalGroupSources =
inputGestures.mapNotNull { gestureData ->
val keyTrigger = gestureData.trigger as KeyTrigger
val keyGestureType = gestureData.action.keyGestureType()
val appLaunchData: AppLaunchData? = gestureData.action.appLaunchData()
fetchGroupLabelByGestureType(keyGestureType)?.let { groupLabel ->
- toInternalKeyboardShortcutInfo(
- keyGestureType,
- keyTrigger,
- appLaunchData
- )?.let { internalKeyboardShortcutInfo ->
- val group =
- InternalKeyboardShortcutGroup(
- label = groupLabel,
- items = listOf(internalKeyboardShortcutInfo),
- )
-
- fetchShortcutCategoryTypeByGestureType(keyGestureType)?.let {
- InternalGroupsSource(groups = listOf(group), type = it)
+ toInternalKeyboardShortcutInfo(keyGestureType, keyTrigger, appLaunchData)
+ ?.let { internalKeyboardShortcutInfo ->
+ val group =
+ InternalKeyboardShortcutGroup(
+ label = groupLabel,
+ items = listOf(internalKeyboardShortcutInfo),
+ )
+
+ fetchShortcutCategoryTypeByGestureType(keyGestureType)?.let {
+ InternalGroupsSource(groups = listOf(group), type = it)
+ }
}
- }
}
}
return ungroupedInternalGroupSources
}
- fun getKeyGestureTypeFromShortcutLabel(label: String): Int? {
- return inputGestureMaps.shortcutLabelToKeyGestureTypeMap[label]
+ fun getKeyGestureTypeForShortcut(
+ shortcutLabel: String,
+ shortcutCategoryType: ShortcutCategoryType,
+ ): Int? {
+ if (shortcutCategoryType == ShortcutCategoryType.AppCategories) {
+ return KEY_GESTURE_TYPE_LAUNCH_APPLICATION
+ }
+ val result = inputGestureMaps.shortcutLabelToKeyGestureTypeMap[shortcutLabel]
+ return result
}
private fun toInternalKeyboardShortcutInfo(
@@ -104,16 +109,14 @@ constructor(
keycode = keyTrigger.keycode,
modifiers = keyTrigger.modifierState,
isCustomShortcut = true,
- icon = appLaunchData?.let { fetchShortcutIconByAppLaunchData(appLaunchData) }
+ icon = appLaunchData?.let { fetchShortcutIconByAppLaunchData(appLaunchData) },
)
}
return null
}
@SuppressLint("QueryPermissionsNeeded")
- private fun fetchShortcutIconByAppLaunchData(
- appLaunchData: AppLaunchData
- ): Icon? {
+ private fun fetchShortcutIconByAppLaunchData(appLaunchData: AppLaunchData): Icon? {
val intent = fetchIntentFromAppLaunchData(appLaunchData) ?: return null
val resolvedActivity = resolveSingleMatchingActivityFrom(intent)
@@ -132,7 +135,7 @@ constructor(
private fun fetchShortcutLabelByGestureType(
@KeyGestureType keyGestureType: Int,
- appLaunchData: AppLaunchData?
+ appLaunchData: AppLaunchData?,
): String? {
inputGestureMaps.gestureToInternalKeyboardShortcutInfoLabelResIdMap[keyGestureType]?.let {
return context.getString(it)
@@ -152,16 +155,14 @@ constructor(
return if (resolvedActivity == null) {
getIntentCategoryLabel(intent.selector?.categories?.iterator()?.next())
} else resolvedActivity.loadLabel(userContext.packageManager).toString()
-
}
@SuppressLint("QueryPermissionsNeeded")
private fun resolveSingleMatchingActivityFrom(intent: Intent): ActivityInfo? {
val packageManager = userContext.packageManager
- val resolvedActivity = intent.resolveActivityInfo(
- packageManager,
- /* flags= */ MATCH_DEFAULT_ONLY
- ) ?: return null
+ val resolvedActivity =
+ intent.resolveActivityInfo(packageManager, /* flags= */ MATCH_DEFAULT_ONLY)
+ ?: return null
val matchesMultipleActivities =
ResolverActivity::class.qualifiedName.equals(resolvedActivity.name)
@@ -172,22 +173,26 @@ constructor(
}
private fun getIntentCategoryLabel(category: String?): String? {
- val categoryLabelRes = when (category.toString()) {
- Intent.CATEGORY_APP_BROWSER -> R.string.keyboard_shortcut_group_applications_browser
- Intent.CATEGORY_APP_CONTACTS -> R.string.keyboard_shortcut_group_applications_contacts
- Intent.CATEGORY_APP_EMAIL -> R.string.keyboard_shortcut_group_applications_email
- Intent.CATEGORY_APP_CALENDAR -> R.string.keyboard_shortcut_group_applications_calendar
- Intent.CATEGORY_APP_MAPS -> R.string.keyboard_shortcut_group_applications_maps
- Intent.CATEGORY_APP_MUSIC -> R.string.keyboard_shortcut_group_applications_music
- Intent.CATEGORY_APP_MESSAGING -> R.string.keyboard_shortcut_group_applications_sms
- Intent.CATEGORY_APP_CALCULATOR -> R.string.keyboard_shortcut_group_applications_calculator
- else -> {
- Log.w(TAG, ("No label for app category $category"))
- null
+ val categoryLabelRes =
+ when (category.toString()) {
+ Intent.CATEGORY_APP_BROWSER -> R.string.keyboard_shortcut_group_applications_browser
+ Intent.CATEGORY_APP_CONTACTS ->
+ R.string.keyboard_shortcut_group_applications_contacts
+ Intent.CATEGORY_APP_EMAIL -> R.string.keyboard_shortcut_group_applications_email
+ Intent.CATEGORY_APP_CALENDAR ->
+ R.string.keyboard_shortcut_group_applications_calendar
+ Intent.CATEGORY_APP_MAPS -> R.string.keyboard_shortcut_group_applications_maps
+ Intent.CATEGORY_APP_MUSIC -> R.string.keyboard_shortcut_group_applications_music
+ Intent.CATEGORY_APP_MESSAGING -> R.string.keyboard_shortcut_group_applications_sms
+ Intent.CATEGORY_APP_CALCULATOR ->
+ R.string.keyboard_shortcut_group_applications_calculator
+ else -> {
+ Log.w(TAG, ("No label for app category $category"))
+ null
+ }
}
- }
- return if (categoryLabelRes == null){
+ return if (categoryLabelRes == null) {
return null
} else {
context.getString(categoryLabelRes)
@@ -196,41 +201,48 @@ constructor(
private fun fetchIntentFromAppLaunchData(appLaunchData: AppLaunchData): Intent? {
return when (appLaunchData) {
- is CategoryData -> Intent.makeMainSelectorActivity(
- /* selectorAction= */ ACTION_MAIN,
- /* selectorCategory= */ appLaunchData.category
- )
+ is CategoryData ->
+ Intent.makeMainSelectorActivity(
+ /* selectorAction= */ ACTION_MAIN,
+ /* selectorCategory= */ appLaunchData.category,
+ )
is RoleData -> getRoleLaunchIntent(appLaunchData.role)
- is ComponentData -> resolveComponentNameIntent(
- packageName = appLaunchData.packageName,
- className = appLaunchData.className
- )
+ is ComponentData ->
+ resolveComponentNameIntent(
+ packageName = appLaunchData.packageName,
+ className = appLaunchData.className,
+ )
else -> null
}
}
private fun resolveComponentNameIntent(packageName: String, className: String): Intent? {
- buildIntentFromComponentName(ComponentName(packageName, className))?.let { return it }
- buildIntentFromComponentName(ComponentName(
- userContext.packageManager.canonicalToCurrentPackageNames(arrayOf(packageName))[0],
- className
- ))?.let { return it }
+ buildIntentFromComponentName(ComponentName(packageName, className))?.let {
+ return it
+ }
+ buildIntentFromComponentName(
+ ComponentName(
+ userContext.packageManager
+ .canonicalToCurrentPackageNames(arrayOf(packageName))[0],
+ className,
+ )
+ )
+ ?.let {
+ return it
+ }
return null
}
private fun buildIntentFromComponentName(componentName: ComponentName): Intent? {
- try{
+ try {
val flags =
MATCH_DIRECT_BOOT_UNAWARE or MATCH_DIRECT_BOOT_AWARE or MATCH_UNINSTALLED_PACKAGES
// attempt to retrieve activity info to see if a NameNotFoundException is thrown.
userContext.packageManager.getActivityInfo(componentName, flags)
} catch (e: NameNotFoundException) {
- Log.w(
- TAG,
- "Unable to find activity info for componentName: $componentName"
- )
+ Log.w(TAG, "Unable to find activity info for componentName: $componentName")
return null
}
@@ -246,8 +258,9 @@ constructor(
val roleManager = userContext.getSystemService(RoleManager::class.java)!!
if (roleManager.isRoleAvailable(role)) {
roleManager.getDefaultApplication(role)?.let { rolePackage ->
- packageManager.getLaunchIntentForPackage(rolePackage)?.let { return it }
- ?: Log.w(TAG, "No launch intent for role $role")
+ packageManager.getLaunchIntentForPackage(rolePackage)?.let {
+ return it
+ } ?: Log.w(TAG, "No launch intent for role $role")
} ?: Log.w(TAG, "No default application for role $role, user= ${userContext.user}")
} else {
Log.w(TAG, "Role $role is not available.")
@@ -264,4 +277,4 @@ constructor(
private companion object {
private const val TAG = "InputGestureDataUtils"
}
-} \ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutCategoriesUtils.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutCategoriesUtils.kt
index 4a725ec8abad..cf5460fef0e2 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutCategoriesUtils.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutCategoriesUtils.kt
@@ -18,6 +18,7 @@ package com.android.systemui.keyboard.shortcut.data.repository
import android.content.Context
import android.graphics.drawable.Icon
+import android.hardware.input.InputGestureData.KeyTrigger
import android.hardware.input.InputManager
import android.hardware.input.KeyGlyphMap
import android.util.Log
@@ -137,8 +138,7 @@ constructor(
label = shortcutInfo.label,
icon = toShortcutIcon(keepIcon, shortcutInfo),
commands = listOf(shortcutCommand),
- isCustomizable =
- shortcutHelperExclusions.isShortcutCustomizable(shortcutInfo.label),
+ isCustomizable = shortcutHelperExclusions.isShortcutCustomizable(shortcutInfo.label),
)
}
@@ -158,6 +158,22 @@ constructor(
return ShortcutIcon(packageName = icon.resPackage, resourceId = icon.resId)
}
+ fun toShortcutCommand(
+ keyGlyphMap: KeyGlyphMap?,
+ keyCharacterMap: KeyCharacterMap,
+ keyTrigger: KeyTrigger,
+ ): ShortcutCommand? {
+ return toShortcutCommand(
+ keyGlyphMap = keyGlyphMap,
+ keyCharacterMap = keyCharacterMap,
+ info =
+ InternalKeyboardShortcutInfo(
+ keycode = keyTrigger.keycode,
+ modifiers = keyTrigger.modifierState,
+ ),
+ )
+ }
+
private fun toShortcutCommand(
keyGlyphMap: KeyGlyphMap?,
keyCharacterMap: KeyCharacterMap,
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperInputDeviceRepository.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperInputDeviceRepository.kt
new file mode 100644
index 000000000000..13613733c2bc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperInputDeviceRepository.kt
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2024 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.systemui.keyboard.shortcut.data.repository
+
+import android.hardware.input.InputManager
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutHelperState.Active
+import javax.inject.Inject
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.withContext
+
+class ShortcutHelperInputDeviceRepository
+@Inject
+constructor(
+ stateRepository: ShortcutHelperStateRepository,
+ @Background private val backgroundScope: CoroutineScope,
+ @Background private val bgCoroutineContext: CoroutineContext,
+ private val inputManager: InputManager,
+) {
+ val activeInputDevice =
+ stateRepository.state
+ .map {
+ if (it is Active) {
+ withContext(bgCoroutineContext) { inputManager.getInputDevice(it.deviceId) }
+ } else {
+ null
+ }
+ }
+ .stateIn(scope = backgroundScope, started = SharingStarted.Lazily, initialValue = null)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutCommand.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutCommand.kt
index c7e6b43b9624..d8bad2590280 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutCommand.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutCommand.kt
@@ -18,7 +18,10 @@ package com.android.systemui.keyboard.shortcut.shared.model
import androidx.annotation.DrawableRes
-data class ShortcutCommand(val keys: List<ShortcutKey>, val isCustom: Boolean = false)
+data class ShortcutCommand(
+ val keys: List<ShortcutKey> = emptyList(),
+ val isCustom: Boolean = false,
+)
class ShortcutCommandBuilder {
private val keys = mutableListOf<ShortcutKey>()
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutCustomizationRequestInfo.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutCustomizationRequestInfo.kt
index 095de41237cf..f183247bb355 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutCustomizationRequestInfo.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutCustomizationRequestInfo.kt
@@ -17,17 +17,27 @@
package com.android.systemui.keyboard.shortcut.shared.model
sealed interface ShortcutCustomizationRequestInfo {
- data class Add(
- val label: String = "",
- val categoryType: ShortcutCategoryType = ShortcutCategoryType.System,
- val subCategoryLabel: String = "",
- ) : ShortcutCustomizationRequestInfo
- data class Delete(
- val label: String = "",
- val categoryType: ShortcutCategoryType = ShortcutCategoryType.System,
- val subCategoryLabel: String = "",
- ) : ShortcutCustomizationRequestInfo
+ sealed interface SingleShortcutCustomization: ShortcutCustomizationRequestInfo {
+ val label: String
+ val categoryType: ShortcutCategoryType
+ val subCategoryLabel: String
+ val shortcutCommand: ShortcutCommand
+
+ data class Add(
+ override val label: String = "",
+ override val categoryType: ShortcutCategoryType = ShortcutCategoryType.System,
+ override val subCategoryLabel: String = "",
+ override val shortcutCommand: ShortcutCommand = ShortcutCommand(),
+ ) : SingleShortcutCustomization
+
+ data class Delete(
+ override val label: String = "",
+ override val categoryType: ShortcutCategoryType = ShortcutCategoryType.System,
+ override val subCategoryLabel: String = "",
+ override val shortcutCommand: ShortcutCommand = ShortcutCommand(),
+ ) : SingleShortcutCustomization
+ }
data object Reset : ShortcutCustomizationRequestInfo
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt
index af6f0cb2ec83..aea583d67289 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt
@@ -115,7 +115,6 @@ import androidx.compose.ui.util.fastForEach
import androidx.compose.ui.util.fastForEachIndexed
import com.android.compose.modifiers.thenIf
import com.android.compose.ui.graphics.painter.rememberDrawablePainter
-import com.android.systemui.keyboard.shortcut.shared.model.Shortcut as ShortcutModel
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCommand
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCustomizationRequestInfo
@@ -127,6 +126,7 @@ import com.android.systemui.keyboard.shortcut.ui.model.ShortcutCategoryUi
import com.android.systemui.keyboard.shortcut.ui.model.ShortcutsUiState
import com.android.systemui.res.R
import kotlinx.coroutines.delay
+import com.android.systemui.keyboard.shortcut.shared.model.Shortcut as ShortcutModel
@Composable
fun ShortcutHelper(
@@ -505,10 +505,10 @@ private fun EndSidePanel(
isCustomizing = isCustomizing and category.type.includeInCustomization,
onCustomizationRequested = { requestInfo ->
when (requestInfo) {
- is ShortcutCustomizationRequestInfo.Add ->
+ is ShortcutCustomizationRequestInfo.SingleShortcutCustomization.Add ->
onCustomizationRequested(requestInfo.copy(categoryType = category.type))
- is ShortcutCustomizationRequestInfo.Delete ->
+ is ShortcutCustomizationRequestInfo.SingleShortcutCustomization.Delete ->
onCustomizationRequested(requestInfo.copy(categoryType = category.type))
ShortcutCustomizationRequestInfo.Reset ->
@@ -568,12 +568,12 @@ private fun SubCategoryContainerDualPane(
isCustomizing = isCustomizing && shortcut.isCustomizable,
onCustomizationRequested = { requestInfo ->
when (requestInfo) {
- is ShortcutCustomizationRequestInfo.Add ->
+ is ShortcutCustomizationRequestInfo.SingleShortcutCustomization.Add ->
onCustomizationRequested(
requestInfo.copy(subCategoryLabel = subCategory.label)
)
- is ShortcutCustomizationRequestInfo.Delete ->
+ is ShortcutCustomizationRequestInfo.SingleShortcutCustomization.Delete ->
onCustomizationRequested(
requestInfo.copy(subCategoryLabel = subCategory.label)
)
@@ -644,12 +644,18 @@ private fun Shortcut(
isCustomizing = isCustomizing,
onAddShortcutRequested = {
onCustomizationRequested(
- ShortcutCustomizationRequestInfo.Add(label = shortcut.label)
+ ShortcutCustomizationRequestInfo.SingleShortcutCustomization.Add(
+ label = shortcut.label,
+ shortcutCommand = shortcut.commands.first(),
+ )
)
},
onDeleteShortcutRequested = {
onCustomizationRequested(
- ShortcutCustomizationRequestInfo.Delete(label = shortcut.label)
+ ShortcutCustomizationRequestInfo.SingleShortcutCustomization.Delete(
+ label = shortcut.label,
+ shortcutCommand = shortcut.commands.first(),
+ )
)
},
)
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutCustomizationViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutCustomizationViewModel.kt
index 92e25929fe4f..373eb250d61d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutCustomizationViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutCustomizationViewModel.kt
@@ -66,8 +66,9 @@ constructor(
}
fun onShortcutCustomizationRequested(requestInfo: ShortcutCustomizationRequestInfo) {
+ shortcutCustomizationInteractor.onCustomizationRequested(requestInfo)
when (requestInfo) {
- is ShortcutCustomizationRequestInfo.Add -> {
+ is ShortcutCustomizationRequestInfo.SingleShortcutCustomization.Add -> {
_shortcutCustomizationUiState.value =
AddShortcutDialog(
shortcutLabel = requestInfo.label,
@@ -75,12 +76,10 @@ constructor(
shortcutCustomizationInteractor.getDefaultCustomShortcutModifierKey(),
pressedKeys = emptyList(),
)
- shortcutCustomizationInteractor.onCustomizationRequested(requestInfo)
}
- is ShortcutCustomizationRequestInfo.Delete -> {
+ is ShortcutCustomizationRequestInfo.SingleShortcutCustomization.Delete -> {
_shortcutCustomizationUiState.value = DeleteShortcutDialog
- shortcutCustomizationInteractor.onCustomizationRequested(requestInfo)
}
ShortcutCustomizationRequestInfo.Reset -> {
diff --git a/packages/SystemUI/src/com/android/systemui/scrim/ScrimDrawable.java b/packages/SystemUI/src/com/android/systemui/scrim/ScrimDrawable.java
index 3c03d2830327..a7b51faaed57 100644
--- a/packages/SystemUI/src/com/android/systemui/scrim/ScrimDrawable.java
+++ b/packages/SystemUI/src/com/android/systemui/scrim/ScrimDrawable.java
@@ -35,7 +35,7 @@ import android.view.animation.DecelerateInterpolator;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.graphics.ColorUtils;
import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
-import static com.android.systemui.Flags.notificationShadeBlur;
+import com.android.systemui.window.flag.WindowBlurFlag;
/**
* Drawable used on SysUI scrims.
@@ -214,8 +214,9 @@ public class ScrimDrawable extends Drawable {
public void draw(@NonNull Canvas canvas) {
mPaint.setColor(mMainColor);
mPaint.setAlpha(mAlpha);
- if (notificationShadeBlur()) {
+ if (WindowBlurFlag.isEnabled()) {
// TODO(b/370555223): Match the alpha to the visual spec when it is finalized.
+ // TODO (b/381263600), wire this at ScrimController, move it to PrimaryBouncerTransition
mPaint.setAlpha((int) (0.5f * mAlpha));
}
if (mConcaveInfo != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java
index bf672be3c8d0..0a9aa9b49e8c 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java
@@ -169,6 +169,10 @@ public class NotificationShadeWindowView extends WindowRootView {
public void onMovedToDisplay(int displayId, Configuration config) {
super.onMovedToDisplay(displayId, config);
ShadeWindowGoesAround.isUnexpectedlyInLegacyMode();
+ ShadeTraceLogger.INSTANCE.logOnMovedToDisplay(displayId, config);
+ if (mConfigurationForwarder != null) {
+ mConfigurationForwarder.dispatchOnMovedToDisplay(displayId, config);
+ }
// When the window is moved we're only receiving a call to this method instead of the
// onConfigurationChange itself. Let's just trigegr a normal config change.
onConfigurationChanged(config);
@@ -177,6 +181,7 @@ public class NotificationShadeWindowView extends WindowRootView {
@Override
protected void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
+ ShadeTraceLogger.INSTANCE.logOnConfigChanged(newConfig);
if (mConfigurationForwarder != null) {
ShadeWindowGoesAround.isUnexpectedlyInLegacyMode();
mConfigurationForwarder.onConfigurationChanged(newConfig);
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadePrimaryDisplayCommand.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadePrimaryDisplayCommand.kt
index a54f6b9c6743..7bfe40c3d811 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadePrimaryDisplayCommand.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadePrimaryDisplayCommand.kt
@@ -16,23 +16,23 @@
package com.android.systemui.shade
-import android.view.Display
+import android.provider.Settings.Global.DEVELOPMENT_SHADE_DISPLAY_AWARENESS
import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.display.data.repository.DisplayRepository
import com.android.systemui.shade.data.repository.MutableShadeDisplaysRepository
import com.android.systemui.shade.display.ShadeDisplayPolicy
-import com.android.systemui.shade.display.SpecificDisplayIdPolicy
import com.android.systemui.statusbar.commandline.Command
import com.android.systemui.statusbar.commandline.CommandRegistry
+import com.android.systemui.util.settings.GlobalSettings
import java.io.PrintWriter
import javax.inject.Inject
-import kotlin.text.toIntOrNull
@SysUISingleton
class ShadePrimaryDisplayCommand
@Inject
constructor(
+ private val globalSettings: GlobalSettings,
private val commandRegistry: CommandRegistry,
private val displaysRepository: DisplayRepository,
private val positionRepository: MutableShadeDisplaysRepository,
@@ -45,7 +45,7 @@ constructor(
}
override fun help(pw: PrintWriter) {
- pw.println("shade_display_override (<displayId>|<policyName>) ")
+ pw.println("shade_display_override <policyName> ")
pw.println("Set the display which is holding the shade, or the policy that defines it.")
pw.println()
pw.println("shade_display_override policies")
@@ -56,9 +56,6 @@ constructor(
pw.println()
pw.println("shade_display_override (list|status) ")
pw.println("Lists available displays and which has the shade")
- pw.println()
- pw.println("shade_display_override any_external")
- pw.println("Moves the shade to the first not-default display available")
}
override fun execute(pw: PrintWriter, args: List<String>) {
@@ -74,28 +71,24 @@ constructor(
fun execute() {
when (val command = args.getOrNull(0)?.lowercase()) {
"reset" -> reset()
+ "policies" -> printPolicies()
"list",
"status" -> printStatus()
- "policies" -> printPolicies()
- "any_external" -> anyExternal()
null -> help(pw)
else -> parsePolicy(command)
}
}
private fun parsePolicy(policyIdentifier: String) {
- val displayId = policyIdentifier.toIntOrNull()
- when {
- displayId != null -> changeDisplay(displayId = displayId)
- policies.any { it.name == policyIdentifier } -> {
- positionRepository.policy.value = policies.first { it.name == policyIdentifier }
- }
- else -> help(pw)
+ if (policies.any { it.name == policyIdentifier }) {
+ globalSettings.putString(DEVELOPMENT_SHADE_DISPLAY_AWARENESS, policyIdentifier)
+ } else {
+ help(pw)
}
}
private fun reset() {
- positionRepository.policy.value = defaultPolicy
+ globalSettings.putString(DEVELOPMENT_SHADE_DISPLAY_AWARENESS, defaultPolicy.name)
pw.println("Reset shade display policy to default policy: ${defaultPolicy.name}")
}
@@ -117,30 +110,5 @@ constructor(
pw.println(if (currentPolicyName == it.name) " (Current policy)" else "")
}
}
-
- private fun anyExternal() {
- val anyExternalDisplay =
- displaysRepository.displays.value.firstOrNull {
- it.displayId != Display.DEFAULT_DISPLAY
- }
- if (anyExternalDisplay == null) {
- pw.println("No external displays available.")
- return
- }
- setDisplay(anyExternalDisplay.displayId)
- }
-
- private fun changeDisplay(displayId: Int) {
- if (displayId < 0) {
- pw.println("Error: display id should be positive integer")
- }
-
- setDisplay(displayId)
- }
-
- private fun setDisplay(id: Int) {
- positionRepository.policy.value = SpecificDisplayIdPolicy(id)
- pw.println("New shade primary display id is $id")
- }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeTraceLogger.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeTraceLogger.kt
new file mode 100644
index 000000000000..a596d4f64638
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeTraceLogger.kt
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2024 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.systemui.shade
+
+import android.content.res.Configuration
+import android.os.Trace
+import com.android.app.tracing.TraceUtils.traceAsync
+
+/**
+ * Centralized logging for shade-related events to a dedicated Perfetto track.
+ *
+ * Used by shade components to log events to a track named [TAG]. This consolidates shade-specific
+ * events into a single track for easier analysis in Perfetto, rather than scattering them across
+ * various threads' logs.
+ */
+object ShadeTraceLogger {
+ private const val TAG = "ShadeTraceLogger"
+
+ fun logOnMovedToDisplay(displayId: Int, config: Configuration) {
+ if (!Trace.isEnabled()) return
+ Trace.instantForTrack(
+ Trace.TRACE_TAG_APP,
+ TAG,
+ "onMovedToDisplay(displayId=$displayId, dpi=" + config.densityDpi + ")",
+ )
+ }
+
+ fun logOnConfigChanged(config: Configuration) {
+ if (!Trace.isEnabled()) return
+ Trace.instantForTrack(
+ Trace.TRACE_TAG_APP,
+ TAG,
+ "onConfigurationChanged(dpi=" + config.densityDpi + ")",
+ )
+ }
+
+ fun logMoveShadeWindowTo(displayId: Int) {
+ if (!Trace.isEnabled()) return
+ Trace.instantForTrack(Trace.TRACE_TAG_APP, TAG, "moveShadeWindowTo(displayId=$displayId)")
+ }
+
+ fun traceReparenting(r: () -> Unit) {
+ traceAsync(TAG, { "reparenting" }) { r() }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeDisplaysRepository.kt b/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeDisplaysRepository.kt
index 756241e9b071..af48231e0a99 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeDisplaysRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeDisplaysRepository.kt
@@ -16,17 +16,22 @@
package com.android.systemui.shade.data.repository
+import android.provider.Settings.Global.DEVELOPMENT_SHADE_DISPLAY_AWARENESS
import android.view.Display
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.shade.display.ShadeDisplayPolicy
+import com.android.systemui.util.settings.GlobalSettings
+import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.stateIn
/** Source of truth for the display currently holding the shade. */
@@ -38,7 +43,7 @@ interface ShadeDisplaysRepository {
/** Allows to change the policy that determines in which display the Shade window is visible. */
interface MutableShadeDisplaysRepository : ShadeDisplaysRepository {
/** Updates the policy to select where the shade is visible. */
- val policy: MutableStateFlow<ShadeDisplayPolicy>
+ val policy: StateFlow<ShadeDisplayPolicy>
}
/** Keeps the policy and propagates the display id for the shade from it. */
@@ -46,9 +51,27 @@ interface MutableShadeDisplaysRepository : ShadeDisplaysRepository {
@OptIn(ExperimentalCoroutinesApi::class)
class ShadeDisplaysRepositoryImpl
@Inject
-constructor(defaultPolicy: ShadeDisplayPolicy, @Background bgScope: CoroutineScope) :
- MutableShadeDisplaysRepository {
- override val policy = MutableStateFlow<ShadeDisplayPolicy>(defaultPolicy)
+constructor(
+ globalSettings: GlobalSettings,
+ defaultPolicy: ShadeDisplayPolicy,
+ @Background bgScope: CoroutineScope,
+ policies: Set<@JvmSuppressWildcards ShadeDisplayPolicy>,
+) : MutableShadeDisplaysRepository {
+
+ override val policy: StateFlow<ShadeDisplayPolicy> =
+ globalSettings
+ .observerFlow(DEVELOPMENT_SHADE_DISPLAY_AWARENESS)
+ .onStart { emit(Unit) }
+ .map {
+ val current = globalSettings.getString(DEVELOPMENT_SHADE_DISPLAY_AWARENESS)
+ for (policy in policies) {
+ if (policy.name == current) return@map policy
+ }
+ globalSettings.putString(DEVELOPMENT_SHADE_DISPLAY_AWARENESS, defaultPolicy.name)
+ return@map defaultPolicy
+ }
+ .distinctUntilChanged()
+ .stateIn(bgScope, SharingStarted.WhileSubscribed(), defaultPolicy)
override val displayId: StateFlow<Int> =
policy
diff --git a/packages/SystemUI/src/com/android/systemui/shade/display/SpecificDisplayIdPolicy.kt b/packages/SystemUI/src/com/android/systemui/shade/display/DefaultDisplayShadePolicy.kt
index d43aad70368e..3819c6ffae08 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/display/SpecificDisplayIdPolicy.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/display/DefaultDisplayShadePolicy.kt
@@ -21,12 +21,9 @@ import javax.inject.Inject
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
-/** Policy to specify a display id explicitly. */
-open class SpecificDisplayIdPolicy(id: Int) : ShadeDisplayPolicy {
- override val name: String = "display_${id}_policy"
+/** Policy to specify a default display explicitly. */
+class DefaultDisplayShadePolicy @Inject constructor() : ShadeDisplayPolicy {
+ override val name: String = "default_display"
- override val displayId: StateFlow<Int> = MutableStateFlow(id)
+ override val displayId: StateFlow<Int> = MutableStateFlow(Display.DEFAULT_DISPLAY)
}
-
-class DefaultDisplayShadePolicy @Inject constructor() :
- SpecificDisplayIdPolicy(Display.DEFAULT_DISPLAY)
diff --git a/packages/SystemUI/src/com/android/systemui/shade/display/ShadeDisplayPolicy.kt b/packages/SystemUI/src/com/android/systemui/shade/display/ShadeDisplayPolicy.kt
index bb96b0b3ce50..17b5e5b584b4 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/display/ShadeDisplayPolicy.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/display/ShadeDisplayPolicy.kt
@@ -23,6 +23,10 @@ import kotlinx.coroutines.flow.StateFlow
/** Describes the display the shade should be shown in. */
interface ShadeDisplayPolicy {
+ /**
+ * String used to identify each policy and used to set policy via adb command. This value must
+ * match a value defined in the SettingsLib shade_display_awareness_values string array.
+ */
val name: String
/** The display id the shade should be at, according to this policy. */
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractor.kt
index 08c03e28d596..8d536accaf76 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractor.kt
@@ -27,6 +27,8 @@ import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.scene.ui.view.WindowRootView
import com.android.systemui.shade.ShadeDisplayAware
+import com.android.systemui.shade.ShadeTraceLogger.logMoveShadeWindowTo
+import com.android.systemui.shade.ShadeTraceLogger.traceReparenting
import com.android.systemui.shade.data.repository.ShadeDisplaysRepository
import com.android.systemui.shade.shared.flag.ShadeWindowGoesAround
import com.android.systemui.util.kotlin.getOrNull
@@ -68,6 +70,7 @@ constructor(
/** Tries to move the shade. If anything wrong happens, fails gracefully without crashing. */
private suspend fun moveShadeWindowTo(destinationId: Int) {
Log.d(TAG, "Trying to move shade window to display with id $destinationId")
+ logMoveShadeWindowTo(destinationId)
// Why using the shade context here instead of the view's Display?
// The context's display is updated before the view one, so it is a better indicator of
// which display the shade is supposed to be at. The View display is updated after the first
@@ -83,7 +86,9 @@ constructor(
return
}
try {
- withContext(mainThreadContext) { reparentToDisplayId(id = destinationId) }
+ withContext(mainThreadContext) {
+ traceReparenting { reparentToDisplayId(id = destinationId) }
+ }
} catch (e: IllegalStateException) {
Log.e(
TAG,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
index 684466ad839b..3408f4ffd082 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
@@ -34,6 +34,7 @@ import androidx.dynamicanimation.animation.SpringAnimation
import androidx.dynamicanimation.animation.SpringForce
import com.android.app.animation.Interpolators
import com.android.systemui.Dumpable
+import com.android.systemui.Flags.notificationShadeBlur
import com.android.systemui.animation.ShadeInterpolation
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dump.DumpManager
@@ -53,7 +54,6 @@ import java.io.PrintWriter
import javax.inject.Inject
import kotlin.math.max
import kotlin.math.sign
-import com.android.systemui.Flags.notificationShadeBlur
/**
* Responsible for blurring the notification shade window, and applying a zoom effect to the
@@ -212,19 +212,13 @@ constructor(
shadeRadius = 0f
}
- var zoomOut = MathUtils.saturate(blurUtils.ratioOfBlurRadius(shadeRadius))
var blur = shadeRadius.toInt()
-
- if (inSplitShade) {
- zoomOut = 0f
- }
-
+ val zoomOut = blurRadiusToZoomOut(blurRadius = shadeRadius)
// Make blur be 0 if it is necessary to stop blur effect.
if (scrimsVisible) {
if (!notificationShadeBlur()) {
blur = 0
}
- zoomOut = 0f
}
if (!blurUtils.supportsBlursOnWindows()) {
@@ -237,24 +231,43 @@ constructor(
return Pair(blur, zoomOut)
}
+ private fun blurRadiusToZoomOut(blurRadius: Float): Float {
+ var zoomOut = MathUtils.saturate(blurUtils.ratioOfBlurRadius(blurRadius))
+ if (inSplitShade) {
+ zoomOut = 0f
+ }
+
+ if (scrimsVisible) {
+ zoomOut = 0f
+ }
+ return zoomOut
+ }
+
+ private val shouldBlurBeOpaque: Boolean
+ get() = if (notificationShadeBlur()) false else scrimsVisible && !blursDisabledForAppLaunch
+
/** Callback that updates the window blur value and is called only once per frame. */
@VisibleForTesting
val updateBlurCallback =
Choreographer.FrameCallback {
updateScheduled = false
- val (blur, zoomOut) = computeBlurAndZoomOut()
- val opaque = if (notificationShadeBlur()) false else scrimsVisible && !blursDisabledForAppLaunch
+ val (blur, zoomOutFromShadeRadius) = computeBlurAndZoomOut()
+ val opaque = shouldBlurBeOpaque
Trace.traceCounter(Trace.TRACE_TAG_APP, "shade_blur_radius", blur)
blurUtils.applyBlur(root.viewRootImpl, blur, opaque)
- lastAppliedBlur = blur
- wallpaperController.setNotificationShadeZoom(zoomOut)
- listeners.forEach {
- it.onWallpaperZoomOutChanged(zoomOut)
- it.onBlurRadiusChanged(blur)
- }
- notificationShadeWindowController.setBackgroundBlurRadius(blur)
+ onBlurApplied(blur, zoomOutFromShadeRadius)
}
+ private fun onBlurApplied(appliedBlurRadius: Int, zoomOutFromShadeRadius: Float) {
+ lastAppliedBlur = appliedBlurRadius
+ wallpaperController.setNotificationShadeZoom(zoomOutFromShadeRadius)
+ listeners.forEach {
+ it.onWallpaperZoomOutChanged(zoomOutFromShadeRadius)
+ it.onBlurRadiusChanged(appliedBlurRadius)
+ }
+ notificationShadeWindowController.setBackgroundBlurRadius(appliedBlurRadius)
+ }
+
/** Animate blurs when unlocking. */
private val keyguardStateCallback =
object : KeyguardStateController.Callback {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt
index 858cac111525..9c7af181284e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt
@@ -65,6 +65,13 @@ constructor(@Assisted private val context: Context) :
listeners.filterForEach({ this.listeners.contains(it) }) { it.onThemeChanged() }
}
+ override fun dispatchOnMovedToDisplay(newDisplayId: Int, newConfiguration: Configuration) {
+ val listeners = synchronized(this.listeners) { ArrayList(this.listeners) }
+ listeners.filterForEach({ this.listeners.contains(it) }) {
+ it.onMovedToDisplay(newDisplayId, newConfiguration)
+ }
+ }
+
override fun onConfigurationChanged(newConfig: Configuration) {
// Avoid concurrent modification exception
val listeners = synchronized(this.listeners) { ArrayList(this.listeners) }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationForwarder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationForwarder.kt
index 3fd46fc484a9..537e3e1893b9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationForwarder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationForwarder.kt
@@ -28,4 +28,13 @@ import android.content.res.Configuration
interface ConfigurationForwarder {
/** Should be called when a new configuration is received. */
fun onConfigurationChanged(newConfiguration: Configuration)
+
+ /**
+ * Should be called when the view associated to this configuration forwarded moved to another
+ * display, usually as a consequence of [View.onMovedToDisplay].
+ *
+ * For the default configuration forwarder (associated with the global configuration) this is
+ * never expected to be called.
+ */
+ fun dispatchOnMovedToDisplay(newDisplayId: Int, newConfiguration: Configuration)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ConfigurationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ConfigurationController.java
index 1bb4e8c66ef1..c77f6c1b8552 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ConfigurationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ConfigurationController.java
@@ -45,5 +45,6 @@ public interface ConfigurationController extends CallbackController<Configuratio
default void onLocaleListChanged() {}
default void onLayoutDirectionChanged(boolean isLayoutRtl) {}
default void onOrientationChanged(int orientation) {}
+ default void onMovedToDisplay(int newDisplayId, Configuration newConfiguration) {}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/window/flag/WindowBlurFlag.kt b/packages/SystemUI/src/com/android/systemui/window/flag/WindowBlurFlag.kt
new file mode 100644
index 000000000000..8b6c8601f5d2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/window/flag/WindowBlurFlag.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2024 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.systemui.window.flag
+
+import com.android.systemui.Flags
+
+/**
+ * Flag that controls whether the background surface is blurred or not while on the
+ * lockscreen/shade/bouncer. This makes the background of scrim, bouncer and few other opaque
+ * surfaces transparent so that we can see the blur effect on the background surface (wallpaper).
+ */
+object WindowBlurFlag {
+ /** Whether the blur is enabled or not */
+ @JvmStatic
+ val isEnabled
+ // Add flags here that require scrims/background surfaces to be transparent.
+ get() = Flags.notificationShadeBlur() || Flags.bouncerUiRevamp()
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/common/ui/data/repository/FakeConfigurationRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/common/ui/data/repository/FakeConfigurationRepository.kt
index 4d74254cf9f8..487049740079 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/common/ui/data/repository/FakeConfigurationRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/common/ui/data/repository/FakeConfigurationRepository.kt
@@ -17,6 +17,7 @@
package com.android.systemui.common.ui.data.repository
import android.content.res.Configuration
+import android.view.Display
import com.android.systemui.dagger.SysUISingleton
import dagger.Binds
import dagger.Module
@@ -25,6 +26,7 @@ import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.asStateFlow
@@ -46,6 +48,10 @@ class FakeConfigurationRepository @Inject constructor() : ConfigurationRepositor
override val configurationValues: Flow<Configuration> =
_configurationChangeValues.asSharedFlow()
+ private val _onMovedToDisplay = MutableStateFlow<Int>(Display.DEFAULT_DISPLAY)
+ override val onMovedToDisplay: StateFlow<Int>
+ get() = _onMovedToDisplay
+
private val _scaleForResolution = MutableStateFlow(1f)
override val scaleForResolution: Flow<Float> = _scaleForResolution.asStateFlow()
@@ -64,6 +70,10 @@ class FakeConfigurationRepository @Inject constructor() : ConfigurationRepositor
onAnyConfigurationChange()
}
+ fun onMovedToDisplay(newDisplayId: Int) {
+ _onMovedToDisplay.value = newDisplayId
+ }
+
fun setScaleForResolution(scale: Float) {
_scaleForResolution.value = scale
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt
index 4cb8a416124f..2641070a1a59 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt
@@ -22,12 +22,14 @@ import android.content.res.mainResources
import android.hardware.input.fakeInputManager
import android.view.windowManager
import com.android.systemui.broadcast.broadcastDispatcher
+import com.android.systemui.keyboard.shortcut.data.repository.AppLaunchDataRepository
import com.android.systemui.keyboard.shortcut.data.repository.CustomInputGesturesRepository
import com.android.systemui.keyboard.shortcut.data.repository.CustomShortcutCategoriesRepository
import com.android.systemui.keyboard.shortcut.data.repository.DefaultShortcutCategoriesRepository
import com.android.systemui.keyboard.shortcut.data.repository.InputGestureDataAdapter
import com.android.systemui.keyboard.shortcut.data.repository.InputGestureMaps
import com.android.systemui.keyboard.shortcut.data.repository.ShortcutCategoriesUtils
+import com.android.systemui.keyboard.shortcut.data.repository.ShortcutHelperInputDeviceRepository
import com.android.systemui.keyboard.shortcut.data.repository.ShortcutHelperStateRepository
import com.android.systemui.keyboard.shortcut.data.repository.ShortcutHelperTestHelper
import com.android.systemui.keyboard.shortcut.data.source.AppCategoriesShortcutsSource
@@ -47,6 +49,7 @@ import com.android.systemui.keyguard.data.repository.fakeCommandQueue
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.kosmos.backgroundCoroutineContext
+import com.android.systemui.kosmos.backgroundScope
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
import com.android.systemui.model.sysUiState
@@ -99,35 +102,54 @@ val Kosmos.defaultShortcutCategoriesRepository by
Kosmos.Fixture {
DefaultShortcutCategoriesRepository(
applicationCoroutineScope,
- testDispatcher,
shortcutHelperSystemShortcutsSource,
shortcutHelperMultiTaskingShortcutsSource,
shortcutHelperAppCategoriesShortcutsSource,
shortcutHelperInputShortcutsSource,
shortcutHelperCurrentAppShortcutsSource,
- fakeInputManager.inputManager,
- shortcutHelperStateRepository,
+ shortcutHelperInputDeviceRepository,
shortcutCategoriesUtils,
)
}
val Kosmos.inputGestureMaps by Kosmos.Fixture { InputGestureMaps(applicationContext) }
-val Kosmos.inputGestureDataAdapter by Kosmos.Fixture { InputGestureDataAdapter(userTracker, inputGestureMaps, applicationContext)}
+val Kosmos.inputGestureDataAdapter by
+ Kosmos.Fixture { InputGestureDataAdapter(userTracker, inputGestureMaps, applicationContext) }
val Kosmos.customInputGesturesRepository by
Kosmos.Fixture { CustomInputGesturesRepository(userTracker, testDispatcher) }
+val Kosmos.shortcutHelperInputDeviceRepository by
+ Kosmos.Fixture {
+ ShortcutHelperInputDeviceRepository(
+ shortcutHelperStateRepository,
+ backgroundScope,
+ backgroundCoroutineContext,
+ fakeInputManager.inputManager,
+ )
+ }
+
+val Kosmos.appLaunchDataRepository by
+ Kosmos.Fixture {
+ AppLaunchDataRepository(
+ fakeInputManager.inputManager,
+ backgroundScope,
+ shortcutCategoriesUtils,
+ shortcutHelperInputDeviceRepository,
+ )
+ }
+
val Kosmos.customShortcutCategoriesRepository by
Kosmos.Fixture {
CustomShortcutCategoriesRepository(
- shortcutHelperStateRepository,
+ shortcutHelperInputDeviceRepository,
applicationCoroutineScope,
- testDispatcher,
shortcutCategoriesUtils,
inputGestureDataAdapter,
customInputGesturesRepository,
fakeInputManager.inputManager,
+ appLaunchDataRepository,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/ShadeDisplaysRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/ShadeDisplaysRepositoryKosmos.kt
index 7488397dde1f..636cb37adf03 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/ShadeDisplaysRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/ShadeDisplaysRepositoryKosmos.kt
@@ -16,20 +16,53 @@
package com.android.systemui.shade.data.repository
-import android.view.Display
+import com.android.systemui.display.data.repository.displayRepository
+import com.android.systemui.keyguard.data.repository.keyguardRepository
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.testScope
+import com.android.systemui.shade.display.AnyExternalShadeDisplayPolicy
+import com.android.systemui.shade.display.DefaultDisplayShadePolicy
import com.android.systemui.shade.display.ShadeDisplayPolicy
-import com.android.systemui.shade.display.SpecificDisplayIdPolicy
+import com.android.systemui.shade.display.StatusBarTouchShadeDisplayPolicy
+import com.android.systemui.util.settings.fakeGlobalSettings
-val Kosmos.defaultShadeDisplayPolicy: ShadeDisplayPolicy by
- Kosmos.Fixture { SpecificDisplayIdPolicy(Display.DEFAULT_DISPLAY) }
+val Kosmos.defaultShadeDisplayPolicy: DefaultDisplayShadePolicy by
+ Kosmos.Fixture { DefaultDisplayShadePolicy() }
+
+val Kosmos.anyExternalShadeDisplayPolicy: AnyExternalShadeDisplayPolicy by
+ Kosmos.Fixture {
+ AnyExternalShadeDisplayPolicy(
+ bgScope = testScope.backgroundScope,
+ displayRepository = displayRepository,
+ )
+ }
+
+val Kosmos.focusBasedShadeDisplayPolicy: StatusBarTouchShadeDisplayPolicy by
+ Kosmos.Fixture {
+ StatusBarTouchShadeDisplayPolicy(
+ displayRepository = displayRepository,
+ backgroundScope = testScope.backgroundScope,
+ keyguardRepository = keyguardRepository,
+ shadeOnDefaultDisplayWhenLocked = false,
+ )
+ }
val Kosmos.shadeDisplaysRepository: MutableShadeDisplaysRepository by
Kosmos.Fixture {
ShadeDisplaysRepositoryImpl(
- defaultPolicy = defaultShadeDisplayPolicy,
bgScope = testScope.backgroundScope,
+ globalSettings = fakeGlobalSettings,
+ policies = shadeDisplayPolicies,
+ defaultPolicy = defaultShadeDisplayPolicy,
+ )
+ }
+
+val Kosmos.shadeDisplayPolicies: Set<ShadeDisplayPolicy> by
+ Kosmos.Fixture {
+ setOf(
+ defaultShadeDisplayPolicy,
+ anyExternalShadeDisplayPolicy,
+ focusBasedShadeDisplayPolicy,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/FakeConfigurationController.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/FakeConfigurationController.kt
index 32191277c94a..13673d16bf3c 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/FakeConfigurationController.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/FakeConfigurationController.kt
@@ -27,6 +27,10 @@ class FakeConfigurationController @Inject constructor() :
listeners.forEach { it.onConfigChanged(newConfiguration) }
}
+ override fun dispatchOnMovedToDisplay(newDisplayId: Int, newConfiguration: Configuration) {
+ listeners.forEach { it.onMovedToDisplay(newDisplayId, newConfiguration) }
+ }
+
override fun notifyThemeChanged() {
listeners.forEach { it.onThemeChanged() }
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeConfigurationController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeConfigurationController.java
index 111c40d49efc..9cf25e8df727 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeConfigurationController.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeConfigurationController.java
@@ -16,6 +16,8 @@ package com.android.systemui.utils.leaks;
import android.content.res.Configuration;
+import androidx.annotation.NonNull;
+
import com.android.systemui.statusbar.policy.ConfigurationController;
public class FakeConfigurationController
@@ -43,4 +45,10 @@ public class FakeConfigurationController
public String getNightModeName() {
return "undefined";
}
+
+ @Override
+ public void dispatchOnMovedToDisplay(int newDisplayId,
+ @NonNull Configuration newConfiguration) {
+
+ }
}
diff --git a/services/core/java/com/android/server/display/DisplayDevice.java b/services/core/java/com/android/server/display/DisplayDevice.java
index 4bbddaeb53a2..2bdb5c25d0d5 100644
--- a/services/core/java/com/android/server/display/DisplayDevice.java
+++ b/services/core/java/com/android/server/display/DisplayDevice.java
@@ -306,6 +306,14 @@ abstract class DisplayDevice {
}
/**
+ * Returns if the display should only mirror another display rather than showing other content
+ * until it is destroyed.
+ */
+ public boolean shouldOnlyMirror() {
+ return false;
+ }
+
+ /**
* Sets the display layer stack while in a transaction.
*/
public final void setLayerStackLocked(SurfaceControl.Transaction t, int layerStack,
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 0b633bd9c549..bad5b8b9567a 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -199,7 +199,6 @@ import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.Consumer;
-
/**
* Manages attached displays.
* <p>
@@ -906,6 +905,16 @@ public final class DisplayManagerService extends SystemService {
}
}
+ @VisibleForTesting
+ ContentObserver getSettingsObserver() {
+ return mSettingsObserver;
+ }
+
+ @VisibleForTesting
+ boolean shouldMirrorBuiltInDisplay() {
+ return mMirrorBuiltInDisplay;
+ }
+
DisplayNotificationManager getDisplayNotificationManager() {
return mDisplayNotificationManager;
}
@@ -1230,11 +1239,6 @@ public final class DisplayManagerService extends SystemService {
}
private void updateMirrorBuiltInDisplaySettingLocked() {
- if (!mFlags.isDisplayContentModeManagementEnabled()) {
- Slog.e(TAG, "MirrorBuiltInDisplay setting shouldn't be updated when the flag is off.");
- return;
- }
-
synchronized (mSyncRoot) {
ContentResolver resolver = mContext.getContentResolver();
final boolean mirrorBuiltInDisplay = Settings.Secure.getIntForUser(resolver,
@@ -1243,6 +1247,9 @@ public final class DisplayManagerService extends SystemService {
return;
}
mMirrorBuiltInDisplay = mirrorBuiltInDisplay;
+ if (mFlags.isDisplayContentModeManagementEnabled()) {
+ mLogicalDisplayMapper.forEachLocked(this::updateCanHostTasksIfNeededLocked);
+ }
}
}
@@ -2308,6 +2315,10 @@ public final class DisplayManagerService extends SystemService {
mDisplayBrightnesses.append(displayId,
new BrightnessPair(brightnessDefault, brightnessDefault));
+ if (mFlags.isDisplayContentModeManagementEnabled()) {
+ updateCanHostTasksIfNeededLocked(display);
+ }
+
DisplayManagerGlobal.invalidateLocalDisplayInfoCaches();
}
@@ -2630,6 +2641,12 @@ public final class DisplayManagerService extends SystemService {
}
}
+ private void updateCanHostTasksIfNeededLocked(LogicalDisplay display) {
+ if (display.setCanHostTasksLocked(!mMirrorBuiltInDisplay)) {
+ sendDisplayEventIfEnabledLocked(display, DisplayManagerGlobal.EVENT_DISPLAY_CHANGED);
+ }
+ }
+
private void recordTopInsetLocked(@Nullable LogicalDisplay d) {
// We must only persist the inset after boot has completed, otherwise we will end up
// overwriting the persisted value before the masking flag has been loaded from the
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 9387e9ede532..730b95cf1eac 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -2235,7 +2235,6 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
setReportedScreenState(REPORTED_TO_POLICY_SCREEN_TURNING_OFF);
blockScreenOff();
mWindowManagerPolicy.screenTurningOff(mDisplayId, mPendingScreenOffUnblocker);
- unblockScreenOff();
} else if (mPendingScreenOffUnblocker != null) {
// Abort doing the state change until screen off is unblocked.
return false;
diff --git a/services/core/java/com/android/server/display/ExternalDisplayPolicy.java b/services/core/java/com/android/server/display/ExternalDisplayPolicy.java
index f34d2cc6e684..519763a1c3db 100644
--- a/services/core/java/com/android/server/display/ExternalDisplayPolicy.java
+++ b/services/core/java/com/android/server/display/ExternalDisplayPolicy.java
@@ -217,8 +217,10 @@ class ExternalDisplayPolicy {
mExternalDisplayStatsService.onDisplayConnected(logicalDisplay);
- if ((Build.IS_ENG || Build.IS_USERDEBUG)
- && SystemProperties.getBoolean(ENABLE_ON_CONNECT, false)) {
+ if (((Build.IS_ENG || Build.IS_USERDEBUG)
+ && SystemProperties.getBoolean(ENABLE_ON_CONNECT, false))
+ || (mFlags.isDisplayContentModeManagementEnabled()
+ && logicalDisplay.canHostTasksLocked())) {
Slog.w(TAG, "External display is enabled by default, bypassing user consent.");
mInjector.sendExternalDisplayEventLocked(logicalDisplay, EVENT_DISPLAY_CONNECTED);
return;
diff --git a/services/core/java/com/android/server/display/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java
index 1de9c9589fb9..f9d413732e3e 100644
--- a/services/core/java/com/android/server/display/LogicalDisplay.java
+++ b/services/core/java/com/android/server/display/LogicalDisplay.java
@@ -17,6 +17,7 @@
package com.android.server.display;
import static com.android.server.display.DisplayDeviceInfo.TOUCH_NONE;
+import static com.android.server.display.layout.Layout.Display.POSITION_REAR;
import static com.android.server.wm.utils.DisplayInfoOverrides.WM_OVERRIDE_FIELDS;
import static com.android.server.wm.utils.DisplayInfoOverrides.copyDisplayInfoFields;
@@ -227,6 +228,8 @@ final class LogicalDisplay {
*/
private final boolean mIsAnisotropyCorrectionEnabled;
+ private boolean mCanHostTasks;
+
LogicalDisplay(int displayId, int layerStack, DisplayDevice primaryDisplayDevice) {
this(displayId, layerStack, primaryDisplayDevice, false, false);
}
@@ -245,6 +248,7 @@ final class LogicalDisplay {
mBaseDisplayInfo.thermalBrightnessThrottlingDataId = mThermalBrightnessThrottlingDataId;
mIsAnisotropyCorrectionEnabled = isAnisotropyCorrectionEnabled;
mAlwaysRotateDisplayDeviceEnabled = isAlwaysRotateDisplayDeviceEnabled;
+ mCanHostTasks = (mDisplayId == Display.DEFAULT_DISPLAY);
}
public void setDevicePositionLocked(int position) {
@@ -568,6 +572,7 @@ final class LogicalDisplay {
mBaseDisplayInfo.layoutLimitedRefreshRate = mLayoutLimitedRefreshRate;
mBaseDisplayInfo.thermalRefreshRateThrottling = mThermalRefreshRateThrottling;
mBaseDisplayInfo.thermalBrightnessThrottlingDataId = mThermalBrightnessThrottlingDataId;
+ mBaseDisplayInfo.canHostTasks = mCanHostTasks;
mPrimaryDisplayDeviceInfo = deviceInfo;
mInfo.set(null);
@@ -927,6 +932,61 @@ final class LogicalDisplay {
return handleLogicalDisplayChangedLocked;
}
+ boolean canHostTasksLocked() {
+ return mCanHostTasks;
+ }
+
+ /**
+ * Sets whether the display can host tasks.
+ *
+ * @param canHostTasks Whether the display can host tasks according to the user's setting.
+ * @return Whether Display Manager should call sendDisplayEventIfEnabledLocked().
+ */
+ boolean setCanHostTasksLocked(boolean canHostTasks) {
+ canHostTasks = validateCanHostTasksLocked(canHostTasks);
+ if (mBaseDisplayInfo.canHostTasks == canHostTasks) {
+ return false;
+ }
+
+ mCanHostTasks = canHostTasks;
+ mBaseDisplayInfo.canHostTasks = canHostTasks;
+ mInfo.set(null);
+ return true;
+ }
+
+ /**
+ * Checks whether the display's ability to host tasks should be determined independently of the
+ * user's setting value. If so, returns the actual validated value based on the display's
+ * usage; otherwise, returns the user's setting value.
+ *
+ * @param canHostTasks Whether the display can host tasks according to the user's setting.
+ * @return Whether the display can actually host task after configuration.
+ */
+ private boolean validateCanHostTasksLocked(boolean canHostTasks) {
+ // The default display can always host tasks.
+ if (getDisplayIdLocked() == Display.DEFAULT_DISPLAY) {
+ return true;
+ }
+
+ // The display that should only mirror can never host tasks.
+ if (mPrimaryDisplayDevice.shouldOnlyMirror()) {
+ return false;
+ }
+
+ // The display that has its own content can always host tasks.
+ final boolean isRearDisplay = getDevicePositionLocked() == POSITION_REAR;
+ final boolean ownContent =
+ ((mPrimaryDisplayDevice.getDisplayDeviceInfoLocked().flags
+ & DisplayDeviceInfo.FLAG_OWN_CONTENT_ONLY)
+ != 0)
+ || isRearDisplay;
+ if (ownContent) {
+ return true;
+ }
+
+ return canHostTasks;
+ }
+
/**
* Swap the underlying {@link DisplayDevice} with the specified LogicalDisplay.
*
diff --git a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
index f14e452ab8d3..558afd1da380 100644
--- a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
@@ -307,13 +307,14 @@ public class VirtualDisplayAdapter extends DisplayAdapter {
private VirtualDisplayDevice removeVirtualDisplayDeviceLocked(IBinder appToken) {
if (getFeatureFlags().isVirtualDisplayLimitEnabled()) {
- int ownerUid = mOwnerUids.get(appToken);
- int noOfDevices = mNoOfDevicesPerPackage.get(ownerUid, /* valueIfKeyNotFound= */ 0);
- if (noOfDevices <= 1) {
- mNoOfDevicesPerPackage.delete(ownerUid);
- mOwnerUids.remove(appToken);
- } else {
- mNoOfDevicesPerPackage.put(ownerUid, noOfDevices - 1);
+ Integer ownerUid = mOwnerUids.remove(appToken);
+ if (ownerUid != null) {
+ int noOfDevices = mNoOfDevicesPerPackage.get(ownerUid, /* valueIfKeyNotFound= */ 0);
+ if (noOfDevices <= 1) {
+ mNoOfDevicesPerPackage.delete(ownerUid);
+ } else {
+ mNoOfDevicesPerPackage.put(ownerUid, noOfDevices - 1);
+ }
}
}
return mVirtualDisplayDevices.remove(appToken);
@@ -500,6 +501,11 @@ public class VirtualDisplayAdapter extends DisplayAdapter {
mPendingChanges = 0;
}
+ @Override
+ public boolean shouldOnlyMirror() {
+ return mProjection != null || ((mFlags & VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR) != 0);
+ }
+
public void setSurfaceLocked(Surface surface) {
if (!mStopped && mSurface != surface) {
if (mDisplayState == Display.STATE_ON
diff --git a/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java b/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java
index b076aebe5210..4eaa11bac016 100644
--- a/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java
+++ b/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java
@@ -432,7 +432,8 @@ class DeferredDisplayUpdater {
|| !first.thermalRefreshRateThrottling.contentEquals(
second.thermalRefreshRateThrottling)
|| !Objects.equals(first.thermalBrightnessThrottlingDataId,
- second.thermalBrightnessThrottlingDataId)) {
+ second.thermalBrightnessThrottlingDataId)
+ || first.canHostTasks != second.canHostTasks) {
diff |= DIFF_NOT_WM_DEFERRABLE;
}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
index 724f083018f2..f96294ed4ca8 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
@@ -30,6 +30,7 @@ import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_D
import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION;
import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_TRUSTED;
import static android.provider.Settings.Global.DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS;
+import static android.provider.Settings.Secure.MIRROR_BUILT_IN_DISPLAY;
import static android.view.ContentRecordingSession.RECORD_CONTENT_DISPLAY;
import static android.view.ContentRecordingSession.RECORD_CONTENT_TASK;
import static android.view.Display.HdrCapabilities.HDR_TYPE_INVALID;
@@ -88,6 +89,7 @@ import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.content.pm.UserInfo;
import android.content.res.Resources;
+import android.database.ContentObserver;
import android.graphics.Insets;
import android.graphics.Rect;
import android.hardware.Sensor;
@@ -3830,6 +3832,96 @@ public class DisplayManagerServiceTest {
assertThat(callback.receivedEvents()).isEmpty();
}
+ @Test
+ public void testMirrorBuiltInDisplay_flagEnabled() {
+ when(mMockFlags.isDisplayContentModeManagementEnabled()).thenReturn(true);
+ Settings.Secure.putInt(mContext.getContentResolver(), MIRROR_BUILT_IN_DISPLAY, 0);
+
+ DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
+ displayManager.systemReady(/* safeMode= */ false);
+ assertThat(displayManager.shouldMirrorBuiltInDisplay()).isFalse();
+
+ Settings.Secure.putInt(mContext.getContentResolver(), MIRROR_BUILT_IN_DISPLAY, 1);
+ final ContentObserver observer = displayManager.getSettingsObserver();
+ observer.onChange(false, Settings.Secure.getUriFor(MIRROR_BUILT_IN_DISPLAY));
+ assertThat(displayManager.shouldMirrorBuiltInDisplay()).isTrue();
+ }
+
+ @Test
+ public void testMirrorBuiltInDisplay_flagDisabled() {
+ when(mMockFlags.isDisplayContentModeManagementEnabled()).thenReturn(false);
+ Settings.Secure.putInt(mContext.getContentResolver(), MIRROR_BUILT_IN_DISPLAY, 0);
+
+ DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
+ displayManager.systemReady(/* safeMode= */ false);
+ assertThat(displayManager.shouldMirrorBuiltInDisplay()).isFalse();
+
+ Settings.Secure.putInt(mContext.getContentResolver(), MIRROR_BUILT_IN_DISPLAY, 1);
+ final ContentObserver observer = displayManager.getSettingsObserver();
+ observer.onChange(false, Settings.Secure.getUriFor(MIRROR_BUILT_IN_DISPLAY));
+ assertThat(displayManager.shouldMirrorBuiltInDisplay()).isFalse();
+ }
+
+ @Test
+ public void testShouldNotNotifyDefaultDisplayChanges_whenMirrorBuiltInDisplayChanges() {
+ when(mMockFlags.isDisplayContentModeManagementEnabled()).thenReturn(true);
+ Settings.Secure.putInt(mContext.getContentResolver(), MIRROR_BUILT_IN_DISPLAY, 0);
+
+ DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
+ displayManager.systemReady(/* safeMode= */ false);
+
+ DisplayManagerService.BinderService displayManagerBinderService =
+ displayManager.new BinderService();
+ Handler handler = displayManager.getDisplayHandler();
+ waitForIdleHandler(handler);
+
+ FakeDisplayManagerCallback callback = new FakeDisplayManagerCallback();
+ displayManagerBinderService.registerCallbackWithEventMask(
+ callback, STANDARD_DISPLAY_EVENTS);
+ waitForIdleHandler(handler);
+
+ // Create a default display device
+ createFakeDisplayDevice(displayManager, new float[] {60f}, Display.TYPE_INTERNAL);
+
+ Settings.Secure.putInt(mContext.getContentResolver(), MIRROR_BUILT_IN_DISPLAY, 1);
+ final ContentObserver observer = displayManager.getSettingsObserver();
+ observer.onChange(false, Settings.Secure.getUriFor(MIRROR_BUILT_IN_DISPLAY));
+ waitForIdleHandler(handler);
+
+ assertThat(callback.receivedEvents()).doesNotContain(EVENT_DISPLAY_CHANGED);
+ }
+
+ @Test
+ public void testShouldNotifyNonDefaultDisplayChanges_whenMirrorBuiltInDisplayChanges() {
+ when(mMockFlags.isDisplayContentModeManagementEnabled()).thenReturn(true);
+ Settings.Secure.putInt(mContext.getContentResolver(), MIRROR_BUILT_IN_DISPLAY, 0);
+
+ DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
+ displayManager.systemReady(/* safeMode= */ false);
+
+ DisplayManagerService.BinderService displayManagerBinderService =
+ displayManager.new BinderService();
+ Handler handler = displayManager.getDisplayHandler();
+ waitForIdleHandler(handler);
+
+ FakeDisplayManagerCallback callback = new FakeDisplayManagerCallback();
+ displayManagerBinderService.registerCallbackWithEventMask(
+ callback, STANDARD_DISPLAY_EVENTS);
+ waitForIdleHandler(handler);
+
+ // Create a default display device
+ createFakeDisplayDevice(displayManager, new float[] {60f}, Display.TYPE_INTERNAL);
+ // Create a non-default display device
+ createFakeDisplayDevice(displayManager, new float[] {60f}, Display.TYPE_EXTERNAL);
+
+ Settings.Secure.putInt(mContext.getContentResolver(), MIRROR_BUILT_IN_DISPLAY, 1);
+ final ContentObserver observer = displayManager.getSettingsObserver();
+ observer.onChange(false, Settings.Secure.getUriFor(MIRROR_BUILT_IN_DISPLAY));
+ waitForIdleHandler(handler);
+
+ assertThat(callback.receivedEvents()).contains(EVENT_DISPLAY_CHANGED);
+ }
+
private void initDisplayPowerController(DisplayManagerInternal localService) {
localService.initPowerManagement(new DisplayManagerInternal.DisplayPowerCallbacks() {
@Override
diff --git a/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayTest.java b/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayTest.java
index 241dc10747ac..1a0ab252f128 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayTest.java
@@ -609,4 +609,69 @@ public class LogicalDisplayTest {
DisplayInfo info = mLogicalDisplay.getDisplayInfoLocked();
assertArrayEquals(appSupportedModes, info.appsSupportedModes);
}
+
+ @Test
+ public void testSetCanHostTasks_defaultDisplay() {
+ mLogicalDisplay = new LogicalDisplay(Display.DEFAULT_DISPLAY, LAYER_STACK, mDisplayDevice);
+ assertTrue(mLogicalDisplay.canHostTasksLocked());
+
+ mLogicalDisplay.setCanHostTasksLocked(true);
+ assertTrue(mLogicalDisplay.canHostTasksLocked());
+
+ mLogicalDisplay.setCanHostTasksLocked(false);
+ assertTrue(mLogicalDisplay.canHostTasksLocked());
+ }
+
+ @Test
+ public void testSetCanHostTasks_nonDefaultNormalDisplay() {
+ mLogicalDisplay =
+ new LogicalDisplay(Display.DEFAULT_DISPLAY + 1, LAYER_STACK, mDisplayDevice);
+
+ mLogicalDisplay.setCanHostTasksLocked(true);
+ assertTrue(mLogicalDisplay.canHostTasksLocked());
+
+ mLogicalDisplay.setCanHostTasksLocked(false);
+ assertFalse(mLogicalDisplay.canHostTasksLocked());
+ }
+
+ @Test
+ public void testSetCanHostTasks_nonDefaultVirtualMirrorDisplay() {
+ mDisplayDeviceInfo.type = Display.TYPE_VIRTUAL;
+ when(mDisplayDevice.shouldOnlyMirror()).thenReturn(true);
+ mLogicalDisplay =
+ new LogicalDisplay(Display.DEFAULT_DISPLAY + 1, LAYER_STACK, mDisplayDevice);
+ mLogicalDisplay.updateLocked(mDeviceRepo, mSyntheticModeManager);
+
+ mLogicalDisplay.setCanHostTasksLocked(true);
+ assertFalse(mLogicalDisplay.canHostTasksLocked());
+
+ mLogicalDisplay.setCanHostTasksLocked(false);
+ assertFalse(mLogicalDisplay.canHostTasksLocked());
+ }
+
+ @Test
+ public void testSetCanHostTasks_nonDefaultRearDisplay() {
+ mLogicalDisplay =
+ new LogicalDisplay(Display.DEFAULT_DISPLAY + 1, LAYER_STACK, mDisplayDevice);
+ mLogicalDisplay.setDevicePositionLocked(Layout.Display.POSITION_REAR);
+
+ mLogicalDisplay.setCanHostTasksLocked(true);
+ assertTrue(mLogicalDisplay.canHostTasksLocked());
+
+ mLogicalDisplay.setCanHostTasksLocked(false);
+ assertTrue(mLogicalDisplay.canHostTasksLocked());
+ }
+
+ @Test
+ public void testSetCanHostTasks_nonDefaultOwnContentOnly() {
+ mDisplayDeviceInfo.flags = DisplayDeviceInfo.FLAG_OWN_CONTENT_ONLY;
+ mLogicalDisplay =
+ new LogicalDisplay(Display.DEFAULT_DISPLAY + 1, LAYER_STACK, mDisplayDevice);
+
+ mLogicalDisplay.setCanHostTasksLocked(true);
+ assertTrue(mLogicalDisplay.canHostTasksLocked());
+
+ mLogicalDisplay.setCanHostTasksLocked(false);
+ assertTrue(mLogicalDisplay.canHostTasksLocked());
+ }
}