summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Android.bp44
-rw-r--r--PREUPLOAD.cfg2
-rw-r--r--apex/jobscheduler/framework/java/android/app/job/JobParameters.java22
-rw-r--r--apex/jobscheduler/framework/java/android/app/job/JobServiceEngine.java10
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java21
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java13
-rw-r--r--boot/preloaded-classes1
-rw-r--r--config/preloaded-classes1
-rw-r--r--config/preloaded-classes-denylist1
-rw-r--r--core/java/android/app/CameraCompatTaskInfo.java17
-rw-r--r--core/java/android/content/pm/flags.aconfig7
-rw-r--r--core/java/android/hardware/contexthub/HubEndpoint.java8
-rw-r--r--core/java/android/hardware/contexthub/HubServiceInfo.java15
-rw-r--r--core/java/android/os/health/OWNERS3
-rw-r--r--core/java/android/os/vibrator/persistence/VibrationXmlSerializer.java11
-rw-r--r--core/java/android/provider/Settings.java12
-rw-r--r--core/java/android/service/autofill/augmented/FillWindow.java8
-rw-r--r--core/java/android/view/Display.java16
-rw-r--r--core/java/android/view/DisplayInfo.java17
-rw-r--r--core/java/android/view/SurfaceControl.java3
-rw-r--r--core/java/com/android/internal/os/BatteryStatsHistory.java47
-rw-r--r--core/java/com/android/internal/os/logging/MetricsLoggerWrapper.java2
-rw-r--r--core/java/com/android/internal/vibrator/persistence/LegacyVibrationEffectXmlSerializer.java (renamed from core/java/com/android/internal/vibrator/persistence/VibrationEffectXmlSerializer.java)2
-rw-r--r--core/java/com/android/internal/vibrator/persistence/SerializedAmplitudeStepWaveform.java43
-rw-r--r--core/java/com/android/internal/vibrator/persistence/SerializedRepeatingEffect.java215
-rw-r--r--core/java/com/android/internal/vibrator/persistence/SerializedWaveformEffectEntries.java121
-rw-r--r--core/java/com/android/internal/vibrator/persistence/VibrationEffectSerializer.java336
-rw-r--r--core/java/com/android/internal/vibrator/persistence/VibrationEffectXmlParser.java27
-rw-r--r--core/java/com/android/internal/vibrator/persistence/XmlConstants.java2
-rw-r--r--core/proto/android/providers/settings/secure.proto1
-rw-r--r--core/res/res/values-watch/styles_device_defaults.xml7
-rw-r--r--core/res/res/values/config_battery_stats.xml2
-rw-r--r--core/res/res/values/symbols.xml1
-rw-r--r--core/tests/vibrator/src/android/os/vibrator/persistence/VibrationEffectXmlSerializationTest.java539
-rw-r--r--core/xsd/vibrator/vibration/schema/current.txt24
-rw-r--r--core/xsd/vibrator/vibration/vibration-plus-hidden-apis.xsd24
-rw-r--r--core/xsd/vibrator/vibration/vibration.xsd25
-rw-r--r--framework-jarjar-rules.txt3
-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.java31
-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--libs/hwui/Properties.cpp8
-rw-r--r--libs/hwui/Properties.h3
-rw-r--r--libs/hwui/aconfig/hwui_flags.aconfig7
-rw-r--r--libs/hwui/renderthread/RenderThread.cpp12
-rw-r--r--media/java/android/media/AudioDevicePort.java21
-rw-r--r--media/java/android/media/AudioManager.java23
-rw-r--r--media/java/android/media/AudioPortEventHandler.java20
-rw-r--r--nfc/api/current.txt6
-rw-r--r--nfc/java/android/nfc/INfcCardEmulation.aidl6
-rw-r--r--nfc/java/android/nfc/INfcEventCallback.aidl (renamed from nfc/java/android/nfc/INfcEventListener.aidl)2
-rw-r--r--nfc/java/android/nfc/NfcAdapter.java5
-rw-r--r--nfc/java/android/nfc/cardemulation/CardEmulation.java40
-rw-r--r--nfc/tests/src/android/nfc/NfcAntennaInfoTest.java77
-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/android/provider/settings/backup/SecureSettings.java1
-rw-r--r--packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java3
-rw-r--r--packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java48
-rw-r--r--packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java3
-rw-r--r--packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java28
-rw-r--r--packages/SettingsProvider/test/src/com/android/providers/settings/SettingsHelperRestoreTest.java101
-rw-r--r--packages/SystemUI/aconfig/systemui.aconfig8
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt6
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt1
-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/communal/ui/viewmodel/CommunalAppWidgetViewModelTest.kt154
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt1
-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/keyguard/data/repository/KeyguardRepositoryImplTest.kt21
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultIndicationAreaSectionTest.kt9
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModelTest.kt37
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodToPrimaryBouncerTransitionViewModelTest.kt62
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/BouncerWindowBlurTestUtilKosmos.kt87
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DozingToPrimaryBouncerTransitionViewModelTest.kt17
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModelTest.kt47
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt3
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModelTest.kt39
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToAodTransitionViewModelTest.kt21
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToDozingTransitionViewModelTest.kt18
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGlanceableHubTransitionViewModelTest.kt65
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModelTest.kt37
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/scene/shared/flag/SceneContainerFlagParameterizationTest.kt4
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java24
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerWithCoroutinesTest.kt28
-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/chips/notification/domain/interactor/SingleNotificationChipInteractorTest.kt30
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt164
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractorTest.kt48
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ConfigurationControllerImplTest.kt39
-rw-r--r--packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockFaceLayout.kt27
-rw-r--r--packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockPreviewConfig.kt1
-rw-r--r--packages/SystemUI/res/layout/ongoing_activity_chip.xml12
-rw-r--r--packages/SystemUI/res/values/dimens.xml1
-rw-r--r--packages/SystemUI/res/values/ids.xml29
-rw-r--r--packages/SystemUI/res/values/styles.xml10
-rw-r--r--packages/SystemUI/src/com/android/keyguard/EmptyLockIconViewController.kt64
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java19
-rw-r--r--packages/SystemUI/src/com/android/systemui/common/ui/data/repository/ConfigurationRepository.kt18
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/ui/binder/CommunalAppWidgetHostViewBinder.kt110
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/ui/view/layout/sections/CommunalAppWidgetSection.kt140
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalAppWidgetViewModel.kt133
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTutorialIndicatorViewModel.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt39
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/util/WidgetViewFactory.kt117
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/widgets/AppWidgetHostListenerDelegate.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/deviceentry/DeviceEntryModule.kt15
-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/keyguard/KeyguardBottomAreaRefactor.kt53
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndication.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt23
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt16
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBottomAreaInteractor.kt61
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt26
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt586
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardIndicationAreaBinder.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockViewBinder.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt57
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt102
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/PrimaryBouncerTransition.kt98
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt39
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultIndicationAreaSection.kt21
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultSettingsPopupMenuSection.kt24
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultShortcutsSection.kt68
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultUdfpsAccessibilityOverlaySection.kt17
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModel.kt23
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToPrimaryBouncerTransitionViewModel.kt14
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToPrimaryBouncerTransitionViewModel.kt22
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt252
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModel.kt33
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModel.kt18
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToAodTransitionViewModel.kt21
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToDozingTransitionViewModel.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGlanceableHubTransitionViewModel.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt44
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModel.kt25
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlag.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/scrim/ScrimDrawable.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/DebugDrawable.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java130
-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.kt61
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt15
-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/shade/domain/interactor/ShadeLockscreenInteractorImpl.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt47
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/interactor/SingleNotificationChipInteractor.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/model/NotificationChipModel.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt49
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/binder/OngoingActivityChipBinder.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractor.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/shared/model/PromotedNotificationContentModel.kt12
-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/phone/KeyguardBottomAreaView.kt124
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaViewController.kt82
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImpl.kt6
-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/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt14
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt771
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt3
-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/flags/EnableSceneContainer.kt2
-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/keyguard/data/repository/FakeKeyguardRepository.kt8
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModelKosmos.kt1
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AodToPrimaryBouncerViewModelKosmos.kt (renamed from packages/SystemUI/src/com/android/keyguard/LockIconViewController.kt)24
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGlanceableHubTransitionViewModelKosmos.kt (renamed from packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardBottomAreaInteractorKosmos.kt)12
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModelKosmos.kt1
-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/shade/domain/interactor/ShadeLockscreenInteractorKosmos.kt1
-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/autofill/bugfixes.aconfig10
-rw-r--r--services/core/java/com/android/server/am/BatteryStatsService.java5
-rw-r--r--services/core/java/com/android/server/audio/AudioService.java2
-rw-r--r--services/core/java/com/android/server/connectivity/Vpn.java12
-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/power/stats/BatteryStatsImpl.java64
-rw-r--r--services/core/java/com/android/server/wm/AppCompatUtils.java2
-rw-r--r--services/core/java/com/android/server/wm/CameraCompatFreeformPolicy.java4
-rw-r--r--services/core/java/com/android/server/wm/DeferredDisplayUpdater.java3
-rw-r--r--services/java/com/android/server/SystemServer.java12
-rw-r--r--services/tests/VpnTests/java/com/android/server/connectivity/VpnTest.java24
-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
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/job/JobParametersTest.java44
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java59
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/job/JobServiceContextTest.java70
-rw-r--r--services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java23
-rw-r--r--services/tests/powerstatstests/src/com/android/server/power/stats/MockBatteryStatsImpl.java17
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/EventConditionProviderTest.java2
-rw-r--r--tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/SurfaceControlPictureProfileTest.java12
268 files changed, 12737 insertions, 10828 deletions
diff --git a/Android.bp b/Android.bp
index 529da53e58f7..a1f6e3079804 100644
--- a/Android.bp
+++ b/Android.bp
@@ -530,6 +530,50 @@ java_library {
],
},
jarjar_prefix: "com.android.internal.hidden_from_bootclasspath",
+
+ jarjar_shards: select(release_flag("RELEASE_USE_SHARDED_JARJAR_ON_FRAMEWORK_MINUS_APEX"), {
+ true: "10",
+ default: "1",
+ }),
+}
+
+// This is identical to "framework-minus-apex" but with "jarjar_shards" hardcodd.
+// (also "stem" is commented out to avoid a conflict with the "framework-minus-apex")
+// TODO(b/383559945) This module is just for local testing / verification. It's not used
+// by anything. Remove it once we roll out RELEASE_USE_SHARDED_JARJAR_ON_FRAMEWORK_MINUS_APEX.
+java_library {
+ name: "framework-minus-apex_jarjar-sharded",
+ defaults: [
+ "framework-minus-apex-with-libs-defaults",
+ "framework-non-updatable-lint-defaults",
+ ],
+ installable: true,
+ // For backwards compatibility.
+ // stem: "framework",
+ apex_available: ["//apex_available:platform"],
+ visibility: [
+ "//frameworks/base",
+ "//frameworks/base/location",
+ // TODO(b/147128803) remove the below lines
+ "//frameworks/base/apex/blobstore/framework",
+ "//frameworks/base/apex/jobscheduler/framework",
+ "//frameworks/base/packages/Tethering/tests/unit",
+ "//packages/modules/Connectivity/Tethering/tests/unit",
+ ],
+ errorprone: {
+ javacflags: [
+ "-Xep:AndroidFrameworkCompatChange:ERROR",
+ "-Xep:AndroidFrameworkUid:ERROR",
+ ],
+ },
+ lint: {
+ baseline_filename: "lint-baseline.xml",
+ warning_checks: [
+ "FlaggedApi",
+ ],
+ },
+ jarjar_prefix: "com.android.internal.hidden_from_bootclasspath",
+ jarjar_shards: "10",
}
java_library {
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/apex/jobscheduler/framework/java/android/app/job/JobParameters.java b/apex/jobscheduler/framework/java/android/app/job/JobParameters.java
index b6f0c04b8c16..7fef4e502c97 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobParameters.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobParameters.java
@@ -23,6 +23,10 @@ import android.annotation.Nullable;
import android.annotation.TestApi;
import android.app.ActivityManager;
import android.app.usage.UsageStatsManager;
+import android.compat.Compatibility;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.Disabled;
+import android.compat.annotation.Overridable;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.ClipData;
import android.content.pm.PackageManager;
@@ -349,6 +353,16 @@ public class JobParameters implements Parcelable {
private JobCleanupCallback mJobCleanupCallback;
@Nullable
private Cleaner.Cleanable mCleanable;
+ /**
+ * Override handling of abandoned jobs in the system. Overriding this change
+ * will prevent the system to handle abandoned jobs and report it as a new
+ * stop reason STOP_REASON_TIMEOUT_ABANDONED.
+ * @hide
+ */
+ @ChangeId
+ @Disabled
+ @Overridable
+ public static final long OVERRIDE_HANDLE_ABANDONED_JOBS = 372529068L;
/** @hide */
public JobParameters(IBinder callback, String namespace, int jobId, PersistableBundle extras,
@@ -677,6 +691,10 @@ public class JobParameters implements Parcelable {
* @hide
*/
public void enableCleaner() {
+ if (!Flags.handleAbandonedJobs()
+ || Compatibility.isChangeEnabled(OVERRIDE_HANDLE_ABANDONED_JOBS)) {
+ return;
+ }
// JobParameters objects are passed by reference in local Binder
// transactions for clients running as SYSTEM. The life cycle of the
// JobParameters objects are no longer controlled by the client.
@@ -695,6 +713,10 @@ public class JobParameters implements Parcelable {
* @hide
*/
public void disableCleaner() {
+ if (!Flags.handleAbandonedJobs()
+ || Compatibility.isChangeEnabled(OVERRIDE_HANDLE_ABANDONED_JOBS)) {
+ return;
+ }
if (mJobCleanupCallback != null) {
mJobCleanupCallback.disableCleaner();
if (mCleanable != null) {
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobServiceEngine.java b/apex/jobscheduler/framework/java/android/app/job/JobServiceEngine.java
index 69b83cc02b54..d460dcc65473 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobServiceEngine.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobServiceEngine.java
@@ -165,11 +165,9 @@ public abstract class JobServiceEngine {
case MSG_EXECUTE_JOB: {
final JobParameters params = (JobParameters) msg.obj;
try {
- if (Flags.handleAbandonedJobs()) {
- params.enableCleaner();
- }
+ params.enableCleaner();
boolean workOngoing = JobServiceEngine.this.onStartJob(params);
- if (Flags.handleAbandonedJobs() && !workOngoing) {
+ if (!workOngoing) {
params.disableCleaner();
}
ackStartMessage(params, workOngoing);
@@ -196,9 +194,7 @@ public abstract class JobServiceEngine {
IJobCallback callback = params.getCallback();
if (callback != null) {
try {
- if (Flags.handleAbandonedJobs()) {
- params.disableCleaner();
- }
+ params.disableCleaner();
callback.jobFinished(params.getJobId(), needsReschedule);
} catch (RemoteException e) {
Log.e(TAG, "Error reporting job finish to system: binder has gone" +
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
index 0b884057ea19..4335cae65a3c 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -16,6 +16,7 @@
package com.android.server.job;
+import static android.app.job.JobParameters.OVERRIDE_HANDLE_ABANDONED_JOBS;
import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
import static android.Manifest.permission.MANAGE_ACTIVITY_TASKS;
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
@@ -1986,8 +1987,8 @@ public class JobSchedulerService extends com.android.server.SystemService
jobStatus.getNumAbandonedFailures(),
/* 0 is reserved for UNKNOWN_POLICY */
jobStatus.getJob().getBackoffPolicy() + 1,
- shouldUseAggressiveBackoff(jobStatus.getNumAbandonedFailures()));
-
+ shouldUseAggressiveBackoff(
+ jobStatus.getNumAbandonedFailures(), jobStatus.getSourceUid()));
// If the job is immediately ready to run, then we can just immediately
// put it in the pending list and try to schedule it. This is especially
@@ -2432,7 +2433,8 @@ public class JobSchedulerService extends com.android.server.SystemService
cancelled.getNumAbandonedFailures(),
/* 0 is reserved for UNKNOWN_POLICY */
cancelled.getJob().getBackoffPolicy() + 1,
- shouldUseAggressiveBackoff(cancelled.getNumAbandonedFailures()));
+ shouldUseAggressiveBackoff(
+ cancelled.getNumAbandonedFailures(), cancelled.getSourceUid()));
}
// If this is a replacement, bring in the new version of the job
if (incomingJob != null) {
@@ -3024,6 +3026,7 @@ public class JobSchedulerService extends com.android.server.SystemService
int numFailures = failureToReschedule.getNumFailures();
int numAbandonedFailures = failureToReschedule.getNumAbandonedFailures();
int numSystemStops = failureToReschedule.getNumSystemStops();
+ final int uid = failureToReschedule.getSourceUid();
// We should back off slowly if JobScheduler keeps stopping the job,
// but back off immediately if the issue appeared to be the app's fault
// or the user stopped the job somehow.
@@ -3033,6 +3036,7 @@ public class JobSchedulerService extends com.android.server.SystemService
|| stopReason == JobParameters.STOP_REASON_USER) {
numFailures++;
} else if (android.app.job.Flags.handleAbandonedJobs()
+ && !CompatChanges.isChangeEnabled(OVERRIDE_HANDLE_ABANDONED_JOBS, uid)
&& internalStopReason == JobParameters.INTERNAL_STOP_REASON_TIMEOUT_ABANDONED) {
numAbandonedFailures++;
numFailures++;
@@ -3041,7 +3045,7 @@ public class JobSchedulerService extends com.android.server.SystemService
}
int backoffPolicy = job.getBackoffPolicy();
- if (shouldUseAggressiveBackoff(numAbandonedFailures)) {
+ if (shouldUseAggressiveBackoff(numAbandonedFailures, uid)) {
backoffPolicy = JobInfo.BACKOFF_POLICY_EXPONENTIAL;
}
@@ -3112,8 +3116,9 @@ public class JobSchedulerService extends com.android.server.SystemService
* @return {@code true} if the given number of abandoned failures indicates that JobScheduler
* should use an aggressive backoff policy.
*/
- public boolean shouldUseAggressiveBackoff(int numAbandonedFailures) {
+ public boolean shouldUseAggressiveBackoff(int numAbandonedFailures, int uid) {
return android.app.job.Flags.handleAbandonedJobs()
+ && !CompatChanges.isChangeEnabled(OVERRIDE_HANDLE_ABANDONED_JOBS, uid)
&& numAbandonedFailures
> mConstants.ABANDONED_JOB_TIMEOUTS_BEFORE_AGGRESSIVE_BACKOFF;
}
@@ -3223,7 +3228,9 @@ public class JobSchedulerService extends com.android.server.SystemService
@VisibleForTesting
void maybeProcessBuggyJob(@NonNull JobStatus jobStatus, int debugStopReason) {
boolean jobTimedOut = debugStopReason == JobParameters.INTERNAL_STOP_REASON_TIMEOUT;
- if (android.app.job.Flags.handleAbandonedJobs()) {
+ if (android.app.job.Flags.handleAbandonedJobs()
+ && !CompatChanges.isChangeEnabled(
+ OVERRIDE_HANDLE_ABANDONED_JOBS, jobStatus.getSourceUid())) {
jobTimedOut |= (debugStopReason
== JobParameters.INTERNAL_STOP_REASON_TIMEOUT_ABANDONED);
}
@@ -3309,6 +3316,8 @@ public class JobSchedulerService extends com.android.server.SystemService
final JobStatus rescheduledJob = needsReschedule
? getRescheduleJobForFailureLocked(jobStatus, stopReason, debugStopReason) : null;
final boolean isStopReasonAbandoned = android.app.job.Flags.handleAbandonedJobs()
+ && !CompatChanges.isChangeEnabled(
+ OVERRIDE_HANDLE_ABANDONED_JOBS, jobStatus.getSourceUid())
&& (debugStopReason == JobParameters.INTERNAL_STOP_REASON_TIMEOUT_ABANDONED);
if (rescheduledJob != null
&& !rescheduledJob.shouldTreatAsUserInitiatedJob()
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
index 2b401c8ff6b1..ebfda527001d 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
@@ -16,6 +16,8 @@
package com.android.server.job;
+import static android.app.job.JobParameters.OVERRIDE_HANDLE_ABANDONED_JOBS;
+
import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_NONE;
import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
import static com.android.server.job.JobSchedulerService.safelyScaleBytesToKBForHistogram;
@@ -550,7 +552,8 @@ public final class JobServiceContext implements ServiceConnection {
job.getNumAbandonedFailures(),
/* 0 is reserved for UNKNOWN_POLICY */
job.getJob().getBackoffPolicy() + 1,
- mService.shouldUseAggressiveBackoff(job.getNumAbandonedFailures()));
+ mService.shouldUseAggressiveBackoff(
+ job.getNumAbandonedFailures(), job.getSourceUid()));
sEnqueuedJwiAtJobStart.logSampleWithUid(job.getUid(), job.getWorkCount());
final String sourcePackage = job.getSourcePackageName();
if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) {
@@ -1461,7 +1464,10 @@ public final class JobServiceContext implements ServiceConnection {
final StringBuilder debugStopReason = new StringBuilder("client timed out");
if (android.app.job.Flags.handleAbandonedJobs()
- && executing != null && executing.isAbandoned()) {
+ && executing != null
+ && !CompatChanges.isChangeEnabled(
+ OVERRIDE_HANDLE_ABANDONED_JOBS, executing.getSourceUid())
+ && executing.isAbandoned()) {
final String abandonedMessage = " and maybe abandoned";
stopReason = JobParameters.STOP_REASON_TIMEOUT_ABANDONED;
internalStopReason = JobParameters.INTERNAL_STOP_REASON_TIMEOUT_ABANDONED;
@@ -1689,7 +1695,8 @@ public final class JobServiceContext implements ServiceConnection {
completedJob.getNumAbandonedFailures(),
/* 0 is reserved for UNKNOWN_POLICY */
completedJob.getJob().getBackoffPolicy() + 1,
- mService.shouldUseAggressiveBackoff(completedJob.getNumAbandonedFailures()));
+ mService.shouldUseAggressiveBackoff(
+ completedJob.getNumAbandonedFailures(), completedJob.getSourceUid()));
if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) {
Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_SYSTEM_SERVER,
JobSchedulerService.TRACE_TRACK_NAME, getId());
diff --git a/boot/preloaded-classes b/boot/preloaded-classes
index afd9984cb124..b83bd4e4d401 100644
--- a/boot/preloaded-classes
+++ b/boot/preloaded-classes
@@ -6583,6 +6583,7 @@ android.permission.LegacyPermissionManager
android.permission.PermissionCheckerManager
android.permission.PermissionControllerManager$1
android.permission.PermissionControllerManager
+android.permission.PermissionManager
android.permission.PermissionManager$1
android.permission.PermissionManager$2
android.permission.PermissionManager$OnPermissionsChangeListenerDelegate
diff --git a/config/preloaded-classes b/config/preloaded-classes
index 343de0bf3b98..e53c78f65877 100644
--- a/config/preloaded-classes
+++ b/config/preloaded-classes
@@ -6587,6 +6587,7 @@ android.permission.LegacyPermissionManager
android.permission.PermissionCheckerManager
android.permission.PermissionControllerManager$1
android.permission.PermissionControllerManager
+android.permission.PermissionManager
android.permission.PermissionManager$1
android.permission.PermissionManager$2
android.permission.PermissionManager$OnPermissionsChangeListenerDelegate
diff --git a/config/preloaded-classes-denylist b/config/preloaded-classes-denylist
index 16f069394639..e3e929cb00d9 100644
--- a/config/preloaded-classes-denylist
+++ b/config/preloaded-classes-denylist
@@ -3,7 +3,6 @@ android.media.MediaCodecInfo$CodecCapabilities$FeatureList
android.net.ConnectivityThread$Singleton
android.os.FileObserver
android.os.NullVibrator
-android.permission.PermissionManager
android.provider.MediaStore
android.speech.tts.TextToSpeech$Connection$SetupConnectionAsyncTask
android.view.HdrRenderState
diff --git a/core/java/android/app/CameraCompatTaskInfo.java b/core/java/android/app/CameraCompatTaskInfo.java
index 845d2acbaf9d..aff6b35a6ded 100644
--- a/core/java/android/app/CameraCompatTaskInfo.java
+++ b/core/java/android/app/CameraCompatTaskInfo.java
@@ -36,36 +36,42 @@ import java.lang.annotation.RetentionPolicy;
*/
public class CameraCompatTaskInfo implements Parcelable {
/**
+ * Undefined camera compat mode.
+ */
+ public static final int CAMERA_COMPAT_FREEFORM_UNSPECIFIED = 0;
+
+ /**
* The value to use when no camera compat treatment should be applied to a windowed task.
*/
- public static final int CAMERA_COMPAT_FREEFORM_NONE = 0;
+ public static final int CAMERA_COMPAT_FREEFORM_NONE = 1;
/**
* The value to use when camera compat treatment should be applied to an activity requesting
* portrait orientation, while a device is in landscape. Applies only to freeform tasks.
*/
- public static final int CAMERA_COMPAT_FREEFORM_PORTRAIT_DEVICE_IN_LANDSCAPE = 1;
+ public static final int CAMERA_COMPAT_FREEFORM_PORTRAIT_DEVICE_IN_LANDSCAPE = 2;
/**
* The value to use when camera compat treatment should be applied to an activity requesting
* landscape orientation, while a device is in landscape. Applies only to freeform tasks.
*/
- public static final int CAMERA_COMPAT_FREEFORM_LANDSCAPE_DEVICE_IN_LANDSCAPE = 2;
+ public static final int CAMERA_COMPAT_FREEFORM_LANDSCAPE_DEVICE_IN_LANDSCAPE = 3;
/**
* The value to use when camera compat treatment should be applied to an activity requesting
* portrait orientation, while a device is in portrait. Applies only to freeform tasks.
*/
- public static final int CAMERA_COMPAT_FREEFORM_PORTRAIT_DEVICE_IN_PORTRAIT = 3;
+ public static final int CAMERA_COMPAT_FREEFORM_PORTRAIT_DEVICE_IN_PORTRAIT = 4;
/**
* The value to use when camera compat treatment should be applied to an activity requesting
* landscape orientation, while a device is in portrait. Applies only to freeform tasks.
*/
- public static final int CAMERA_COMPAT_FREEFORM_LANDSCAPE_DEVICE_IN_PORTRAIT = 4;
+ public static final int CAMERA_COMPAT_FREEFORM_LANDSCAPE_DEVICE_IN_PORTRAIT = 5;
@Retention(RetentionPolicy.SOURCE)
@IntDef(prefix = { "CAMERA_COMPAT_FREEFORM_" }, value = {
+ CAMERA_COMPAT_FREEFORM_UNSPECIFIED,
CAMERA_COMPAT_FREEFORM_NONE,
CAMERA_COMPAT_FREEFORM_PORTRAIT_DEVICE_IN_LANDSCAPE,
CAMERA_COMPAT_FREEFORM_LANDSCAPE_DEVICE_IN_LANDSCAPE,
@@ -184,6 +190,7 @@ public class CameraCompatTaskInfo implements Parcelable {
public static String freeformCameraCompatModeToString(
@FreeformCameraCompatMode int freeformCameraCompatMode) {
return switch (freeformCameraCompatMode) {
+ case CAMERA_COMPAT_FREEFORM_UNSPECIFIED -> "undefined";
case CAMERA_COMPAT_FREEFORM_NONE -> "inactive";
case CAMERA_COMPAT_FREEFORM_PORTRAIT_DEVICE_IN_LANDSCAPE ->
"app-portrait-device-landscape";
diff --git a/core/java/android/content/pm/flags.aconfig b/core/java/android/content/pm/flags.aconfig
index 00ddae334ef2..0d219a901b9d 100644
--- a/core/java/android/content/pm/flags.aconfig
+++ b/core/java/android/content/pm/flags.aconfig
@@ -149,6 +149,13 @@ flag {
}
flag {
+ name: "cache_sdk_system_features"
+ namespace: "system_performance"
+ description: "Feature flag to enable optimized cache for SDK-defined system feature lookups."
+ bug: "375000483"
+}
+
+flag {
name: "provide_info_of_apk_in_apex"
is_exported: true
namespace: "package_manager_service"
diff --git a/core/java/android/hardware/contexthub/HubEndpoint.java b/core/java/android/hardware/contexthub/HubEndpoint.java
index b251aa1abc0d..99f331f43450 100644
--- a/core/java/android/hardware/contexthub/HubEndpoint.java
+++ b/core/java/android/hardware/contexthub/HubEndpoint.java
@@ -404,11 +404,11 @@ public class HubEndpoint {
HubEndpointSession newSession;
try {
- // Request system service to assign session id.
- int sessionId = mServiceToken.openSession(destinationInfo, serviceDescriptor);
-
- // Save the newly created session
synchronized (mLock) {
+ // Request system service to assign session id.
+ int sessionId = mServiceToken.openSession(destinationInfo, serviceDescriptor);
+
+ // Save the newly created session
newSession =
new HubEndpointSession(
sessionId,
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/os/health/OWNERS b/core/java/android/os/health/OWNERS
index 6045344126c0..26fc8fae8f45 100644
--- a/core/java/android/os/health/OWNERS
+++ b/core/java/android/os/health/OWNERS
@@ -2,3 +2,6 @@
dplotnikov@google.com
mwachens@google.com
+
+# for headroom API only
+xwxw@google.com \ No newline at end of file
diff --git a/core/java/android/os/vibrator/persistence/VibrationXmlSerializer.java b/core/java/android/os/vibrator/persistence/VibrationXmlSerializer.java
index a95ce7914d8b..c7778dee5ebe 100644
--- a/core/java/android/os/vibrator/persistence/VibrationXmlSerializer.java
+++ b/core/java/android/os/vibrator/persistence/VibrationXmlSerializer.java
@@ -22,7 +22,8 @@ import android.annotation.TestApi;
import android.os.VibrationEffect;
import android.util.Xml;
-import com.android.internal.vibrator.persistence.VibrationEffectXmlSerializer;
+import com.android.internal.vibrator.persistence.LegacyVibrationEffectXmlSerializer;
+import com.android.internal.vibrator.persistence.VibrationEffectSerializer;
import com.android.internal.vibrator.persistence.XmlConstants;
import com.android.internal.vibrator.persistence.XmlSerializedVibration;
import com.android.internal.vibrator.persistence.XmlSerializerException;
@@ -123,7 +124,13 @@ public final class VibrationXmlSerializer {
}
try {
- serializedVibration = VibrationEffectXmlSerializer.serialize(effect, serializerFlags);
+ if (android.os.vibrator.Flags.normalizedPwleEffects()) {
+ serializedVibration = VibrationEffectSerializer.serialize(effect,
+ serializerFlags);
+ } else {
+ serializedVibration = LegacyVibrationEffectXmlSerializer.serialize(effect,
+ serializerFlags);
+ }
XmlValidator.checkSerializedVibration(serializedVibration, effect);
} catch (XmlSerializerException e) {
// Serialization failed or created incomplete representation, fail before writing.
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index cf0e90fb43ce..c3a49305af87 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -8950,6 +8950,18 @@ public final class Settings {
"high_text_contrast_enabled";
/**
+ * Setting that specifies the status of the High Contrast Text
+ * rectangle refresh's one-time prompt.
+ * 0 = UNKNOWN
+ * 1 = PROMPT_SHOWN
+ * 2 = PROMPT_UNNECESSARY
+ *
+ * @hide
+ */
+ public static final String ACCESSIBILITY_HCT_RECT_PROMPT_STATUS =
+ "accessibility_hct_rect_prompt_status";
+
+ /**
* The color contrast, float in [-1, 1], 1 being the highest contrast.
*
* @hide
diff --git a/core/java/android/service/autofill/augmented/FillWindow.java b/core/java/android/service/autofill/augmented/FillWindow.java
index 0ce040d7f862..d42ec7c71830 100644
--- a/core/java/android/service/autofill/augmented/FillWindow.java
+++ b/core/java/android/service/autofill/augmented/FillWindow.java
@@ -17,6 +17,7 @@ package android.service.autofill.augmented;
import static android.service.autofill.augmented.AugmentedAutofillService.sDebug;
import static android.service.autofill.augmented.AugmentedAutofillService.sVerbose;
+import static android.service.autofill.Flags.addAccessibilityTitleForAugmentedAutofillDropdown;
import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
@@ -36,6 +37,7 @@ import android.view.WindowManager;
import android.view.autofill.IAutofillWindowPresenter;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.R;
import dalvik.system.CloseGuard;
@@ -208,6 +210,12 @@ public final class FillWindow implements AutoCloseable {
if (mWm != null && mFillView != null) {
try {
p.flags |= WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
+ if (addAccessibilityTitleForAugmentedAutofillDropdown()) {
+ p.accessibilityTitle =
+ mFillView
+ .getContext()
+ .getString(R.string.autofill_picker_accessibility_title);
+ }
if (!mShowing) {
mWm.addView(mFillView, p);
mShowing = true;
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/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index dd9a95e58bd1..f22505b80948 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -4671,8 +4671,7 @@ public final class SurfaceControl implements Parcelable {
* Sets the importance the layer's contents has to the app's user experience.
* <p>
* When a two layers within the same app are competing for a limited rendering resource,
- * the priority will determine which layer gets access to the resource. The lower the
- * priority, the more likely the layer will get access to the resource.
+ * the layer with the highest priority will gets access to the resource.
* <p>
* Resources managed by this priority:
* <ul>
diff --git a/core/java/com/android/internal/os/BatteryStatsHistory.java b/core/java/com/android/internal/os/BatteryStatsHistory.java
index b56aadd366d5..c9c4be1e2c93 100644
--- a/core/java/com/android/internal/os/BatteryStatsHistory.java
+++ b/core/java/com/android/internal/os/BatteryStatsHistory.java
@@ -250,23 +250,22 @@ public class BatteryStatsHistory {
private static class BatteryHistoryDirectory {
private final File mDirectory;
private final MonotonicClock mMonotonicClock;
- private int mMaxHistoryFiles;
+ private int mMaxHistorySize;
private final List<BatteryHistoryFile> mHistoryFiles = new ArrayList<>();
private final ReentrantLock mLock = new ReentrantLock();
private boolean mCleanupNeeded;
- BatteryHistoryDirectory(File directory, MonotonicClock monotonicClock,
- int maxHistoryFiles) {
+ BatteryHistoryDirectory(File directory, MonotonicClock monotonicClock, int maxHistorySize) {
mDirectory = directory;
mMonotonicClock = monotonicClock;
- mMaxHistoryFiles = maxHistoryFiles;
- if (mMaxHistoryFiles == 0) {
- Slog.wtf(TAG, "mMaxHistoryFiles should not be zero when writing history");
+ mMaxHistorySize = maxHistorySize;
+ if (mMaxHistorySize == 0) {
+ Slog.w(TAG, "mMaxHistorySize should not be zero when writing history");
}
}
- void setMaxHistoryFiles(int maxHistoryFiles) {
- mMaxHistoryFiles = maxHistoryFiles;
+ void setMaxHistorySize(int maxHistorySize) {
+ mMaxHistorySize = maxHistorySize;
cleanup();
}
@@ -500,13 +499,14 @@ public class BatteryStatsHistory {
oldest.atomicFile.delete();
}
- // if there are more history files than allowed, delete oldest history files.
- // mMaxHistoryFiles comes from Constants.MAX_HISTORY_FILES and
- // can be updated by DeviceConfig at run time.
- while (mHistoryFiles.size() > mMaxHistoryFiles) {
+ // if there is more history stored than allowed, delete oldest history files.
+ int size = getSize();
+ while (size > mMaxHistorySize) {
BatteryHistoryFile oldest = mHistoryFiles.get(0);
+ int length = (int) oldest.atomicFile.getBaseFile().length();
oldest.atomicFile.delete();
mHistoryFiles.remove(0);
+ size -= length;
}
} finally {
unlock();
@@ -595,19 +595,19 @@ public class BatteryStatsHistory {
* Constructor
*
* @param systemDir typically /data/system
- * @param maxHistoryFiles the largest number of history buffer files to keep
+ * @param maxHistorySize the largest amount of battery history to keep on disk
* @param maxHistoryBufferSize the most amount of RAM to used for buffering of history steps
*/
public BatteryStatsHistory(Parcel historyBuffer, File systemDir,
- int maxHistoryFiles, int maxHistoryBufferSize,
+ int maxHistorySize, int maxHistoryBufferSize,
HistoryStepDetailsCalculator stepDetailsCalculator, Clock clock,
MonotonicClock monotonicClock, TraceDelegate tracer, EventLogger eventLogger) {
- this(historyBuffer, systemDir, maxHistoryFiles, maxHistoryBufferSize, stepDetailsCalculator,
+ this(historyBuffer, systemDir, maxHistorySize, maxHistoryBufferSize, stepDetailsCalculator,
clock, monotonicClock, tracer, eventLogger, null);
}
private BatteryStatsHistory(@Nullable Parcel historyBuffer, @Nullable File systemDir,
- int maxHistoryFiles, int maxHistoryBufferSize,
+ int maxHistorySize, int maxHistoryBufferSize,
@NonNull HistoryStepDetailsCalculator stepDetailsCalculator, @NonNull Clock clock,
@NonNull MonotonicClock monotonicClock, @NonNull TraceDelegate tracer,
@NonNull EventLogger eventLogger, @Nullable BatteryStatsHistory writableHistory) {
@@ -634,7 +634,7 @@ public class BatteryStatsHistory {
mHistoryDir = writableHistory.mHistoryDir;
} else if (systemDir != null) {
mHistoryDir = new BatteryHistoryDirectory(new File(systemDir, HISTORY_DIR),
- monotonicClock, maxHistoryFiles);
+ monotonicClock, maxHistorySize);
mHistoryDir.load();
BatteryHistoryFile activeFile = mHistoryDir.getLastFile();
if (activeFile == null) {
@@ -690,11 +690,11 @@ public class BatteryStatsHistory {
}
/**
- * Changes the maximum number of history files to be kept.
+ * Changes the maximum amount of history to be kept on disk.
*/
- public void setMaxHistoryFiles(int maxHistoryFiles) {
+ public void setMaxHistorySize(int maxHistorySize) {
if (mHistoryDir != null) {
- mHistoryDir.setMaxHistoryFiles(maxHistoryFiles);
+ mHistoryDir.setMaxHistorySize(maxHistorySize);
}
}
@@ -1175,6 +1175,13 @@ public class BatteryStatsHistory {
}
/**
+ * Returns the maximum storage size allocated to battery history.
+ */
+ public int getMaxHistorySize() {
+ return mHistoryDir.mMaxHistorySize;
+ }
+
+ /**
* @return the total size of all history files and history buffer.
*/
public int getHistoryUsedSize() {
diff --git a/core/java/com/android/internal/os/logging/MetricsLoggerWrapper.java b/core/java/com/android/internal/os/logging/MetricsLoggerWrapper.java
index 0e0098ebf074..efdc8ca694b8 100644
--- a/core/java/com/android/internal/os/logging/MetricsLoggerWrapper.java
+++ b/core/java/com/android/internal/os/logging/MetricsLoggerWrapper.java
@@ -61,7 +61,7 @@ public class MetricsLoggerWrapper {
return;
}
int pid = Process.myPid();
- String processName = Application.getProcessName();
+ String processName = Process.myProcessName();
Collection<NativeAllocationRegistry.Metrics> metrics =
NativeAllocationRegistry.getMetrics();
int nMetrics = metrics.size();
diff --git a/core/java/com/android/internal/vibrator/persistence/VibrationEffectXmlSerializer.java b/core/java/com/android/internal/vibrator/persistence/LegacyVibrationEffectXmlSerializer.java
index ebe34344c6f5..be30750ff281 100644
--- a/core/java/com/android/internal/vibrator/persistence/VibrationEffectXmlSerializer.java
+++ b/core/java/com/android/internal/vibrator/persistence/LegacyVibrationEffectXmlSerializer.java
@@ -53,7 +53,7 @@ import java.util.List;
*
* @hide
*/
-public final class VibrationEffectXmlSerializer {
+public final class LegacyVibrationEffectXmlSerializer {
/**
* Creates a serialized representation of the input {@code vibration}.
diff --git a/core/java/com/android/internal/vibrator/persistence/SerializedAmplitudeStepWaveform.java b/core/java/com/android/internal/vibrator/persistence/SerializedAmplitudeStepWaveform.java
index cd7dcfdac906..efc7e354995d 100644
--- a/core/java/com/android/internal/vibrator/persistence/SerializedAmplitudeStepWaveform.java
+++ b/core/java/com/android/internal/vibrator/persistence/SerializedAmplitudeStepWaveform.java
@@ -35,6 +35,7 @@ import com.android.modules.utils.TypedXmlSerializer;
import java.io.IOException;
import java.util.Arrays;
+import java.util.function.BiConsumer;
/**
* Serialized representation of a waveform effect created via
@@ -144,7 +145,7 @@ final class SerializedAmplitudeStepWaveform implements SerializedSegment {
// Read all nested tag that is not a repeating tag as a waveform entry.
while (XmlReader.readNextTagWithin(parser, outerDepth)
&& !TAG_REPEATING.equals(parser.getName())) {
- parseWaveformEntry(parser, waveformBuilder);
+ parseWaveformEntry(parser, waveformBuilder::addDurationAndAmplitude);
}
// If found a repeating tag, read its content.
@@ -162,6 +163,25 @@ final class SerializedAmplitudeStepWaveform implements SerializedSegment {
return waveformBuilder.build();
}
+ static void parseWaveformEntry(TypedXmlPullParser parser,
+ BiConsumer<Integer, Integer> builder) throws XmlParserException, IOException {
+ XmlValidator.checkStartTag(parser, TAG_WAVEFORM_ENTRY);
+ XmlValidator.checkTagHasNoUnexpectedAttributes(
+ parser, ATTRIBUTE_DURATION_MS, ATTRIBUTE_AMPLITUDE);
+
+ String rawAmplitude = parser.getAttributeValue(NAMESPACE, ATTRIBUTE_AMPLITUDE);
+ int amplitude = VALUE_AMPLITUDE_DEFAULT.equals(rawAmplitude)
+ ? VibrationEffect.DEFAULT_AMPLITUDE
+ : XmlReader.readAttributeIntInRange(
+ parser, ATTRIBUTE_AMPLITUDE, 0, VibrationEffect.MAX_AMPLITUDE);
+ int durationMs = XmlReader.readAttributeIntNonNegative(parser, ATTRIBUTE_DURATION_MS);
+
+ builder.accept(durationMs, amplitude);
+
+ // Consume tag
+ XmlReader.readEndTag(parser);
+ }
+
private static void parseRepeating(TypedXmlPullParser parser, Builder waveformBuilder)
throws XmlParserException, IOException {
XmlValidator.checkStartTag(parser, TAG_REPEATING);
@@ -172,7 +192,7 @@ final class SerializedAmplitudeStepWaveform implements SerializedSegment {
boolean hasEntry = false;
int outerDepth = parser.getDepth();
while (XmlReader.readNextTagWithin(parser, outerDepth)) {
- parseWaveformEntry(parser, waveformBuilder);
+ parseWaveformEntry(parser, waveformBuilder::addDurationAndAmplitude);
hasEntry = true;
}
@@ -182,24 +202,5 @@ final class SerializedAmplitudeStepWaveform implements SerializedSegment {
// Consume tag
XmlReader.readEndTag(parser, TAG_REPEATING, outerDepth);
}
-
- private static void parseWaveformEntry(TypedXmlPullParser parser, Builder waveformBuilder)
- throws XmlParserException, IOException {
- XmlValidator.checkStartTag(parser, TAG_WAVEFORM_ENTRY);
- XmlValidator.checkTagHasNoUnexpectedAttributes(
- parser, ATTRIBUTE_DURATION_MS, ATTRIBUTE_AMPLITUDE);
-
- String rawAmplitude = parser.getAttributeValue(NAMESPACE, ATTRIBUTE_AMPLITUDE);
- int amplitude = VALUE_AMPLITUDE_DEFAULT.equals(rawAmplitude)
- ? VibrationEffect.DEFAULT_AMPLITUDE
- : XmlReader.readAttributeIntInRange(
- parser, ATTRIBUTE_AMPLITUDE, 0, VibrationEffect.MAX_AMPLITUDE);
- int durationMs = XmlReader.readAttributeIntNonNegative(parser, ATTRIBUTE_DURATION_MS);
-
- waveformBuilder.addDurationAndAmplitude(durationMs, amplitude);
-
- // Consume tag
- XmlReader.readEndTag(parser);
- }
}
}
diff --git a/core/java/com/android/internal/vibrator/persistence/SerializedRepeatingEffect.java b/core/java/com/android/internal/vibrator/persistence/SerializedRepeatingEffect.java
new file mode 100644
index 000000000000..12acc7247b86
--- /dev/null
+++ b/core/java/com/android/internal/vibrator/persistence/SerializedRepeatingEffect.java
@@ -0,0 +1,215 @@
+/*
+ * 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.internal.vibrator.persistence;
+
+import static com.android.internal.vibrator.persistence.XmlConstants.NAMESPACE;
+import static com.android.internal.vibrator.persistence.XmlConstants.TAG_BASIC_ENVELOPE_EFFECT;
+import static com.android.internal.vibrator.persistence.XmlConstants.TAG_PREAMBLE;
+import static com.android.internal.vibrator.persistence.XmlConstants.TAG_PREDEFINED_EFFECT;
+import static com.android.internal.vibrator.persistence.XmlConstants.TAG_PRIMITIVE_EFFECT;
+import static com.android.internal.vibrator.persistence.XmlConstants.TAG_REPEATING;
+import static com.android.internal.vibrator.persistence.XmlConstants.TAG_REPEATING_EFFECT;
+import static com.android.internal.vibrator.persistence.XmlConstants.TAG_WAVEFORM_ENTRY;
+import static com.android.internal.vibrator.persistence.XmlConstants.TAG_WAVEFORM_ENVELOPE_EFFECT;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.VibrationEffect;
+
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Serialized representation of a repeating effect created via
+ * {@link VibrationEffect#createRepeatingEffect}.
+ *
+ * @hide
+ */
+public class SerializedRepeatingEffect implements SerializedComposedEffect.SerializedSegment {
+
+ @Nullable
+ private final SerializedComposedEffect mSerializedPreamble;
+ @NonNull
+ private final SerializedComposedEffect mSerializedRepeating;
+
+ SerializedRepeatingEffect(@Nullable SerializedComposedEffect serializedPreamble,
+ @NonNull SerializedComposedEffect serializedRepeating) {
+ mSerializedPreamble = serializedPreamble;
+ mSerializedRepeating = serializedRepeating;
+ }
+
+ @Override
+ public void write(@NonNull TypedXmlSerializer serializer) throws IOException {
+ serializer.startTag(NAMESPACE, TAG_REPEATING_EFFECT);
+
+ if (mSerializedPreamble != null) {
+ serializer.startTag(NAMESPACE, TAG_PREAMBLE);
+ mSerializedPreamble.writeContent(serializer);
+ serializer.endTag(NAMESPACE, TAG_PREAMBLE);
+ }
+
+ serializer.startTag(NAMESPACE, TAG_REPEATING);
+ mSerializedRepeating.writeContent(serializer);
+ serializer.endTag(NAMESPACE, TAG_REPEATING);
+
+ serializer.endTag(NAMESPACE, TAG_REPEATING_EFFECT);
+ }
+
+ @Override
+ public void deserializeIntoComposition(@NonNull VibrationEffect.Composition composition) {
+ if (mSerializedPreamble != null) {
+ composition.addEffect(
+ VibrationEffect.createRepeatingEffect(mSerializedPreamble.deserialize(),
+ mSerializedRepeating.deserialize()));
+ return;
+ }
+
+ composition.addEffect(
+ VibrationEffect.createRepeatingEffect(mSerializedRepeating.deserialize()));
+ }
+
+ @Override
+ public String toString() {
+ return "SerializedRepeatingEffect{"
+ + "preamble=" + mSerializedPreamble
+ + ", repeating=" + mSerializedRepeating
+ + '}';
+ }
+
+ static final class Builder {
+ private SerializedComposedEffect mPreamble;
+ private SerializedComposedEffect mRepeating;
+
+ void setPreamble(SerializedComposedEffect effect) {
+ mPreamble = effect;
+ }
+
+ void setRepeating(SerializedComposedEffect effect) {
+ mRepeating = effect;
+ }
+
+ boolean hasRepeatingSegment() {
+ return mRepeating != null;
+ }
+
+ SerializedRepeatingEffect build() {
+ return new SerializedRepeatingEffect(mPreamble, mRepeating);
+ }
+ }
+
+ /** Parser implementation for {@link SerializedRepeatingEffect}. */
+ static final class Parser {
+
+ @NonNull
+ static SerializedRepeatingEffect parseNext(@NonNull TypedXmlPullParser parser,
+ @XmlConstants.Flags int flags) throws XmlParserException, IOException {
+ XmlValidator.checkStartTag(parser, TAG_REPEATING_EFFECT);
+ XmlValidator.checkTagHasNoUnexpectedAttributes(parser);
+
+ Builder builder = new Builder();
+ int outerDepth = parser.getDepth();
+
+ boolean hasNestedTag = XmlReader.readNextTagWithin(parser, outerDepth);
+ if (hasNestedTag && TAG_PREAMBLE.equals(parser.getName())) {
+ builder.setPreamble(parseEffect(parser, TAG_PREAMBLE, flags));
+ hasNestedTag = XmlReader.readNextTagWithin(parser, outerDepth);
+ }
+
+ XmlValidator.checkParserCondition(hasNestedTag,
+ "Missing %s tag in %s", TAG_REPEATING, TAG_REPEATING_EFFECT);
+ builder.setRepeating(parseEffect(parser, TAG_REPEATING, flags));
+
+ XmlValidator.checkParserCondition(builder.hasRepeatingSegment(),
+ "Unexpected %s tag with no repeating segment", TAG_REPEATING_EFFECT);
+
+ // Consume tag
+ XmlReader.readEndTag(parser, TAG_REPEATING_EFFECT, outerDepth);
+
+ return builder.build();
+ }
+
+ private static SerializedComposedEffect parseEffect(TypedXmlPullParser parser,
+ String tagName, int flags) throws XmlParserException, IOException {
+ XmlValidator.checkStartTag(parser, tagName);
+ XmlValidator.checkTagHasNoUnexpectedAttributes(parser);
+ int vibrationTagDepth = parser.getDepth();
+ XmlValidator.checkParserCondition(
+ XmlReader.readNextTagWithin(parser, vibrationTagDepth),
+ "Unsupported empty %s tag", tagName);
+
+ SerializedComposedEffect effect;
+ switch (parser.getName()) {
+ case TAG_PREDEFINED_EFFECT:
+ effect = new SerializedComposedEffect(
+ SerializedPredefinedEffect.Parser.parseNext(parser, flags));
+ break;
+ case TAG_PRIMITIVE_EFFECT:
+ effect = parsePrimitiveEffects(parser, vibrationTagDepth);
+ break;
+ case TAG_WAVEFORM_ENTRY:
+ effect = parseWaveformEntries(parser, vibrationTagDepth);
+ break;
+ case TAG_WAVEFORM_ENVELOPE_EFFECT:
+ effect = new SerializedComposedEffect(
+ SerializedWaveformEnvelopeEffect.Parser.parseNext(parser, flags));
+ break;
+ case TAG_BASIC_ENVELOPE_EFFECT:
+ effect = new SerializedComposedEffect(
+ SerializedBasicEnvelopeEffect.Parser.parseNext(parser, flags));
+ break;
+ default:
+ throw new XmlParserException("Unexpected tag " + parser.getName()
+ + " in vibration tag " + tagName);
+ }
+
+ // Consume tag
+ XmlReader.readEndTag(parser, tagName, vibrationTagDepth);
+
+ return effect;
+ }
+
+ private static SerializedComposedEffect parsePrimitiveEffects(TypedXmlPullParser parser,
+ int vibrationTagDepth)
+ throws IOException, XmlParserException {
+ List<SerializedComposedEffect.SerializedSegment> primitives = new ArrayList<>();
+ do { // First primitive tag already open
+ primitives.add(SerializedCompositionPrimitive.Parser.parseNext(parser));
+ } while (XmlReader.readNextTagWithin(parser, vibrationTagDepth));
+ return new SerializedComposedEffect(primitives.toArray(
+ new SerializedComposedEffect.SerializedSegment[
+ primitives.size()]));
+ }
+
+ private static SerializedComposedEffect parseWaveformEntries(TypedXmlPullParser parser,
+ int vibrationTagDepth)
+ throws IOException, XmlParserException {
+ SerializedWaveformEffectEntries.Builder waveformBuilder =
+ new SerializedWaveformEffectEntries.Builder();
+ do { // First waveform-entry tag already open
+ SerializedWaveformEffectEntries
+ .Parser.parseWaveformEntry(parser, waveformBuilder);
+ } while (XmlReader.readNextTagWithin(parser, vibrationTagDepth));
+ XmlValidator.checkParserCondition(waveformBuilder.hasNonZeroDuration(),
+ "Unexpected %s tag with total duration zero", TAG_WAVEFORM_ENTRY);
+ return new SerializedComposedEffect(waveformBuilder.build());
+ }
+ }
+}
diff --git a/core/java/com/android/internal/vibrator/persistence/SerializedWaveformEffectEntries.java b/core/java/com/android/internal/vibrator/persistence/SerializedWaveformEffectEntries.java
new file mode 100644
index 000000000000..8849e75e7891
--- /dev/null
+++ b/core/java/com/android/internal/vibrator/persistence/SerializedWaveformEffectEntries.java
@@ -0,0 +1,121 @@
+/*
+ * 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.internal.vibrator.persistence;
+
+import static com.android.internal.vibrator.persistence.XmlConstants.ATTRIBUTE_AMPLITUDE;
+import static com.android.internal.vibrator.persistence.XmlConstants.ATTRIBUTE_DURATION_MS;
+import static com.android.internal.vibrator.persistence.XmlConstants.NAMESPACE;
+import static com.android.internal.vibrator.persistence.XmlConstants.TAG_WAVEFORM_ENTRY;
+import static com.android.internal.vibrator.persistence.XmlConstants.VALUE_AMPLITUDE_DEFAULT;
+
+import android.annotation.NonNull;
+import android.os.VibrationEffect;
+import android.util.IntArray;
+import android.util.LongArray;
+
+import com.android.internal.vibrator.persistence.SerializedComposedEffect.SerializedSegment;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
+
+import java.io.IOException;
+import java.util.Arrays;
+
+/**
+ * Serialized representation of a list of waveform entries created via
+ * {@link VibrationEffect#createWaveform(long[], int[], int)}.
+ *
+ * @hide
+ */
+final class SerializedWaveformEffectEntries implements SerializedSegment {
+
+ @NonNull
+ private final long[] mTimings;
+ @NonNull
+ private final int[] mAmplitudes;
+
+ private SerializedWaveformEffectEntries(@NonNull long[] timings,
+ @NonNull int[] amplitudes) {
+ mTimings = timings;
+ mAmplitudes = amplitudes;
+ }
+
+ @Override
+ public void deserializeIntoComposition(@NonNull VibrationEffect.Composition composition) {
+ composition.addEffect(VibrationEffect.createWaveform(mTimings, mAmplitudes, -1));
+ }
+
+ @Override
+ public void write(@NonNull TypedXmlSerializer serializer) throws IOException {
+ for (int i = 0; i < mTimings.length; i++) {
+ serializer.startTag(NAMESPACE, TAG_WAVEFORM_ENTRY);
+
+ if (mAmplitudes[i] == VibrationEffect.DEFAULT_AMPLITUDE) {
+ serializer.attribute(NAMESPACE, ATTRIBUTE_AMPLITUDE, VALUE_AMPLITUDE_DEFAULT);
+ } else {
+ serializer.attributeInt(NAMESPACE, ATTRIBUTE_AMPLITUDE, mAmplitudes[i]);
+ }
+
+ serializer.attributeLong(NAMESPACE, ATTRIBUTE_DURATION_MS, mTimings[i]);
+ serializer.endTag(NAMESPACE, TAG_WAVEFORM_ENTRY);
+ }
+
+ }
+
+ @Override
+ public String toString() {
+ return "SerializedWaveformEffectEntries{"
+ + "timings=" + Arrays.toString(mTimings)
+ + ", amplitudes=" + Arrays.toString(mAmplitudes)
+ + '}';
+ }
+
+ /** Builder for {@link SerializedWaveformEffectEntries}. */
+ static final class Builder {
+ private final LongArray mTimings = new LongArray();
+ private final IntArray mAmplitudes = new IntArray();
+
+ void addDurationAndAmplitude(long durationMs, int amplitude) {
+ mTimings.add(durationMs);
+ mAmplitudes.add(amplitude);
+ }
+
+ boolean hasNonZeroDuration() {
+ for (int i = 0; i < mTimings.size(); i++) {
+ if (mTimings.get(i) > 0) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ SerializedWaveformEffectEntries build() {
+ return new SerializedWaveformEffectEntries(
+ mTimings.toArray(), mAmplitudes.toArray());
+ }
+ }
+
+ /** Parser implementation for the {@link XmlConstants#TAG_WAVEFORM_ENTRY}. */
+ static final class Parser {
+
+ /** Parses a single {@link XmlConstants#TAG_WAVEFORM_ENTRY} into the builder. */
+ public static void parseWaveformEntry(TypedXmlPullParser parser, Builder waveformBuilder)
+ throws XmlParserException, IOException {
+ SerializedAmplitudeStepWaveform.Parser.parseWaveformEntry(parser,
+ waveformBuilder::addDurationAndAmplitude);
+ }
+ }
+}
diff --git a/core/java/com/android/internal/vibrator/persistence/VibrationEffectSerializer.java b/core/java/com/android/internal/vibrator/persistence/VibrationEffectSerializer.java
new file mode 100644
index 000000000000..df483ecdf881
--- /dev/null
+++ b/core/java/com/android/internal/vibrator/persistence/VibrationEffectSerializer.java
@@ -0,0 +1,336 @@
+/*
+ * 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.internal.vibrator.persistence;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.PersistableBundle;
+import android.os.VibrationEffect;
+import android.os.vibrator.BasicPwleSegment;
+import android.os.vibrator.Flags;
+import android.os.vibrator.PrebakedSegment;
+import android.os.vibrator.PrimitiveSegment;
+import android.os.vibrator.PwleSegment;
+import android.os.vibrator.StepSegment;
+import android.os.vibrator.VibrationEffectSegment;
+
+import java.util.List;
+import java.util.function.BiConsumer;
+
+/**
+ * Serializer implementation for {@link VibrationEffect}.
+ *
+ * <p>This serializer does not support effects created with {@link VibrationEffect.WaveformBuilder}
+ * nor {@link VibrationEffect.Composition#addEffect(VibrationEffect)}. It only supports vibration
+ * effects defined as:
+ *
+ * <ul>
+ * <li>{@link VibrationEffect#createPredefined(int)}
+ * <li>{@link VibrationEffect#createWaveform(long[], int[], int)}
+ * <li>A composition created exclusively via
+ * {@link VibrationEffect.Composition#addPrimitive(int, float, int)}
+ * <li>{@link VibrationEffect#createVendorEffect(PersistableBundle)}
+ * <li>{@link VibrationEffect.WaveformEnvelopeBuilder}
+ * <li>{@link VibrationEffect.BasicEnvelopeBuilder}
+ * </ul>
+ *
+ * <p>This serializer also supports repeating effects. For repeating waveform effects, it attempts
+ * to serialize the effect as a single unit. If this fails, it falls back to serializing it as a
+ * sequence of individual waveform entries.
+ *
+ * @hide
+ */
+public class VibrationEffectSerializer {
+ private static final String TAG = "VibrationEffectSerializer";
+
+ /**
+ * Creates a serialized representation of the input {@code vibration}.
+ */
+ @NonNull
+ public static XmlSerializedVibration<? extends VibrationEffect> serialize(
+ @NonNull VibrationEffect vibration, @XmlConstants.Flags int flags)
+ throws XmlSerializerException {
+
+ if (Flags.vendorVibrationEffects()
+ && (vibration instanceof VibrationEffect.VendorEffect vendorEffect)) {
+ return serializeVendorEffect(vendorEffect);
+ }
+
+ XmlValidator.checkSerializerCondition(vibration instanceof VibrationEffect.Composed,
+ "Unsupported VibrationEffect type %s", vibration);
+
+ VibrationEffect.Composed composed = (VibrationEffect.Composed) vibration;
+ XmlValidator.checkSerializerCondition(!composed.getSegments().isEmpty(),
+ "Unsupported empty VibrationEffect %s", vibration);
+
+ List<VibrationEffectSegment> segments = composed.getSegments();
+ int repeatIndex = composed.getRepeatIndex();
+
+ SerializedComposedEffect serializedEffect;
+ if (repeatIndex >= 0) {
+ serializedEffect = trySerializeRepeatingAmplitudeWaveformEffect(segments, repeatIndex);
+ if (serializedEffect == null) {
+ serializedEffect = serializeRepeatingEffect(segments, repeatIndex, flags);
+ }
+ } else {
+ serializedEffect = serializeNonRepeatingEffect(segments, flags);
+ }
+
+ return serializedEffect;
+ }
+
+ private static SerializedComposedEffect serializeRepeatingEffect(
+ List<VibrationEffectSegment> segments, int repeatIndex, @XmlConstants.Flags int flags)
+ throws XmlSerializerException {
+
+ SerializedRepeatingEffect.Builder builder = new SerializedRepeatingEffect.Builder();
+ if (repeatIndex > 0) {
+ List<VibrationEffectSegment> preambleSegments = segments.subList(0, repeatIndex);
+ builder.setPreamble(serializeEffectEntries(preambleSegments, flags));
+
+ // Update segments to match the repeating block only, after preamble was consumed.
+ segments = segments.subList(repeatIndex, segments.size());
+ }
+
+ builder.setRepeating(serializeEffectEntries(segments, flags));
+
+ return new SerializedComposedEffect(builder.build());
+ }
+
+ @NonNull
+ private static SerializedComposedEffect serializeNonRepeatingEffect(
+ List<VibrationEffectSegment> segments, @XmlConstants.Flags int flags)
+ throws XmlSerializerException {
+ SerializedComposedEffect effect = trySerializeNonWaveformEffect(segments, flags);
+ if (effect == null) {
+ effect = serializeWaveformEffect(segments);
+ }
+
+ return effect;
+ }
+
+ @NonNull
+ private static SerializedComposedEffect serializeEffectEntries(
+ List<VibrationEffectSegment> segments, @XmlConstants.Flags int flags)
+ throws XmlSerializerException {
+ SerializedComposedEffect effect = trySerializeNonWaveformEffect(segments, flags);
+ if (effect == null) {
+ effect = serializeWaveformEffectEntries(segments);
+ }
+
+ return effect;
+ }
+
+ @Nullable
+ private static SerializedComposedEffect trySerializeNonWaveformEffect(
+ List<VibrationEffectSegment> segments, int flags) throws XmlSerializerException {
+ VibrationEffectSegment firstSegment = segments.getFirst();
+
+ if (firstSegment instanceof PrebakedSegment) {
+ return serializePredefinedEffect(segments, flags);
+ }
+ if (firstSegment instanceof PrimitiveSegment) {
+ return serializePrimitiveEffect(segments);
+ }
+ if (firstSegment instanceof PwleSegment) {
+ return serializeWaveformEnvelopeEffect(segments);
+ }
+ if (firstSegment instanceof BasicPwleSegment) {
+ return serializeBasicEnvelopeEffect(segments);
+ }
+
+ return null;
+ }
+
+ private static SerializedComposedEffect serializePredefinedEffect(
+ List<VibrationEffectSegment> segments, @XmlConstants.Flags int flags)
+ throws XmlSerializerException {
+ XmlValidator.checkSerializerCondition(segments.size() == 1,
+ "Unsupported multiple segments in predefined effect: %s", segments);
+ return new SerializedComposedEffect(serializePrebakedSegment(segments.getFirst(), flags));
+ }
+
+ private static SerializedVendorEffect serializeVendorEffect(
+ VibrationEffect.VendorEffect effect) {
+ return new SerializedVendorEffect(effect.getVendorData());
+ }
+
+ private static SerializedComposedEffect serializePrimitiveEffect(
+ List<VibrationEffectSegment> segments) throws XmlSerializerException {
+ SerializedComposedEffect.SerializedSegment[] primitives =
+ new SerializedComposedEffect.SerializedSegment[segments.size()];
+ for (int i = 0; i < segments.size(); i++) {
+ primitives[i] = serializePrimitiveSegment(segments.get(i));
+ }
+
+ return new SerializedComposedEffect(primitives);
+ }
+
+ private static SerializedComposedEffect serializeWaveformEnvelopeEffect(
+ List<VibrationEffectSegment> segments) throws XmlSerializerException {
+ SerializedWaveformEnvelopeEffect.Builder builder =
+ new SerializedWaveformEnvelopeEffect.Builder();
+ for (int i = 0; i < segments.size(); i++) {
+ XmlValidator.checkSerializerCondition(segments.get(i) instanceof PwleSegment,
+ "Unsupported segment for waveform envelope effect %s", segments.get(i));
+ PwleSegment segment = (PwleSegment) segments.get(i);
+
+ if (i == 0 && segment.getStartFrequencyHz() != segment.getEndFrequencyHz()) {
+ // Initial frequency explicitly defined.
+ builder.setInitialFrequencyHz(segment.getStartFrequencyHz());
+ }
+
+ builder.addControlPoint(segment.getEndAmplitude(), segment.getEndFrequencyHz(),
+ segment.getDuration());
+ }
+
+ return new SerializedComposedEffect(builder.build());
+ }
+
+ private static SerializedComposedEffect serializeBasicEnvelopeEffect(
+ List<VibrationEffectSegment> segments) throws XmlSerializerException {
+ SerializedBasicEnvelopeEffect.Builder builder = new SerializedBasicEnvelopeEffect.Builder();
+ for (int i = 0; i < segments.size(); i++) {
+ XmlValidator.checkSerializerCondition(segments.get(i) instanceof BasicPwleSegment,
+ "Unsupported segment for basic envelope effect %s", segments.get(i));
+ BasicPwleSegment segment = (BasicPwleSegment) segments.get(i);
+
+ if (i == 0 && segment.getStartSharpness() != segment.getEndSharpness()) {
+ // Initial sharpness explicitly defined.
+ builder.setInitialSharpness(segment.getStartSharpness());
+ }
+
+ builder.addControlPoint(segment.getEndIntensity(), segment.getEndSharpness(),
+ segment.getDuration());
+ }
+
+ return new SerializedComposedEffect(builder.build());
+ }
+
+ private static SerializedComposedEffect trySerializeRepeatingAmplitudeWaveformEffect(
+ List<VibrationEffectSegment> segments, int repeatingIndex) {
+ SerializedAmplitudeStepWaveform.Builder builder =
+ new SerializedAmplitudeStepWaveform.Builder();
+
+ for (int i = 0; i < segments.size(); i++) {
+ if (repeatingIndex == i) {
+ builder.setRepeatIndexToCurrentEntry();
+ }
+ try {
+ serializeStepSegment(segments.get(i), builder::addDurationAndAmplitude);
+ } catch (XmlSerializerException e) {
+ return null;
+ }
+ }
+
+ return new SerializedComposedEffect(builder.build());
+ }
+
+ private static SerializedComposedEffect serializeWaveformEffect(
+ List<VibrationEffectSegment> segments) throws XmlSerializerException {
+ SerializedAmplitudeStepWaveform.Builder builder =
+ new SerializedAmplitudeStepWaveform.Builder();
+ for (int i = 0; i < segments.size(); i++) {
+ serializeStepSegment(segments.get(i), builder::addDurationAndAmplitude);
+ }
+
+ return new SerializedComposedEffect(builder.build());
+ }
+
+ private static SerializedComposedEffect serializeWaveformEffectEntries(
+ List<VibrationEffectSegment> segments) throws XmlSerializerException {
+ SerializedWaveformEffectEntries.Builder builder =
+ new SerializedWaveformEffectEntries.Builder();
+ for (int i = 0; i < segments.size(); i++) {
+ serializeStepSegment(segments.get(i), builder::addDurationAndAmplitude);
+ }
+
+ return new SerializedComposedEffect(builder.build());
+ }
+
+ private static void serializeStepSegment(VibrationEffectSegment segment,
+ BiConsumer<Long, Integer> builder) throws XmlSerializerException {
+ XmlValidator.checkSerializerCondition(segment instanceof StepSegment,
+ "Unsupported segment for waveform effect %s", segment);
+
+ XmlValidator.checkSerializerCondition(
+ Float.compare(((StepSegment) segment).getFrequencyHz(), 0) == 0,
+ "Unsupported segment with non-default frequency %f",
+ ((StepSegment) segment).getFrequencyHz());
+
+ builder.accept(segment.getDuration(),
+ toAmplitudeInt(((StepSegment) segment).getAmplitude()));
+ }
+
+ private static SerializedPredefinedEffect serializePrebakedSegment(
+ VibrationEffectSegment segment, @XmlConstants.Flags int flags)
+ throws XmlSerializerException {
+ XmlValidator.checkSerializerCondition(segment instanceof PrebakedSegment,
+ "Unsupported segment for predefined effect %s", segment);
+
+ PrebakedSegment prebaked = (PrebakedSegment) segment;
+ XmlConstants.PredefinedEffectName effectName = XmlConstants.PredefinedEffectName.findById(
+ prebaked.getEffectId(), flags);
+
+ XmlValidator.checkSerializerCondition(effectName != null,
+ "Unsupported predefined effect id %s", prebaked.getEffectId());
+
+ if ((flags & XmlConstants.FLAG_ALLOW_HIDDEN_APIS) == 0) {
+ // Only allow effects with default fallback flag if using the public APIs schema.
+ XmlValidator.checkSerializerCondition(
+ prebaked.shouldFallback() == PrebakedSegment.DEFAULT_SHOULD_FALLBACK,
+ "Unsupported predefined effect with should fallback %s",
+ prebaked.shouldFallback());
+ }
+
+ return new SerializedPredefinedEffect(effectName, prebaked.shouldFallback());
+ }
+
+ private static SerializedCompositionPrimitive serializePrimitiveSegment(
+ VibrationEffectSegment segment) throws XmlSerializerException {
+ XmlValidator.checkSerializerCondition(segment instanceof PrimitiveSegment,
+ "Unsupported segment for primitive composition %s", segment);
+
+ PrimitiveSegment primitive = (PrimitiveSegment) segment;
+ XmlConstants.PrimitiveEffectName primitiveName =
+ XmlConstants.PrimitiveEffectName.findById(primitive.getPrimitiveId());
+
+ XmlValidator.checkSerializerCondition(primitiveName != null,
+ "Unsupported primitive effect id %s", primitive.getPrimitiveId());
+
+ XmlConstants.PrimitiveDelayType delayType = null;
+
+ if (Flags.primitiveCompositionAbsoluteDelay()) {
+ delayType = XmlConstants.PrimitiveDelayType.findByType(primitive.getDelayType());
+ XmlValidator.checkSerializerCondition(delayType != null,
+ "Unsupported primitive delay type %s", primitive.getDelayType());
+ } else {
+ XmlValidator.checkSerializerCondition(
+ primitive.getDelayType() == PrimitiveSegment.DEFAULT_DELAY_TYPE,
+ "Unsupported primitive delay type %s", primitive.getDelayType());
+ }
+
+ return new SerializedCompositionPrimitive(
+ primitiveName, primitive.getScale(), primitive.getDelay(), delayType);
+ }
+
+ private static int toAmplitudeInt(float amplitude) {
+ return Float.compare(amplitude, VibrationEffect.DEFAULT_AMPLITUDE) == 0
+ ? VibrationEffect.DEFAULT_AMPLITUDE
+ : Math.round(amplitude * VibrationEffect.MAX_AMPLITUDE);
+ }
+}
diff --git a/core/java/com/android/internal/vibrator/persistence/VibrationEffectXmlParser.java b/core/java/com/android/internal/vibrator/persistence/VibrationEffectXmlParser.java
index 314bfe40ee0b..efd75fc17cb7 100644
--- a/core/java/com/android/internal/vibrator/persistence/VibrationEffectXmlParser.java
+++ b/core/java/com/android/internal/vibrator/persistence/VibrationEffectXmlParser.java
@@ -19,6 +19,7 @@ package com.android.internal.vibrator.persistence;
import static com.android.internal.vibrator.persistence.XmlConstants.TAG_BASIC_ENVELOPE_EFFECT;
import static com.android.internal.vibrator.persistence.XmlConstants.TAG_PREDEFINED_EFFECT;
import static com.android.internal.vibrator.persistence.XmlConstants.TAG_PRIMITIVE_EFFECT;
+import static com.android.internal.vibrator.persistence.XmlConstants.TAG_REPEATING_EFFECT;
import static com.android.internal.vibrator.persistence.XmlConstants.TAG_VENDOR_EFFECT;
import static com.android.internal.vibrator.persistence.XmlConstants.TAG_VIBRATION_EFFECT;
import static com.android.internal.vibrator.persistence.XmlConstants.TAG_WAVEFORM_EFFECT;
@@ -120,6 +121,26 @@ import java.util.List;
* }
* </pre>
*
+ * * Repeating effects
+ *
+ * <pre>
+ * {@code
+ * <vibration-effect>
+ * <repeating-effect>
+ * <preamble>
+ * <primitive-effect name="click" />
+ * </preamble>
+ * <repeating>
+ * <basic-envelope-effect>
+ * <control-point intensity="0.3" sharpness="0.4" durationMs="25" />
+ * <control-point intensity="0.0" sharpness="0.5" durationMs="30" />
+ * </basic-envelope-effect>
+ * </repeating>
+ * </repeating-effect>
+ * </vibration-effect>
+ * }
+ * </pre>
+ *
* @hide
*/
public class VibrationEffectXmlParser {
@@ -191,6 +212,12 @@ public class VibrationEffectXmlParser {
SerializedBasicEnvelopeEffect.Parser.parseNext(parser, flags));
break;
} // else fall through
+ case TAG_REPEATING_EFFECT:
+ if (Flags.normalizedPwleEffects()) {
+ serializedVibration = new SerializedComposedEffect(
+ SerializedRepeatingEffect.Parser.parseNext(parser, flags));
+ break;
+ } // else fall through
default:
throw new XmlParserException("Unexpected tag " + parser.getName()
+ " in vibration tag " + vibrationTagName);
diff --git a/core/java/com/android/internal/vibrator/persistence/XmlConstants.java b/core/java/com/android/internal/vibrator/persistence/XmlConstants.java
index df262cfecd5a..cc5c7cfb4683 100644
--- a/core/java/com/android/internal/vibrator/persistence/XmlConstants.java
+++ b/core/java/com/android/internal/vibrator/persistence/XmlConstants.java
@@ -45,8 +45,10 @@ public final class XmlConstants {
public static final String TAG_WAVEFORM_ENVELOPE_EFFECT = "waveform-envelope-effect";
public static final String TAG_BASIC_ENVELOPE_EFFECT = "basic-envelope-effect";
public static final String TAG_WAVEFORM_EFFECT = "waveform-effect";
+ public static final String TAG_REPEATING_EFFECT = "repeating-effect";
public static final String TAG_WAVEFORM_ENTRY = "waveform-entry";
public static final String TAG_REPEATING = "repeating";
+ public static final String TAG_PREAMBLE = "preamble";
public static final String TAG_CONTROL_POINT = "control-point";
public static final String ATTRIBUTE_NAME = "name";
diff --git a/core/proto/android/providers/settings/secure.proto b/core/proto/android/providers/settings/secure.proto
index 2e0fe9eb13d9..7e9d62315ef1 100644
--- a/core/proto/android/providers/settings/secure.proto
+++ b/core/proto/android/providers/settings/secure.proto
@@ -105,6 +105,7 @@ message SecureSettingsProto {
optional SettingProto accessibility_gesture_targets = 57 [ (android.privacy).dest = DEST_AUTOMATIC ];
optional SettingProto display_daltonizer_saturation_level = 58 [ (android.privacy).dest = DEST_AUTOMATIC ];
optional SettingProto accessibility_key_gesture_targets = 59 [ (android.privacy).dest = DEST_AUTOMATIC ];
+ optional SettingProto hct_rect_prompt_status = 60 [ (android.privacy).dest = DEST_AUTOMATIC ];
}
optional Accessibility accessibility = 2;
diff --git a/core/res/res/values-watch/styles_device_defaults.xml b/core/res/res/values-watch/styles_device_defaults.xml
index f3c85a96936f..d8d424ae15c6 100644
--- a/core/res/res/values-watch/styles_device_defaults.xml
+++ b/core/res/res/values-watch/styles_device_defaults.xml
@@ -85,4 +85,11 @@
<item name="maxHeight">@dimen/progress_bar_height</item>
<item name="mirrorForRtl">true</item>
</style>
+
+ <style name="Widget.DeviceDefault.ProgressBar" parent="Widget.Material.ProgressBar">
+ <!-- Allow determinate option -->
+ <item name="indeterminateOnly">false</item>
+ <!-- Use Wear Material3 ring shape as default determinate drawable -->
+ <item name="progressDrawable">@drawable/progress_ring_watch</item>
+ </style>
</resources>
diff --git a/core/res/res/values/config_battery_stats.xml b/core/res/res/values/config_battery_stats.xml
index 9498273e17e0..1b453737ce88 100644
--- a/core/res/res/values/config_battery_stats.xml
+++ b/core/res/res/values/config_battery_stats.xml
@@ -53,4 +53,6 @@
battery history, in bytes. -->
<integer name="config_accumulatedBatteryUsageStatsSpanSize">32768</integer>
+ <!-- Size of storage allocated to battery history, in bytes -->
+ <integer name="config_batteryHistoryStorageSize">4194304</integer>
</resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 6c01994b3c93..75812f23eead 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -5358,6 +5358,7 @@
<java-symbol type="integer" name="config_powerStatsAggregationPeriod" />
<java-symbol type="integer" name="config_aggregatedPowerStatsSpanDuration" />
<java-symbol type="integer" name="config_accumulatedBatteryUsageStatsSpanSize" />
+ <java-symbol type="integer" name="config_batteryHistoryStorageSize" />
<!--Dynamic Tokens-->
<java-symbol name="materialColorBackground" type="color"/>
diff --git a/core/tests/vibrator/src/android/os/vibrator/persistence/VibrationEffectXmlSerializationTest.java b/core/tests/vibrator/src/android/os/vibrator/persistence/VibrationEffectXmlSerializationTest.java
index 5f25e9315831..c05888560f10 100644
--- a/core/tests/vibrator/src/android/os/vibrator/persistence/VibrationEffectXmlSerializationTest.java
+++ b/core/tests/vibrator/src/android/os/vibrator/persistence/VibrationEffectXmlSerializationTest.java
@@ -931,6 +931,545 @@ public class VibrationEffectXmlSerializationTest {
}
@Test
+ @EnableFlags(Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
+ public void testRepeating_withWaveformEnvelopeEffect_allSucceed() throws Exception {
+ VibrationEffect preamble = new VibrationEffect.WaveformEnvelopeBuilder()
+ .addControlPoint(0.1f, 50f, 10)
+ .addControlPoint(0.2f, 60f, 20)
+ .build();
+ VibrationEffect repeating = new VibrationEffect.WaveformEnvelopeBuilder()
+ .setInitialFrequencyHz(70f)
+ .addControlPoint(0.3f, 80f, 25)
+ .addControlPoint(0.4f, 90f, 30)
+ .build();
+ VibrationEffect effect = VibrationEffect.createRepeatingEffect(preamble, repeating);
+
+ String xml = """
+ <vibration-effect>
+ <repeating-effect>
+ <preamble>
+ <waveform-envelope-effect>
+ <control-point amplitude="0.1" frequencyHz="50.0" durationMs="10"/>
+ <control-point amplitude="0.2" frequencyHz="60.0" durationMs="20"/>
+ </waveform-envelope-effect>
+ </preamble>
+ <repeating>
+ <waveform-envelope-effect initialFrequencyHz="70.0">
+ <control-point amplitude="0.3" frequencyHz="80.0" durationMs="25"/>
+ <control-point amplitude="0.4" frequencyHz="90.0" durationMs="30"/>
+ </waveform-envelope-effect>
+ </repeating>
+ </repeating-effect>
+ </vibration-effect>
+ """;
+
+ assertPublicApisParserSucceeds(xml, effect);
+ assertPublicApisSerializerSucceeds(effect, "0.1", "0.2", "0.3", "0.4", "50.0", "60.0",
+ "70.0", "80.0", "90.0", "10", "20", "25", "30");
+ assertPublicApisRoundTrip(effect);
+
+ assertHiddenApisParserSucceeds(xml, effect);
+ assertHiddenApisSerializerSucceeds(effect, "0.1", "0.2", "0.3", "0.4", "50.0", "60.0",
+ "70.0", "80.0", "90.0", "10", "20", "25", "30");
+ assertHiddenApisRoundTrip(effect);
+
+ effect = VibrationEffect.createRepeatingEffect(repeating);
+
+ xml = """
+ <vibration-effect>
+ <repeating-effect>
+ <repeating>
+ <waveform-envelope-effect initialFrequencyHz="70.0">
+ <control-point amplitude="0.3" frequencyHz="80.0" durationMs="25"/>
+ <control-point amplitude="0.4" frequencyHz="90.0" durationMs="30"/>
+ </waveform-envelope-effect>
+ </repeating>
+ </repeating-effect>
+ </vibration-effect>
+ """;
+
+ assertPublicApisParserSucceeds(xml, effect);
+ assertPublicApisSerializerSucceeds(effect, "0.3", "0.4", "70.0", "80.0", "90.0", "25",
+ "30");
+ assertPublicApisRoundTrip(effect);
+
+ assertHiddenApisParserSucceeds(xml, effect);
+ assertHiddenApisSerializerSucceeds(effect, "0.3", "0.4", "70.0", "80.0", "90.0", "25",
+ "30");
+ assertHiddenApisRoundTrip(effect);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
+ public void testRepeating_withBasicEnvelopeEffect_allSucceed() throws Exception {
+ VibrationEffect preamble = new VibrationEffect.BasicEnvelopeBuilder()
+ .addControlPoint(0.1f, 0.1f, 10)
+ .addControlPoint(0.2f, 0.2f, 20)
+ .addControlPoint(0.0f, 0.2f, 20)
+ .build();
+ VibrationEffect repeating = new VibrationEffect.BasicEnvelopeBuilder()
+ .setInitialSharpness(0.3f)
+ .addControlPoint(0.3f, 0.4f, 25)
+ .addControlPoint(0.4f, 0.6f, 30)
+ .addControlPoint(0.0f, 0.7f, 35)
+ .build();
+ VibrationEffect effect = VibrationEffect.createRepeatingEffect(preamble, repeating);
+
+ String xml = """
+ <vibration-effect>
+ <repeating-effect>
+ <preamble>
+ <basic-envelope-effect>
+ <control-point intensity="0.1" sharpness="0.1" durationMs="10" />
+ <control-point intensity="0.2" sharpness="0.2" durationMs="20" />
+ <control-point intensity="0.0" sharpness="0.2" durationMs="20" />
+ </basic-envelope-effect>
+ </preamble>
+ <repeating>
+ <basic-envelope-effect initialSharpness="0.3">
+ <control-point intensity="0.3" sharpness="0.4" durationMs="25" />
+ <control-point intensity="0.4" sharpness="0.6" durationMs="30" />
+ <control-point intensity="0.0" sharpness="0.7" durationMs="35" />
+ </basic-envelope-effect>
+ </repeating>
+ </repeating-effect>
+ </vibration-effect>
+ """;
+
+ assertPublicApisParserSucceeds(xml, effect);
+ assertPublicApisSerializerSucceeds(effect, "0.0", "0.1", "0.2", "0.3", "0.4", "0.1", "0.2",
+ "0.3", "0.4", "0.6", "0.7", "10", "20", "25", "30", "35");
+ assertPublicApisRoundTrip(effect);
+
+ assertHiddenApisParserSucceeds(xml, effect);
+ assertHiddenApisSerializerSucceeds(effect, "0.0", "0.1", "0.2", "0.3", "0.4", "0.1", "0.2",
+ "0.3", "0.4", "0.6", "0.7", "10", "20", "25", "30", "35");
+ assertHiddenApisRoundTrip(effect);
+
+ effect = VibrationEffect.createRepeatingEffect(repeating);
+
+ xml = """
+ <vibration-effect>
+ <repeating-effect>
+ <repeating>
+ <basic-envelope-effect initialSharpness="0.3">
+ <control-point intensity="0.3" sharpness="0.4" durationMs="25" />
+ <control-point intensity="0.4" sharpness="0.6" durationMs="30" />
+ <control-point intensity="0.0" sharpness="0.7" durationMs="35" />
+ </basic-envelope-effect>
+ </repeating>
+ </repeating-effect>
+ </vibration-effect>
+ """;
+
+ assertPublicApisParserSucceeds(xml, effect);
+ assertPublicApisSerializerSucceeds(effect, "0.3", "0.4", "0.0", "0.4", "0.6", "0.7", "25",
+ "30", "35");
+ assertPublicApisRoundTrip(effect);
+
+ assertHiddenApisParserSucceeds(xml, effect);
+ assertHiddenApisSerializerSucceeds(effect, "0.3", "0.4", "0.0", "0.4", "0.6", "0.7", "25",
+ "30", "35");
+ assertHiddenApisRoundTrip(effect);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
+ public void testRepeating_withPredefinedEffects_allSucceed() throws Exception {
+ for (Map.Entry<String, Integer> entry : createPublicPredefinedEffectsMap().entrySet()) {
+ VibrationEffect preamble = VibrationEffect.get(entry.getValue());
+ VibrationEffect repeating = VibrationEffect.get(entry.getValue());
+ VibrationEffect effect = VibrationEffect.createRepeatingEffect(preamble, repeating);
+ String xml = String.format("""
+ <vibration-effect>
+ <repeating-effect>
+ <preamble>
+ <predefined-effect name="%s"/>
+ </preamble>
+ <repeating>
+ <predefined-effect name="%s"/>
+ </repeating>
+ </repeating-effect>
+ </vibration-effect>
+ """,
+ entry.getKey(), entry.getKey());
+
+ assertPublicApisParserSucceeds(xml, effect);
+ assertPublicApisSerializerSucceeds(effect, entry.getKey());
+ assertPublicApisRoundTrip(effect);
+
+ assertHiddenApisParserSucceeds(xml, effect);
+ assertHiddenApisSerializerSucceeds(effect, entry.getKey());
+ assertHiddenApisRoundTrip(effect);
+
+ effect = VibrationEffect.createRepeatingEffect(repeating);
+ xml = String.format("""
+ <vibration-effect>
+ <repeating-effect>
+ <repeating>
+ <predefined-effect name="%s"/>
+ </repeating>
+ </repeating-effect>
+ </vibration-effect>
+ """,
+ entry.getKey());
+
+ assertPublicApisParserSucceeds(xml, effect);
+ assertPublicApisSerializerSucceeds(effect, entry.getKey());
+ assertPublicApisRoundTrip(effect);
+
+ assertHiddenApisParserSucceeds(xml, effect);
+ assertHiddenApisSerializerSucceeds(effect, entry.getKey());
+ assertHiddenApisRoundTrip(effect);
+ }
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
+ public void testRepeating_withWaveformEntry_allSucceed() throws Exception {
+ VibrationEffect preamble = VibrationEffect.createWaveform(new long[]{123, 456, 789, 0},
+ new int[]{254, 1, 255, 0}, /* repeat= */ -1);
+ VibrationEffect repeating = VibrationEffect.createWaveform(new long[]{123, 456, 789, 0},
+ new int[]{254, 1, 255, 0}, /* repeat= */ -1);
+ VibrationEffect effect = VibrationEffect.createRepeatingEffect(preamble, repeating);
+
+ String xml = """
+ <vibration-effect>
+ <repeating-effect>
+ <preamble>
+ <waveform-entry durationMs="123" amplitude="254"/>
+ <waveform-entry durationMs="456" amplitude="1"/>
+ <waveform-entry durationMs="789" amplitude="255"/>
+ <waveform-entry durationMs="0" amplitude="0"/>
+ </preamble>
+ <repeating>
+ <waveform-entry durationMs="123" amplitude="254"/>
+ <waveform-entry durationMs="456" amplitude="1"/>
+ <waveform-entry durationMs="789" amplitude="255"/>
+ <waveform-entry durationMs="0" amplitude="0"/>
+ </repeating>
+ </repeating-effect>
+ </vibration-effect>
+ """;
+
+ assertPublicApisParserSucceeds(xml, effect);
+ assertPublicApisSerializerSucceeds(effect, "123", "456", "789", "254", "1", "255", "0");
+ assertPublicApisRoundTrip(effect);
+
+ assertHiddenApisParserSucceeds(xml, effect);
+ assertHiddenApisSerializerSucceeds(effect, "123", "456", "789", "254", "1", "255", "0");
+ assertHiddenApisRoundTrip(effect);
+
+ xml = """
+ <vibration-effect>
+ <repeating-effect>
+ <repeating>
+ <waveform-entry durationMs="123" amplitude="254"/>
+ <waveform-entry durationMs="456" amplitude="1"/>
+ <waveform-entry durationMs="789" amplitude="255"/>
+ <waveform-entry durationMs="0" amplitude="0"/>
+ </repeating>
+ </repeating-effect>
+ </vibration-effect>
+ """;
+
+ effect = VibrationEffect.createRepeatingEffect(repeating);
+
+ assertPublicApisParserSucceeds(xml, effect);
+ assertPublicApisSerializerSucceeds(effect, "123", "456", "789", "254", "1", "255", "0");
+ assertPublicApisRoundTrip(effect);
+
+ assertHiddenApisParserSucceeds(xml, effect);
+ assertHiddenApisSerializerSucceeds(effect, "123", "456", "789", "254", "1", "255", "0");
+ assertHiddenApisRoundTrip(effect);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
+ public void testRepeating_withPrimitives_allSucceed() throws Exception {
+ VibrationEffect preamble = VibrationEffect.startComposition()
+ .addPrimitive(PRIMITIVE_CLICK)
+ .addPrimitive(PRIMITIVE_TICK, 0.2497f)
+ .addPrimitive(PRIMITIVE_LOW_TICK, 1f, 356)
+ .addPrimitive(PRIMITIVE_SPIN, 0.6364f, 7)
+ .compose();
+ VibrationEffect repeating = VibrationEffect.startComposition()
+ .addPrimitive(PRIMITIVE_CLICK)
+ .addPrimitive(PRIMITIVE_TICK, 0.2497f)
+ .addPrimitive(PRIMITIVE_LOW_TICK, 1f, 356)
+ .addPrimitive(PRIMITIVE_SPIN, 0.6364f, 7)
+ .compose();
+ VibrationEffect effect = VibrationEffect.createRepeatingEffect(preamble, repeating);
+
+ String xml = """
+ <vibration-effect>
+ <repeating-effect>
+ <preamble>
+ <primitive-effect name="click" />
+ <primitive-effect name="tick" scale="0.2497" />
+ <primitive-effect name="low_tick" delayMs="356" />
+ <primitive-effect name="spin" scale="0.6364" delayMs="7" />
+ </preamble>
+ <repeating>
+ <primitive-effect name="click" />
+ <primitive-effect name="tick" scale="0.2497" />
+ <primitive-effect name="low_tick" delayMs="356" />
+ <primitive-effect name="spin" scale="0.6364" delayMs="7" />
+ </repeating>
+ </repeating-effect>
+ </vibration-effect>
+ """;
+
+ assertPublicApisParserSucceeds(xml, effect);
+ assertPublicApisSerializerSucceeds(effect, "click", "tick", "low_tick", "spin");
+ assertPublicApisRoundTrip(effect);
+
+ assertHiddenApisParserSucceeds(xml, effect);
+ assertHiddenApisSerializerSucceeds(effect, "click", "tick", "low_tick", "spin");
+ assertHiddenApisRoundTrip(effect);
+
+ repeating = VibrationEffect.startComposition()
+ .addPrimitive(PRIMITIVE_CLICK)
+ .addPrimitive(PRIMITIVE_TICK, 0.2497f)
+ .addPrimitive(PRIMITIVE_LOW_TICK, 1f, 356)
+ .addPrimitive(PRIMITIVE_SPIN, 0.6364f, 7)
+ .compose();
+ effect = VibrationEffect.createRepeatingEffect(repeating);
+
+ xml = """
+ <vibration-effect>
+ <repeating-effect>
+ <repeating>
+ <primitive-effect name="click" />
+ <primitive-effect name="tick" scale="0.2497" />
+ <primitive-effect name="low_tick" delayMs="356" />
+ <primitive-effect name="spin" scale="0.6364" delayMs="7" />
+ </repeating>
+ </repeating-effect>
+ </vibration-effect>
+ """;
+
+ assertPublicApisParserSucceeds(xml, effect);
+ assertPublicApisSerializerSucceeds(effect, "click", "tick", "low_tick", "spin");
+ assertPublicApisRoundTrip(effect);
+
+ assertHiddenApisParserSucceeds(xml, effect);
+ assertHiddenApisSerializerSucceeds(effect, "click", "tick", "low_tick", "spin");
+ assertHiddenApisRoundTrip(effect);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
+ public void testRepeating_withMixedVibrations_allSucceed() throws Exception {
+ VibrationEffect preamble = new VibrationEffect.WaveformEnvelopeBuilder()
+ .addControlPoint(0.1f, 50f, 10)
+ .build();
+ VibrationEffect repeating = VibrationEffect.get(VibrationEffect.EFFECT_TICK);
+ VibrationEffect effect = VibrationEffect.createRepeatingEffect(preamble, repeating);
+ String xml = """
+ <vibration-effect>
+ <repeating-effect>
+ <preamble>
+ <waveform-envelope-effect>
+ <control-point amplitude="0.1" frequencyHz="50.0" durationMs="10"/>
+ </waveform-envelope-effect>
+ </preamble>
+ <repeating>
+ <predefined-effect name="tick"/>
+ </repeating>
+ </repeating-effect>
+ </vibration-effect>
+ """;
+ assertPublicApisParserSucceeds(xml, effect);
+ assertPublicApisSerializerSucceeds(effect, "0.1", "50.0", "10", "tick");
+ assertPublicApisRoundTrip(effect);
+
+ assertHiddenApisParserSucceeds(xml, effect);
+ assertHiddenApisSerializerSucceeds(effect, "0.1", "50.0", "10", "tick");
+ assertHiddenApisRoundTrip(effect);
+
+ preamble = VibrationEffect.createWaveform(new long[]{123, 456},
+ new int[]{254, 1}, /* repeat= */ -1);
+ repeating = new VibrationEffect.BasicEnvelopeBuilder()
+ .addControlPoint(0.3f, 0.4f, 25)
+ .addControlPoint(0.0f, 0.5f, 30)
+ .build();
+ effect = VibrationEffect.createRepeatingEffect(preamble, repeating);
+
+ xml = """
+ <vibration-effect>
+ <repeating-effect>
+ <preamble>
+ <waveform-entry durationMs="123" amplitude="254"/>
+ <waveform-entry durationMs="456" amplitude="1"/>
+ </preamble>
+ <repeating>
+ <basic-envelope-effect>
+ <control-point intensity="0.3" sharpness="0.4" durationMs="25" />
+ <control-point intensity="0.0" sharpness="0.5" durationMs="30" />
+ </basic-envelope-effect>
+ </repeating>
+ </repeating-effect>
+ </vibration-effect>
+ """;
+
+ assertPublicApisParserSucceeds(xml, effect);
+ assertPublicApisSerializerSucceeds(effect, "123", "456", "254", "1", "0.3", "0.0", "0.4",
+ "0.5", "25", "30");
+ assertPublicApisRoundTrip(effect);
+
+ assertHiddenApisParserSucceeds(xml, effect);
+ assertHiddenApisSerializerSucceeds(effect, "123", "456", "254", "1", "0.3", "0.0", "0.4",
+ "0.5", "25", "30");
+ assertHiddenApisRoundTrip(effect);
+
+ preamble = VibrationEffect.startComposition()
+ .addPrimitive(PRIMITIVE_CLICK)
+ .compose();
+ effect = VibrationEffect.createRepeatingEffect(preamble, repeating);
+
+ xml = """
+ <vibration-effect>
+ <repeating-effect>
+ <preamble>
+ <primitive-effect name="click" />
+ </preamble>
+ <repeating>
+ <basic-envelope-effect>
+ <control-point intensity="0.3" sharpness="0.4" durationMs="25" />
+ <control-point intensity="0.0" sharpness="0.5" durationMs="30" />
+ </basic-envelope-effect>
+ </repeating>
+ </repeating-effect>
+ </vibration-effect>
+ """;
+
+ assertPublicApisParserSucceeds(xml, effect);
+ assertPublicApisSerializerSucceeds(effect, "click", "0.3", "0.4", "0.0", "0.5", "25", "30");
+ assertPublicApisRoundTrip(effect);
+
+ assertHiddenApisParserSucceeds(xml, effect);
+ assertHiddenApisSerializerSucceeds(effect, "click", "0.3", "0.4", "0.0", "0.5", "25", "30");
+ assertHiddenApisRoundTrip(effect);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
+ public void testRepeating_badXml_throwsException() throws IOException {
+ // Incomplete XML
+ assertParseElementFails("""
+ <vibration-effect>
+ <repeating-effect>
+ <preamble>
+ <primitive-effect name="click" />
+ </preamble>
+ <repeating>
+ <primitive-effect name="click" />
+ """);
+
+ assertParseElementFails("""
+ <vibration-effect>
+ <repeating-effect>
+ <primitive-effect name="click" />
+ <repeating>
+ <primitive-effect name="click" />
+ </repeating>
+ </repeating-effect>
+ </vibration-effect>
+ """);
+
+ assertParseElementFails("""
+ <vibration-effect>
+ <repeating-effect>
+ <preamble>
+ <primitive-effect name="click" />
+ </preamble>
+ <primitive-effect name="click" />
+ </repeating-effect>
+ </vibration-effect>
+ """);
+
+ // Bad vibration XML
+ assertParseElementFails("""
+ <vibration-effect>
+ <repeating-effect>
+ <repeating>
+ <primitive-effect name="click" />
+ </repeating>
+ <preamble>
+ <primitive-effect name="click" />
+ </preamble>
+ </repeating-effect>
+ </vibration-effect>
+ """);
+
+ assertParseElementFails("""
+ <vibration-effect>
+ <repeating-effect>
+ <repeating>
+ <preamble>
+ <primitive-effect name="click" />
+ </preamble>
+ <primitive-effect name="click" />
+ </repeating>
+ </repeating-effect>
+ </vibration-effect>
+ """);
+
+ assertParseElementFails("""
+ <vibration-effect>
+ <repeating-effect>
+ <preamble>
+ <primitive-effect name="click" />
+ <repeating>
+ <primitive-effect name="click" />
+ </repeating>
+ </preamble>
+ </repeating-effect>
+ </vibration-effect>
+ """);
+
+ assertParseElementFails("""
+ <vibration-effect>
+ <repeating-effect>
+ <primitive-effect name="click" />
+ <primitive-effect name="click" />
+ </repeating-effect>
+ </vibration-effect>
+ """);
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
+ public void testRepeatingEffect_featureFlagDisabled_allFail() throws Exception {
+ VibrationEffect repeating = VibrationEffect.startComposition()
+ .addPrimitive(PRIMITIVE_CLICK)
+ .addPrimitive(PRIMITIVE_TICK, 0.2497f)
+ .addPrimitive(PRIMITIVE_LOW_TICK, 1f, 356)
+ .addPrimitive(PRIMITIVE_SPIN, 0.6364f, 7)
+ .compose();
+ VibrationEffect effect = VibrationEffect.createRepeatingEffect(repeating);
+
+ String xml = """
+ <vibration-effect>
+ <repeating-effect>
+ <repeating>
+ <primitive-effect name="click" />
+ <primitive-effect name="tick" scale="0.2497" />
+ <primitive-effect name="low_tick" delayMs="356" />
+ <primitive-effect name="spin" scale="0.6364" delayMs="7" />
+ </repeating>
+ </repeating-effect>
+ </vibration-effect>
+ """;
+
+ assertPublicApisParserFails(xml);
+ assertPublicApisSerializerFails(effect);
+ assertHiddenApisParserFails(xml);
+ assertHiddenApisSerializerFails(effect);
+ }
+
+ @Test
@EnableFlags(Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
public void testVendorEffect_allSucceed() throws Exception {
PersistableBundle vendorData = new PersistableBundle();
diff --git a/core/xsd/vibrator/vibration/schema/current.txt b/core/xsd/vibrator/vibration/schema/current.txt
index 29f8d199c1d1..89ca04432fa7 100644
--- a/core/xsd/vibrator/vibration/schema/current.txt
+++ b/core/xsd/vibrator/vibration/schema/current.txt
@@ -62,17 +62,41 @@ package com.android.internal.vibrator.persistence {
enum_constant public static final com.android.internal.vibrator.persistence.PrimitiveEffectName tick;
}
+ public class RepeatingEffect {
+ ctor public RepeatingEffect();
+ method public com.android.internal.vibrator.persistence.RepeatingEffectEntry getPreamble();
+ method public com.android.internal.vibrator.persistence.RepeatingEffectEntry getRepeating();
+ method public void setPreamble(com.android.internal.vibrator.persistence.RepeatingEffectEntry);
+ method public void setRepeating(com.android.internal.vibrator.persistence.RepeatingEffectEntry);
+ }
+
+ public class RepeatingEffectEntry {
+ ctor public RepeatingEffectEntry();
+ method public com.android.internal.vibrator.persistence.BasicEnvelopeEffect getBasicEnvelopeEffect_optional();
+ method public com.android.internal.vibrator.persistence.PredefinedEffect getPredefinedEffect_optional();
+ method public com.android.internal.vibrator.persistence.PrimitiveEffect getPrimitiveEffect_optional();
+ method public com.android.internal.vibrator.persistence.WaveformEntry getWaveformEntry_optional();
+ method public com.android.internal.vibrator.persistence.WaveformEnvelopeEffect getWaveformEnvelopeEffect_optional();
+ method public void setBasicEnvelopeEffect_optional(com.android.internal.vibrator.persistence.BasicEnvelopeEffect);
+ method public void setPredefinedEffect_optional(com.android.internal.vibrator.persistence.PredefinedEffect);
+ method public void setPrimitiveEffect_optional(com.android.internal.vibrator.persistence.PrimitiveEffect);
+ method public void setWaveformEntry_optional(com.android.internal.vibrator.persistence.WaveformEntry);
+ method public void setWaveformEnvelopeEffect_optional(com.android.internal.vibrator.persistence.WaveformEnvelopeEffect);
+ }
+
public class VibrationEffect {
ctor public VibrationEffect();
method public com.android.internal.vibrator.persistence.BasicEnvelopeEffect getBasicEnvelopeEffect_optional();
method public com.android.internal.vibrator.persistence.PredefinedEffect getPredefinedEffect_optional();
method public com.android.internal.vibrator.persistence.PrimitiveEffect getPrimitiveEffect_optional();
+ method public com.android.internal.vibrator.persistence.RepeatingEffect getRepeatingEffect_optional();
method public byte[] getVendorEffect_optional();
method public com.android.internal.vibrator.persistence.WaveformEffect getWaveformEffect_optional();
method public com.android.internal.vibrator.persistence.WaveformEnvelopeEffect getWaveformEnvelopeEffect_optional();
method public void setBasicEnvelopeEffect_optional(com.android.internal.vibrator.persistence.BasicEnvelopeEffect);
method public void setPredefinedEffect_optional(com.android.internal.vibrator.persistence.PredefinedEffect);
method public void setPrimitiveEffect_optional(com.android.internal.vibrator.persistence.PrimitiveEffect);
+ method public void setRepeatingEffect_optional(com.android.internal.vibrator.persistence.RepeatingEffect);
method public void setVendorEffect_optional(byte[]);
method public void setWaveformEffect_optional(com.android.internal.vibrator.persistence.WaveformEffect);
method public void setWaveformEnvelopeEffect_optional(com.android.internal.vibrator.persistence.WaveformEnvelopeEffect);
diff --git a/core/xsd/vibrator/vibration/vibration-plus-hidden-apis.xsd b/core/xsd/vibrator/vibration/vibration-plus-hidden-apis.xsd
index b4df2d187702..57bcde7c97d4 100644
--- a/core/xsd/vibrator/vibration/vibration-plus-hidden-apis.xsd
+++ b/core/xsd/vibrator/vibration/vibration-plus-hidden-apis.xsd
@@ -60,6 +60,30 @@
<!-- Basic envelope effect -->
<xs:element name="basic-envelope-effect" type="BasicEnvelopeEffect"/>
+ <!-- Repeating vibration effect -->
+ <xs:element name="repeating-effect" type="RepeatingEffect"/>
+
+ </xs:choice>
+ </xs:complexType>
+
+ <xs:complexType name="RepeatingEffect">
+ <xs:sequence>
+ <xs:element name="preamble" maxOccurs="1" minOccurs="0" type="RepeatingEffectEntry" />
+ <xs:element name="repeating" maxOccurs="1" minOccurs="1" type="RepeatingEffectEntry" />
+ </xs:sequence>
+ </xs:complexType>
+
+ <xs:complexType name="RepeatingEffectEntry">
+ <xs:choice>
+ <xs:element name="predefined-effect" type="PredefinedEffect" />
+ <xs:element name="waveform-envelope-effect" type="WaveformEnvelopeEffect" />
+ <xs:element name="basic-envelope-effect" type="BasicEnvelopeEffect" />
+ <xs:sequence>
+ <xs:element name="waveform-entry" type="WaveformEntry" />
+ </xs:sequence>
+ <xs:sequence>
+ <xs:element name="primitive-effect" type="PrimitiveEffect" />
+ </xs:sequence>
</xs:choice>
</xs:complexType>
diff --git a/core/xsd/vibrator/vibration/vibration.xsd b/core/xsd/vibrator/vibration/vibration.xsd
index fba966faa9c9..c11fb667e709 100644
--- a/core/xsd/vibrator/vibration/vibration.xsd
+++ b/core/xsd/vibrator/vibration/vibration.xsd
@@ -58,9 +58,34 @@
<!-- Basic envelope effect -->
<xs:element name="basic-envelope-effect" type="BasicEnvelopeEffect"/>
+ <!-- Repeating vibration effect -->
+ <xs:element name="repeating-effect" type="RepeatingEffect"/>
+
+ </xs:choice>
+ </xs:complexType>
+
+ <xs:complexType name="RepeatingEffect">
+ <xs:sequence>
+ <xs:element name="preamble" maxOccurs="1" minOccurs="0" type="RepeatingEffectEntry" />
+ <xs:element name="repeating" maxOccurs="1" minOccurs="1" type="RepeatingEffectEntry" />
+ </xs:sequence>
+ </xs:complexType>
+
+ <xs:complexType name="RepeatingEffectEntry">
+ <xs:choice>
+ <xs:element name="predefined-effect" type="PredefinedEffect" />
+ <xs:element name="waveform-envelope-effect" type="WaveformEnvelopeEffect" />
+ <xs:element name="basic-envelope-effect" type="BasicEnvelopeEffect" />
+ <xs:sequence>
+ <xs:element name="waveform-entry" type="WaveformEntry" />
+ </xs:sequence>
+ <xs:sequence>
+ <xs:element name="primitive-effect" type="PrimitiveEffect" />
+ </xs:sequence>
</xs:choice>
</xs:complexType>
+
<xs:complexType name="WaveformEffect">
<xs:sequence>
diff --git a/framework-jarjar-rules.txt b/framework-jarjar-rules.txt
index 087378bef6a1..3da69e854b63 100644
--- a/framework-jarjar-rules.txt
+++ b/framework-jarjar-rules.txt
@@ -10,3 +10,6 @@ rule com.android.modules.utils.build.** android.internal.modules.utils.build.@1
# For Perfetto proto dependencies
rule perfetto.protos.** android.internal.perfetto.protos.@1
+
+# For aconfig storage classes
+rule android.aconfig.storage.** android.internal.aconfig.storage.@1
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..246760e361cd 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) {
@@ -2999,7 +3005,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mSplitLayout.update(startTransaction, false /* resetImePosition */);
}
- if (mMixedHandler.isEnteringPip(change, transitType)) {
+ if (mMixedHandler.isEnteringPip(change, transitType)
+ && getSplitItemStage(change.getLastParent()) != STAGE_TYPE_UNDEFINED) {
pipChange = change;
}
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/libs/hwui/Properties.cpp b/libs/hwui/Properties.cpp
index 064cac2a6fc6..7d01dfbb446f 100644
--- a/libs/hwui/Properties.cpp
+++ b/libs/hwui/Properties.cpp
@@ -54,6 +54,9 @@ constexpr bool resample_gainmap_regions() {
constexpr bool query_global_priority() {
return false;
}
+constexpr bool early_preload_gl_context() {
+ return false;
+}
} // namespace hwui_flags
#endif
@@ -291,5 +294,10 @@ bool Properties::resampleGainmapRegions() {
return sResampleGainmapRegions;
}
+bool Properties::earlyPreloadGlContext() {
+ return base::GetBoolProperty(PROPERTY_EARLY_PRELOAD_GL_CONTEXT,
+ hwui_flags::early_preload_gl_context());
+}
+
} // namespace uirenderer
} // namespace android
diff --git a/libs/hwui/Properties.h b/libs/hwui/Properties.h
index db930f3904de..280a75a28e65 100644
--- a/libs/hwui/Properties.h
+++ b/libs/hwui/Properties.h
@@ -236,6 +236,8 @@ enum DebugLevel {
#define PROPERTY_SKIP_EGLMANAGER_TELEMETRY "debug.hwui.skip_eglmanager_telemetry"
+#define PROPERTY_EARLY_PRELOAD_GL_CONTEXT "debug.hwui.early_preload_gl_context"
+
///////////////////////////////////////////////////////////////////////////////
// Misc
///////////////////////////////////////////////////////////////////////////////
@@ -381,6 +383,7 @@ public:
static bool initializeGlAlways();
static bool resampleGainmapRegions();
+ static bool earlyPreloadGlContext();
private:
static StretchEffectBehavior stretchEffectBehavior;
diff --git a/libs/hwui/aconfig/hwui_flags.aconfig b/libs/hwui/aconfig/hwui_flags.aconfig
index e497ea1f3cb4..76ad2acccf89 100644
--- a/libs/hwui/aconfig/hwui_flags.aconfig
+++ b/libs/hwui/aconfig/hwui_flags.aconfig
@@ -166,4 +166,11 @@ flag {
metadata {
purpose: PURPOSE_BUGFIX
}
+}
+
+flag {
+ name: "early_preload_gl_context"
+ namespace: "core_graphics"
+ description: "Initialize GL context and GraphicBufferAllocater init on renderThread preload. This improves app startup time for apps using GL."
+ bug: "383612849"
} \ No newline at end of file
diff --git a/libs/hwui/renderthread/RenderThread.cpp b/libs/hwui/renderthread/RenderThread.cpp
index 92c6ad10d1c7..69fe40c755ea 100644
--- a/libs/hwui/renderthread/RenderThread.cpp
+++ b/libs/hwui/renderthread/RenderThread.cpp
@@ -25,6 +25,7 @@
#include <private/android/choreographer.h>
#include <sys/resource.h>
#include <ui/FatVector.h>
+#include <ui/GraphicBufferAllocator.h>
#include <utils/Condition.h>
#include <utils/Log.h>
#include <utils/Mutex.h>
@@ -518,11 +519,18 @@ bool RenderThread::isCurrent() {
void RenderThread::preload() {
// EGL driver is always preloaded only if HWUI renders with GL.
if (Properties::getRenderPipelineType() == RenderPipelineType::SkiaGL) {
- std::thread eglInitThread([]() { eglGetDisplay(EGL_DEFAULT_DISPLAY); });
- eglInitThread.detach();
+ if (Properties::earlyPreloadGlContext()) {
+ queue().post([this]() { requireGlContext(); });
+ } else {
+ std::thread eglInitThread([]() { eglGetDisplay(EGL_DEFAULT_DISPLAY); });
+ eglInitThread.detach();
+ }
} else {
requireVkContext();
}
+ if (Properties::earlyPreloadGlContext()) {
+ queue().post([]() { GraphicBufferAllocator::getInstance(); });
+ }
HardwareBitmapUploader::initialize();
}
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..52eae43f7db9 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.
@@ -8469,9 +8466,13 @@ public class AudioManager {
/**
* @hide
* Registers a callback for notification of audio server state changes.
- * @param executor {@link Executor} to handle the callbacks
- * @param stateCallback the callback to receive the audio server state changes
- * To remove the callabck, pass a null reference for both executor and stateCallback.
+ * @param executor {@link Executor} to handle the callbacks. Must be non null.
+ * @param stateCallback the callback to receive the audio server state changes.
+ * Must be non null. To remove the callabck,
+ * call {@link #clearAudioServerStateCallback()}
+ * @throws IllegalArgumentException If a null argument is specified.
+ * @throws IllegalStateException If a callback is already registered
+ * *
*/
@SystemApi
public void setAudioServerStateCallback(@NonNull Executor executor,
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/nfc/api/current.txt b/nfc/api/current.txt
index 0ee81cbb7a73..c8c479a4d2ad 100644
--- a/nfc/api/current.txt
+++ b/nfc/api/current.txt
@@ -211,7 +211,7 @@ package android.nfc.cardemulation {
method public boolean isDefaultServiceForCategory(android.content.ComponentName, String);
method @FlaggedApi("android.nfc.enable_card_emulation_euicc") public boolean isEuiccSupported();
method public boolean registerAidsForService(android.content.ComponentName, String, java.util.List<java.lang.String>);
- method @FlaggedApi("android.nfc.nfc_event_listener") public void registerNfcEventListener(@NonNull java.util.concurrent.Executor, @NonNull android.nfc.cardemulation.CardEmulation.NfcEventListener);
+ method @FlaggedApi("android.nfc.nfc_event_listener") public void registerNfcEventCallback(@NonNull java.util.concurrent.Executor, @NonNull android.nfc.cardemulation.CardEmulation.NfcEventCallback);
method @FlaggedApi("android.nfc.nfc_read_polling_loop") public boolean registerPollingLoopFilterForService(@NonNull android.content.ComponentName, @NonNull String, boolean);
method @FlaggedApi("android.nfc.nfc_read_polling_loop") public boolean registerPollingLoopPatternFilterForService(@NonNull android.content.ComponentName, @NonNull String, boolean);
method public boolean removeAidsForService(android.content.ComponentName, String);
@@ -221,7 +221,7 @@ package android.nfc.cardemulation {
method public boolean setPreferredService(android.app.Activity, android.content.ComponentName);
method @FlaggedApi("android.nfc.nfc_observe_mode") public boolean setShouldDefaultToObserveModeForService(@NonNull android.content.ComponentName, boolean);
method public boolean supportsAidPrefixRegistration();
- method @FlaggedApi("android.nfc.nfc_event_listener") public void unregisterNfcEventListener(@NonNull android.nfc.cardemulation.CardEmulation.NfcEventListener);
+ method @FlaggedApi("android.nfc.nfc_event_listener") public void unregisterNfcEventCallback(@NonNull android.nfc.cardemulation.CardEmulation.NfcEventCallback);
method @NonNull @RequiresPermission(android.Manifest.permission.NFC) public boolean unsetOffHostForService(@NonNull android.content.ComponentName);
method public boolean unsetPreferredService(android.app.Activity);
field @Deprecated public static final String ACTION_CHANGE_DEFAULT = "android.nfc.cardemulation.action.ACTION_CHANGE_DEFAULT";
@@ -244,7 +244,7 @@ package android.nfc.cardemulation {
field public static final int SELECTION_MODE_PREFER_DEFAULT = 0; // 0x0
}
- @FlaggedApi("android.nfc.nfc_event_listener") public static interface CardEmulation.NfcEventListener {
+ @FlaggedApi("android.nfc.nfc_event_listener") public static interface CardEmulation.NfcEventCallback {
method @FlaggedApi("android.nfc.nfc_event_listener") public default void onAidConflictOccurred(@NonNull String);
method @FlaggedApi("android.nfc.nfc_event_listener") public default void onAidNotRouted(@NonNull String);
method @FlaggedApi("android.nfc.nfc_event_listener") public default void onInternalErrorReported(int);
diff --git a/nfc/java/android/nfc/INfcCardEmulation.aidl b/nfc/java/android/nfc/INfcCardEmulation.aidl
index bb9fe959dc06..00ceaa9801d8 100644
--- a/nfc/java/android/nfc/INfcCardEmulation.aidl
+++ b/nfc/java/android/nfc/INfcCardEmulation.aidl
@@ -17,7 +17,7 @@
package android.nfc;
import android.content.ComponentName;
-import android.nfc.INfcEventListener;
+import android.nfc.INfcEventCallback;
import android.nfc.cardemulation.AidGroup;
import android.nfc.cardemulation.ApduServiceInfo;
@@ -60,6 +60,6 @@ interface INfcCardEmulation
List<String> getRoutingStatus();
void overwriteRoutingTable(int userHandle, String emptyAid, String protocol, String tech, String sc);
- void registerNfcEventListener(in INfcEventListener listener);
- void unregisterNfcEventListener(in INfcEventListener listener);
+ void registerNfcEventCallback(in INfcEventCallback listener);
+ void unregisterNfcEventCallback(in INfcEventCallback listener);
}
diff --git a/nfc/java/android/nfc/INfcEventListener.aidl b/nfc/java/android/nfc/INfcEventCallback.aidl
index 774d8f875192..af1fa2fb2456 100644
--- a/nfc/java/android/nfc/INfcEventListener.aidl
+++ b/nfc/java/android/nfc/INfcEventCallback.aidl
@@ -5,7 +5,7 @@ import android.nfc.ComponentNameAndUser;
/**
* @hide
*/
-oneway interface INfcEventListener {
+oneway interface INfcEventCallback {
void onPreferredServiceChanged(in ComponentNameAndUser ComponentNameAndUser);
void onObserveModeStateChanged(boolean isEnabled);
void onAidConflictOccurred(in String aid);
diff --git a/nfc/java/android/nfc/NfcAdapter.java b/nfc/java/android/nfc/NfcAdapter.java
index 89ce4239cd4d..63397c21b036 100644
--- a/nfc/java/android/nfc/NfcAdapter.java
+++ b/nfc/java/android/nfc/NfcAdapter.java
@@ -1789,6 +1789,11 @@ public final class NfcAdapter {
* @param listenTechnology Flags indicating listen technologies.
* @throws UnsupportedOperationException if FEATURE_NFC,
* FEATURE_NFC_HOST_CARD_EMULATION, FEATURE_NFC_HOST_CARD_EMULATION_NFCF are unavailable.
+ *
+ * NOTE: This API overrides all technology flags regardless of the current device state,
+ * it is incompatible with enableReaderMode() API and the others that either update
+ * or assume any techlology flag set by the OS.
+ * Please use with care.
*/
@FlaggedApi(Flags.FLAG_ENABLE_NFC_SET_DISCOVERY_TECH)
diff --git a/nfc/java/android/nfc/cardemulation/CardEmulation.java b/nfc/java/android/nfc/cardemulation/CardEmulation.java
index baae05b4ea03..fee9c5bfa328 100644
--- a/nfc/java/android/nfc/cardemulation/CardEmulation.java
+++ b/nfc/java/android/nfc/cardemulation/CardEmulation.java
@@ -39,7 +39,7 @@ import android.nfc.ComponentNameAndUser;
import android.nfc.Constants;
import android.nfc.Flags;
import android.nfc.INfcCardEmulation;
-import android.nfc.INfcEventListener;
+import android.nfc.INfcEventCallback;
import android.nfc.NfcAdapter;
import android.os.Build;
import android.os.RemoteException;
@@ -1304,7 +1304,7 @@ public final class CardEmulation {
/** Listener for preferred service state changes. */
@FlaggedApi(android.nfc.Flags.FLAG_NFC_EVENT_LISTENER)
- public interface NfcEventListener {
+ public interface NfcEventCallback {
/**
* This method is called when this package gains or loses preferred Nfc service status,
* either the Default Wallet Role holder (see {@link
@@ -1380,10 +1380,10 @@ public final class CardEmulation {
default void onInternalErrorReported(@NfcInternalErrorType int errorType) {}
}
- private final ArrayMap<NfcEventListener, Executor> mNfcEventListeners = new ArrayMap<>();
+ private final ArrayMap<NfcEventCallback, Executor> mNfcEventCallbacks = new ArrayMap<>();
- final INfcEventListener mINfcEventListener =
- new INfcEventListener.Stub() {
+ final INfcEventCallback mINfcEventCallback =
+ new INfcEventCallback.Stub() {
public void onPreferredServiceChanged(ComponentNameAndUser componentNameAndUser) {
if (!android.nfc.Flags.nfcEventListener()) {
return;
@@ -1443,12 +1443,12 @@ public final class CardEmulation {
}
interface ListenerCall {
- void invoke(NfcEventListener listener);
+ void invoke(NfcEventCallback listener);
}
private void callListeners(ListenerCall listenerCall) {
- synchronized (mNfcEventListeners) {
- mNfcEventListeners.forEach(
+ synchronized (mNfcEventCallbacks) {
+ mNfcEventCallbacks.forEach(
(listener, executor) -> {
executor.execute(() -> listenerCall.invoke(listener));
});
@@ -1463,34 +1463,34 @@ public final class CardEmulation {
* @param listener The listener to register
*/
@FlaggedApi(android.nfc.Flags.FLAG_NFC_EVENT_LISTENER)
- public void registerNfcEventListener(
- @NonNull @CallbackExecutor Executor executor, @NonNull NfcEventListener listener) {
+ public void registerNfcEventCallback(
+ @NonNull @CallbackExecutor Executor executor, @NonNull NfcEventCallback listener) {
if (!android.nfc.Flags.nfcEventListener()) {
return;
}
- synchronized (mNfcEventListeners) {
- mNfcEventListeners.put(listener, executor);
- if (mNfcEventListeners.size() == 1) {
- callService(() -> sService.registerNfcEventListener(mINfcEventListener));
+ synchronized (mNfcEventCallbacks) {
+ mNfcEventCallbacks.put(listener, executor);
+ if (mNfcEventCallbacks.size() == 1) {
+ callService(() -> sService.registerNfcEventCallback(mINfcEventCallback));
}
}
}
/**
* Unregister a preferred service listener that was previously registered with {@link
- * #registerNfcEventListener(Executor, NfcEventListener)}
+ * #registerNfcEventCallback(Executor, NfcEventCallback)}
*
* @param listener The previously registered listener to unregister
*/
@FlaggedApi(android.nfc.Flags.FLAG_NFC_EVENT_LISTENER)
- public void unregisterNfcEventListener(@NonNull NfcEventListener listener) {
+ public void unregisterNfcEventCallback(@NonNull NfcEventCallback listener) {
if (!android.nfc.Flags.nfcEventListener()) {
return;
}
- synchronized (mNfcEventListeners) {
- mNfcEventListeners.remove(listener);
- if (mNfcEventListeners.size() == 0) {
- callService(() -> sService.unregisterNfcEventListener(mINfcEventListener));
+ synchronized (mNfcEventCallbacks) {
+ mNfcEventCallbacks.remove(listener);
+ if (mNfcEventCallbacks.size() == 0) {
+ callService(() -> sService.unregisterNfcEventCallback(mINfcEventCallback));
}
}
}
diff --git a/nfc/tests/src/android/nfc/NfcAntennaInfoTest.java b/nfc/tests/src/android/nfc/NfcAntennaInfoTest.java
new file mode 100644
index 000000000000..c24816d85517
--- /dev/null
+++ b/nfc/tests/src/android/nfc/NfcAntennaInfoTest.java
@@ -0,0 +1,77 @@
+/*
+ * 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 android.nfc;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.mock;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class NfcAntennaInfoTest {
+ private NfcAntennaInfo mNfcAntennaInfo;
+
+
+ @Before
+ public void setUp() {
+ AvailableNfcAntenna availableNfcAntenna = mock(AvailableNfcAntenna.class);
+ List<AvailableNfcAntenna> antennas = new ArrayList<>();
+ antennas.add(availableNfcAntenna);
+ mNfcAntennaInfo = new NfcAntennaInfo(1, 1, false, antennas);
+ }
+
+ @After
+ public void tearDown() {
+ }
+
+ @Test
+ public void testGetDeviceHeight() {
+ int height = mNfcAntennaInfo.getDeviceHeight();
+ assertThat(height).isEqualTo(1);
+ }
+
+ @Test
+ public void testGetDeviceWidth() {
+ int width = mNfcAntennaInfo.getDeviceWidth();
+ assertThat(width).isEqualTo(1);
+ }
+
+ @Test
+ public void testIsDeviceFoldable() {
+ boolean foldable = mNfcAntennaInfo.isDeviceFoldable();
+ assertThat(foldable).isFalse();
+ }
+
+ @Test
+ public void testGetAvailableNfcAntennas() {
+ List<AvailableNfcAntenna> antennas = mNfcAntennaInfo.getAvailableNfcAntennas();
+ assertThat(antennas).isNotNull();
+ assertThat(antennas.size()).isEqualTo(1);
+ }
+
+}
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/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
index 731cb7269037..18bebd40b03a 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
@@ -52,6 +52,7 @@ public class SecureSettings {
Settings.Secure.ACCESSIBILITY_BUTTON_TARGET_COMPONENT,
Settings.Secure.ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN,
Settings.Secure.ACCESSIBILITY_SHORTCUT_ON_LOCK_SCREEN,
+ Settings.Secure.ACCESSIBILITY_HCT_RECT_PROMPT_STATUS,
Settings.Secure.ACCESSIBILITY_HIGH_TEXT_CONTRAST_ENABLED,
Settings.Secure.CONTRAST_LEVEL,
Settings.Secure.ACCESSIBILITY_CAPTIONING_PRESET,
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index 039832cee6f2..1d7608d7d4d0 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -88,6 +88,9 @@ public class SecureSettingsValidators {
VALIDATORS.put(Secure.ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.ACCESSIBILITY_SHORTCUT_ON_LOCK_SCREEN, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.ACCESSIBILITY_HIGH_TEXT_CONTRAST_ENABLED, BOOLEAN_VALIDATOR);
+ VALIDATORS.put(
+ Secure.ACCESSIBILITY_HCT_RECT_PROMPT_STATUS,
+ new DiscreteValueValidator(new String[] {"0", "1", "2"}));
VALIDATORS.put(Secure.CONTRAST_LEVEL, new InclusiveFloatRangeValidator(-1f, 1f));
VALIDATORS.put(
Secure.ACCESSIBILITY_CAPTIONING_PRESET,
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java
index 6f40e2760386..9bbb509f323a 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java
@@ -25,6 +25,8 @@ import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
import android.content.res.Configuration;
import android.hardware.display.ColorDisplayManager;
import android.icu.util.ULocale;
@@ -32,6 +34,7 @@ import android.media.AudioManager;
import android.media.RingtoneManager;
import android.media.Utils;
import android.net.Uri;
+import android.os.Build;
import android.os.LocaleList;
import android.os.RemoteException;
import android.os.ServiceManager;
@@ -50,6 +53,7 @@ import com.android.settingslib.devicestate.DeviceStateRotationLockSettingsManage
import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.HashMap;
+import java.util.List;
import java.util.Locale;
import java.util.Set;
@@ -69,6 +73,9 @@ public class SettingsHelper {
private static final int LONG_PRESS_POWER_FOR_ASSISTANT = 5;
/** See frameworks/base/core/res/res/values/config.xml#config_keyChordPowerVolumeUp **/
private static final int KEY_CHORD_POWER_VOLUME_UP_GLOBAL_ACTIONS = 2;
+ @VisibleForTesting
+ static final String HIGH_CONTRAST_TEXT_RESTORED_BROADCAST_ACTION =
+ "com.android.settings.accessibility.ACTION_HIGH_CONTRAST_TEXT_RESTORED";
// Error messages for logging metrics.
private static final String ERROR_REMOTE_EXCEPTION_SETTING_LOCALE_DATA =
@@ -252,6 +259,23 @@ public class SettingsHelper {
// Don't write it to setting. Let the broadcast receiver in
// AccessibilityManagerService handle restore/merging logic.
return;
+ } else if (com.android.graphics.hwui.flags.Flags.highContrastTextSmallTextRect()
+ && Settings.Secure.ACCESSIBILITY_HIGH_TEXT_CONTRAST_ENABLED.equals(name)) {
+ final boolean currentlyEnabled = Settings.Secure.getInt(
+ context.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_HIGH_TEXT_CONTRAST_ENABLED, 0) == 1;
+ final boolean enabledInRestore = value != null && Integer.parseInt(value) == 1;
+
+ // If restoring from Android 15 or earlier and the user didn't already enable HCT
+ // on this new device, then don't restore and trigger custom migration logic.
+ final boolean needsCustomMigration = !currentlyEnabled
+ && restoredFromSdkInt < Build.VERSION_CODES.BAKLAVA
+ && enabledInRestore;
+ if (needsCustomMigration) {
+ migrateHighContrastText(context);
+ return;
+ }
+ // fall through to the ordinary write to settings
}
// Default case: write the restored value to settings
@@ -529,6 +553,30 @@ public class SettingsHelper {
}
}
+ private static void migrateHighContrastText(Context context) {
+ final Intent intent = new Intent(HIGH_CONTRAST_TEXT_RESTORED_BROADCAST_ACTION)
+ .setPackage(getSettingsAppPackage(context));
+ context.sendBroadcastAsUser(intent, context.getUser(), null);
+ }
+
+ /**
+ * Returns the System Settings application's package name
+ */
+ private static String getSettingsAppPackage(Context context) {
+ String settingsAppPackage = null;
+ PackageManager packageManager = context.getPackageManager();
+ if (packageManager != null) {
+ List<ResolveInfo> results = packageManager.queryIntentActivities(
+ new Intent(Settings.ACTION_SETTINGS),
+ PackageManager.MATCH_SYSTEM_ONLY);
+ if (!results.isEmpty()) {
+ settingsAppPackage = results.getFirst().activityInfo.applicationInfo.packageName;
+ }
+ }
+
+ return !TextUtils.isEmpty(settingsAppPackage) ? settingsAppPackage : "com.android.settings";
+ }
+
/* package */ byte[] getLocaleData() {
Configuration conf = mContext.getResources().getConfiguration();
return conf.getLocales().toLanguageTags().getBytes();
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index 37eda3ebf9a8..f9c64422b0db 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -1778,6 +1778,9 @@ class SettingsProtoDumpUtil {
Settings.Secure.ACCESSIBILITY_HIGH_TEXT_CONTRAST_ENABLED,
SecureSettingsProto.Accessibility.HIGH_TEXT_CONTRAST_ENABLED);
dumpSetting(s, p,
+ Settings.Secure.ACCESSIBILITY_HCT_RECT_PROMPT_STATUS,
+ SecureSettingsProto.Accessibility.HCT_RECT_PROMPT_STATUS);
+ dumpSetting(s, p,
Settings.Secure.CONTRAST_LEVEL,
SecureSettingsProto.Accessibility.CONTRAST_LEVEL);
dumpSetting(s, p,
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/SettingsProvider/test/src/com/android/providers/settings/SettingsHelperRestoreTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsHelperRestoreTest.java
index 048d93b09967..8bffba3cb75f 100644
--- a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsHelperRestoreTest.java
+++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsHelperRestoreTest.java
@@ -26,15 +26,19 @@ import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.Settings;
import android.provider.SettingsStringUtil;
-import androidx.test.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
import com.android.internal.util.test.BroadcastInterceptingContext;
+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;
@@ -48,7 +52,8 @@ import java.util.concurrent.ExecutionException;
*/
@RunWith(AndroidJUnit4.class)
public class SettingsHelperRestoreTest {
-
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
private static final float FLOAT_TOLERANCE = 0.01f;
private Context mContext;
@@ -57,11 +62,99 @@ public class SettingsHelperRestoreTest {
@Before
public void setUp() {
- mContext = InstrumentationRegistry.getContext();
+ mContext = InstrumentationRegistry.getInstrumentation().getContext();
mContentResolver = mContext.getContentResolver();
mSettingsHelper = new SettingsHelper(mContext);
}
+ @After
+ public void cleanUp() {
+ setDefaultAccessibilityDisplayMagnificationScale();
+ Settings.Secure.putInt(mContentResolver,
+ Settings.Secure.ACCESSIBILITY_HIGH_TEXT_CONTRAST_ENABLED, 0);
+ Settings.Secure.putString(mContentResolver, Settings.Secure.ACCESSIBILITY_QS_TARGETS, null);
+ Settings.Secure.putString(mContentResolver,
+ Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, null);
+ }
+
+ @Test
+ public void restoreHighTextContrastEnabled_currentlyEnabled_enableInRestoredFromVanilla_dontSendNotification_hctKeepsEnabled()
+ throws ExecutionException, InterruptedException {
+ BroadcastInterceptingContext interceptingContext = new BroadcastInterceptingContext(
+ mContext);
+ BroadcastInterceptingContext.FutureIntent futureIntent =
+ interceptingContext.nextBroadcastIntent(
+ SettingsHelper.HIGH_CONTRAST_TEXT_RESTORED_BROADCAST_ACTION);
+ mContentResolver = interceptingContext.getContentResolver();
+ String settingName = Settings.Secure.ACCESSIBILITY_HIGH_TEXT_CONTRAST_ENABLED;
+ Settings.Secure.putInt(mContentResolver, settingName, 1);
+
+ mSettingsHelper.restoreValue(
+ interceptingContext,
+ mContentResolver,
+ new ContentValues(2),
+ Settings.Secure.getUriFor(settingName),
+ settingName,
+ String.valueOf(1),
+ Build.VERSION_CODES.VANILLA_ICE_CREAM);
+
+ futureIntent.assertNotReceived();
+ assertThat(Settings.Secure.getInt(mContentResolver, settingName, 0)).isEqualTo(1);
+ }
+
+ @EnableFlags(com.android.graphics.hwui.flags.Flags.FLAG_HIGH_CONTRAST_TEXT_SMALL_TEXT_RECT)
+ @Test
+ public void restoreHighTextContrastEnabled_currentlyDisabled_enableInRestoredFromVanilla_sendNotification_hctKeepsDisabled()
+ throws ExecutionException, InterruptedException {
+ BroadcastInterceptingContext interceptingContext = new BroadcastInterceptingContext(
+ mContext);
+ BroadcastInterceptingContext.FutureIntent futureIntent =
+ interceptingContext.nextBroadcastIntent(
+ SettingsHelper.HIGH_CONTRAST_TEXT_RESTORED_BROADCAST_ACTION);
+ mContentResolver = interceptingContext.getContentResolver();
+ String settingName = Settings.Secure.ACCESSIBILITY_HIGH_TEXT_CONTRAST_ENABLED;
+ Settings.Secure.putInt(mContentResolver, settingName, 0);
+
+ mSettingsHelper.restoreValue(
+ interceptingContext,
+ mContentResolver,
+ new ContentValues(2),
+ Settings.Secure.getUriFor(settingName),
+ settingName,
+ String.valueOf(1),
+ Build.VERSION_CODES.VANILLA_ICE_CREAM);
+
+ Intent intentReceived = futureIntent.get();
+ assertThat(intentReceived).isNotNull();
+ assertThat(intentReceived.getPackage()).isNotNull();
+ assertThat(Settings.Secure.getInt(mContentResolver, settingName, 0)).isEqualTo(0);
+ }
+
+ @Test
+ public void restoreHighTextContrastEnabled_currentlyDisabled_enableInRestoredFromAfterVanilla_dontSendNotification_hctShouldEnabled()
+ throws ExecutionException, InterruptedException {
+ BroadcastInterceptingContext interceptingContext = new BroadcastInterceptingContext(
+ mContext);
+ BroadcastInterceptingContext.FutureIntent futureIntent =
+ interceptingContext.nextBroadcastIntent(
+ SettingsHelper.HIGH_CONTRAST_TEXT_RESTORED_BROADCAST_ACTION);
+ mContentResolver = interceptingContext.getContentResolver();
+ String settingName = Settings.Secure.ACCESSIBILITY_HIGH_TEXT_CONTRAST_ENABLED;
+ Settings.Secure.putInt(mContentResolver, settingName, 0);
+
+ mSettingsHelper.restoreValue(
+ interceptingContext,
+ mContentResolver,
+ new ContentValues(2),
+ Settings.Secure.getUriFor(settingName),
+ settingName,
+ String.valueOf(1),
+ Build.VERSION_CODES.BAKLAVA);
+
+ futureIntent.assertNotReceived();
+ assertThat(Settings.Secure.getInt(mContentResolver, settingName, 0)).isEqualTo(1);
+ }
+
/** Tests for {@link Settings.Secure#ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE}. */
@Test
public void
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 755b4542b759..74c871064073 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -265,14 +265,6 @@ flag {
}
flag {
- name: "keyguard_bottom_area_refactor"
- namespace: "systemui"
- description: "Bottom area of keyguard refactor move into KeyguardRootView. Includes "
- "lock icon and others."
- bug: "290652751"
-}
-
-flag {
name: "device_entry_udfps_refactor"
namespace: "systemui"
description: "Refactoring device entry UDFPS icon to use modern architecture and "
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..5dbedc7045e4 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
@@ -1398,6 +1398,7 @@ private fun WidgetContent(
val shrinkWidgetLabel = stringResource(R.string.accessibility_action_label_shrink_widget)
val expandWidgetLabel = stringResource(R.string.accessibility_action_label_expand_widget)
+ val isFocusable by viewModel.isFocusable.collectAsStateWithLifecycle(initialValue = false)
val selectedKey by viewModel.selectedKey.collectAsStateWithLifecycle()
val selectedIndex =
selectedKey?.let { key -> contentListState.list.indexOfFirst { it.key == key } }
@@ -1511,7 +1512,8 @@ private fun WidgetContent(
) {
with(widgetSection) {
Widget(
- viewModel = viewModel,
+ isFocusable = isFocusable,
+ openWidgetEditor = { viewModel.onOpenWidgetEditor() },
model = model,
size = size,
modifier = Modifier.fillMaxSize().allowGestures(allowed = !viewModel.isEditMode),
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt
index 105e8dadfafb..7956d0293a1d 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt
@@ -159,6 +159,7 @@ constructor(
with(lockSection) { LockIcon() }
// Aligned to bottom and constrained to below the lock icon.
+ // TODO("b/383588832") change this away from "keyguard_bottom_area"
Column(modifier = Modifier.fillMaxWidth().sysuiResTag("keyguard_bottom_area")) {
if (isUdfpsVisible && ambientIndicationSectionOptional.isPresent) {
with(ambientIndicationSectionOptional.get()) {
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/communal/ui/viewmodel/CommunalAppWidgetViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalAppWidgetViewModelTest.kt
new file mode 100644
index 000000000000..a8a3873d6de2
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalAppWidgetViewModelTest.kt
@@ -0,0 +1,154 @@
+/*
+ * 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.communal.ui.viewmodel
+
+import android.appwidget.AppWidgetHost.AppWidgetHostListener
+import android.appwidget.AppWidgetHostView
+import android.platform.test.flag.junit.FlagsParameterization
+import android.util.SizeF
+import android.widget.RemoteViews
+import androidx.test.filters.SmallTest
+import com.android.systemui.Flags.FLAG_SECONDARY_USER_WIDGET_HOST
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.communal.shared.model.fakeGlanceableHubMultiUserHelper
+import com.android.systemui.communal.widgets.AppWidgetHostListenerDelegate
+import com.android.systemui.communal.widgets.CommunalAppWidgetHost
+import com.android.systemui.communal.widgets.GlanceableHubWidgetManager
+import com.android.systemui.concurrency.fakeExecutor
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.backgroundCoroutineContext
+import com.android.systemui.kosmos.runCurrent
+import com.android.systemui.kosmos.runTest
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.lifecycle.activateIn
+import com.android.systemui.testKosmos
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.any
+import org.mockito.kotlin.doAnswer
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.verify
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
+
+@SmallTest
+@RunWith(ParameterizedAndroidJunit4::class)
+class CommunalAppWidgetViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
+ val kosmos = testKosmos()
+
+ init {
+ mSetFlagsRule.setFlagsParameterization(flags)
+ }
+
+ private val Kosmos.listenerDelegateFactory by
+ Kosmos.Fixture {
+ AppWidgetHostListenerDelegate.Factory { listener ->
+ AppWidgetHostListenerDelegate(fakeExecutor, listener)
+ }
+ }
+
+ private val Kosmos.appWidgetHost by
+ Kosmos.Fixture {
+ mock<CommunalAppWidgetHost> {
+ on { setListener(any(), any()) } doAnswer
+ { invocation ->
+ val callback = invocation.arguments[1] as AppWidgetHostListener
+ callback.updateAppWidget(mock<RemoteViews>())
+ }
+ }
+ }
+
+ private val Kosmos.glanceableHubWidgetManager by
+ Kosmos.Fixture {
+ mock<GlanceableHubWidgetManager> {
+ on { setAppWidgetHostListener(any(), any()) } doAnswer
+ { invocation ->
+ val callback = invocation.arguments[1] as AppWidgetHostListener
+ callback.updateAppWidget(mock<RemoteViews>())
+ }
+ }
+ }
+
+ private val Kosmos.underTest by
+ Kosmos.Fixture {
+ CommunalAppWidgetViewModel(
+ backgroundCoroutineContext,
+ { appWidgetHost },
+ listenerDelegateFactory,
+ { glanceableHubWidgetManager },
+ fakeGlanceableHubMultiUserHelper,
+ )
+ .apply { activateIn(testScope) }
+ }
+
+ @Test
+ fun setListener() =
+ kosmos.runTest {
+ val listener = mock<AppWidgetHostListener>()
+
+ underTest.setListener(123, listener)
+ runAll()
+
+ verify(listener).updateAppWidget(any())
+ }
+
+ @Test
+ fun setListener_HSUM() =
+ kosmos.runTest {
+ fakeGlanceableHubMultiUserHelper.setIsInHeadlessSystemUser(true)
+ val listener = mock<AppWidgetHostListener>()
+
+ underTest.setListener(123, listener)
+ runAll()
+
+ verify(listener).updateAppWidget(any())
+ }
+
+ @Test
+ fun updateSize() =
+ kosmos.runTest {
+ val view = mock<AppWidgetHostView>()
+ val size = SizeF(/* width= */ 100f, /* height= */ 200f)
+
+ underTest.updateSize(size, view)
+ runAll()
+
+ verify(view)
+ .updateAppWidgetSize(
+ /* newOptions = */ any(),
+ /* minWidth = */ eq(100),
+ /* minHeight = */ eq(200),
+ /* maxWidth = */ eq(100),
+ /* maxHeight = */ eq(200),
+ /* ignorePadding = */ eq(true),
+ )
+ }
+
+ private fun Kosmos.runAll() {
+ runCurrent()
+ fakeExecutor.runAllReady()
+ }
+
+ private companion object {
+ @JvmStatic
+ @Parameters(name = "{0}")
+ fun getParams(): List<FlagsParameterization> {
+ return FlagsParameterization.allCombinationsOf(FLAG_SECONDARY_USER_WIDGET_HOST)
+ }
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
index 9d711ab0cd29..d70af2806430 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
@@ -172,7 +172,6 @@ class CommunalViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
kosmos.testDispatcher,
testScope,
kosmos.testScope.backgroundScope,
- context.resources,
kosmos.keyguardTransitionInteractor,
kosmos.keyguardInteractor,
mock<KeyguardIndicationController>(),
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/keyguard/data/repository/KeyguardRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
index 77f19795eaf7..6805a133459f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
@@ -123,27 +123,6 @@ class KeyguardRepositoryImplTest : SysuiTestCase() {
}
@Test
- fun bottomAreaAlpha() =
- testScope.runTest {
- assertThat(underTest.bottomAreaAlpha.value).isEqualTo(1f)
-
- underTest.setBottomAreaAlpha(0.1f)
- assertThat(underTest.bottomAreaAlpha.value).isEqualTo(0.1f)
-
- underTest.setBottomAreaAlpha(0.2f)
- assertThat(underTest.bottomAreaAlpha.value).isEqualTo(0.2f)
-
- underTest.setBottomAreaAlpha(0.3f)
- assertThat(underTest.bottomAreaAlpha.value).isEqualTo(0.3f)
-
- underTest.setBottomAreaAlpha(0.5f)
- assertThat(underTest.bottomAreaAlpha.value).isEqualTo(0.5f)
-
- underTest.setBottomAreaAlpha(1.0f)
- assertThat(underTest.bottomAreaAlpha.value).isEqualTo(1f)
- }
-
- @Test
fun panelAlpha() =
testScope.runTest {
assertThat(underTest.panelAlpha.value).isEqualTo(1f)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultIndicationAreaSectionTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultIndicationAreaSectionTest.kt
index 10f7128af43c..9ceabd743618 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultIndicationAreaSectionTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultIndicationAreaSectionTest.kt
@@ -56,21 +56,12 @@ class DefaultIndicationAreaSectionTest : SysuiTestCase() {
@Test
fun addViewsConditionally() {
- mSetFlagsRule.enableFlags(AConfigFlags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR)
val constraintLayout = ConstraintLayout(context, null)
underTest.addViews(constraintLayout)
assertThat(constraintLayout.childCount).isGreaterThan(0)
}
@Test
- fun addViewsConditionally_migrateFlagOff() {
- mSetFlagsRule.disableFlags(AConfigFlags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR)
- val constraintLayout = ConstraintLayout(context, null)
- underTest.addViews(constraintLayout)
- assertThat(constraintLayout.childCount).isEqualTo(0)
- }
-
- @Test
fun applyConstraints() {
val cs = ConstraintSet()
underTest.applyConstraints(cs)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModelTest.kt
index 9fab60374f8b..70d7a5f2bdc1 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModelTest.kt
@@ -27,7 +27,9 @@ import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionState.RUNNING
import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.keyguard.ui.transitions.PrimaryBouncerTransition
import com.android.systemui.kosmos.testScope
+import com.android.systemui.shade.shadeTestUtil
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -47,6 +49,8 @@ class AlternateBouncerToPrimaryBouncerTransitionViewModelTest : SysuiTestCase()
private val keyguardTransitionRepository by lazy { kosmos.fakeKeyguardTransitionRepository }
private val underTest by lazy { kosmos.alternateBouncerToPrimaryBouncerTransitionViewModel }
+ private val shadeTestUtil by lazy { kosmos.shadeTestUtil }
+
@Test
fun deviceEntryParentViewDisappear() =
testScope.runTest {
@@ -67,13 +71,44 @@ class AlternateBouncerToPrimaryBouncerTransitionViewModelTest : SysuiTestCase()
values.forEach { assertThat(it).isEqualTo(0f) }
}
+ @Test
+ fun blurRadiusGoesToMaximumWhenShadeIsExpanded() =
+ testScope.runTest {
+ val values by collectValues(underTest.windowBlurRadius)
+ kosmos.bouncerWindowBlurTestUtil.shadeExpanded(true)
+
+ kosmos.bouncerWindowBlurTestUtil.assertTransitionToBlurRadius(
+ transitionProgress = listOf(0f, 0f, 0.1f, 0.2f, 0.3f, 1f),
+ startValue = PrimaryBouncerTransition.MAX_BACKGROUND_BLUR_RADIUS,
+ endValue = PrimaryBouncerTransition.MAX_BACKGROUND_BLUR_RADIUS,
+ checkInterpolatedValues = false,
+ transitionFactory = ::step,
+ actualValuesProvider = { values },
+ )
+ }
+
+ @Test
+ fun blurRadiusGoesFromMinToMaxWhenShadeIsNotExpanded() =
+ testScope.runTest {
+ val values by collectValues(underTest.windowBlurRadius)
+ kosmos.bouncerWindowBlurTestUtil.shadeExpanded(false)
+
+ kosmos.bouncerWindowBlurTestUtil.assertTransitionToBlurRadius(
+ transitionProgress = listOf(0f, 0f, 0.1f, 0.2f, 0.3f, 1f),
+ startValue = PrimaryBouncerTransition.MIN_BACKGROUND_BLUR_RADIUS,
+ endValue = PrimaryBouncerTransition.MAX_BACKGROUND_BLUR_RADIUS,
+ transitionFactory = ::step,
+ actualValuesProvider = { values },
+ )
+ }
+
private fun step(value: Float, state: TransitionState = RUNNING): TransitionStep {
return TransitionStep(
from = KeyguardState.ALTERNATE_BOUNCER,
to = KeyguardState.PRIMARY_BOUNCER,
value = value,
transitionState = state,
- ownerName = "AlternateBouncerToPrimaryBouncerTransitionViewModelTest"
+ ownerName = "AlternateBouncerToPrimaryBouncerTransitionViewModelTest",
)
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodToPrimaryBouncerTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodToPrimaryBouncerTransitionViewModelTest.kt
new file mode 100644
index 000000000000..0f239e8472a8
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodToPrimaryBouncerTransitionViewModelTest.kt
@@ -0,0 +1,62 @@
+/*
+ * 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.keyguard.ui.viewmodel
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectValues
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.keyguard.ui.transitions.PrimaryBouncerTransition
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@ExperimentalCoroutinesApi
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class AodToPrimaryBouncerTransitionViewModelTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val underTest by lazy { kosmos.aodToPrimaryBouncerTransitionViewModel }
+
+ @Test
+ fun aodToPrimaryBouncerChangesBlurToMax() =
+ testScope.runTest {
+ val values by collectValues(underTest.windowBlurRadius)
+
+ kosmos.bouncerWindowBlurTestUtil.assertTransitionToBlurRadius(
+ transitionProgress = listOf(0.0f, 0.0f, 0.3f, 0.4f, 0.5f, 1.0f),
+ startValue = PrimaryBouncerTransition.MAX_BACKGROUND_BLUR_RADIUS,
+ endValue = PrimaryBouncerTransition.MAX_BACKGROUND_BLUR_RADIUS,
+ transitionFactory = { value, state ->
+ TransitionStep(
+ from = KeyguardState.AOD,
+ to = KeyguardState.PRIMARY_BOUNCER,
+ value = value,
+ transitionState = state,
+ ownerName = "AodToPrimaryBouncerTransitionViewModelTest",
+ )
+ },
+ actualValuesProvider = { values },
+ checkInterpolatedValues = false,
+ )
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/BouncerWindowBlurTestUtilKosmos.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/BouncerWindowBlurTestUtilKosmos.kt
new file mode 100644
index 000000000000..c3f0deb925e4
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/BouncerWindowBlurTestUtilKosmos.kt
@@ -0,0 +1,87 @@
+/*
+ * 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.keyguard.ui.viewmodel
+
+import android.util.MathUtils
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.StatusBarState
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.TransitionState.RUNNING
+import com.android.systemui.keyguard.shared.model.TransitionState.STARTED
+import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.shade.ShadeTestUtil
+import com.android.systemui.shade.shadeTestUtil
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.TestScope
+
+val Kosmos.bouncerWindowBlurTestUtil by
+ Kosmos.Fixture {
+ BouncerWindowBlurTestUtil(
+ shadeTestUtil = shadeTestUtil,
+ fakeKeyguardTransitionRepository = fakeKeyguardTransitionRepository,
+ fakeKeyguardRepository = fakeKeyguardRepository,
+ testScope = testScope,
+ )
+ }
+
+class BouncerWindowBlurTestUtil(
+ private val shadeTestUtil: ShadeTestUtil,
+ private val fakeKeyguardTransitionRepository: FakeKeyguardTransitionRepository,
+ private val fakeKeyguardRepository: FakeKeyguardRepository,
+ private val testScope: TestScope,
+) {
+
+ suspend fun assertTransitionToBlurRadius(
+ transitionProgress: List<Float>,
+ startValue: Float,
+ endValue: Float,
+ actualValuesProvider: () -> List<Float>,
+ transitionFactory: (value: Float, state: TransitionState) -> TransitionStep,
+ checkInterpolatedValues: Boolean = true,
+ ) {
+ val transitionSteps =
+ listOf(
+ transitionFactory(transitionProgress.first(), STARTED),
+ *transitionProgress.drop(1).map { transitionFactory(it, RUNNING) }.toTypedArray(),
+ )
+ fakeKeyguardTransitionRepository.sendTransitionSteps(transitionSteps, testScope)
+
+ val interpolationFunction = { step: Float -> MathUtils.lerp(startValue, endValue, step) }
+
+ if (checkInterpolatedValues) {
+ assertThat(actualValuesProvider.invoke())
+ .containsExactly(*transitionProgress.map(interpolationFunction).toTypedArray())
+ } else {
+ assertThat(actualValuesProvider.invoke()).contains(endValue)
+ }
+ }
+
+ fun shadeExpanded(expanded: Boolean) {
+ if (expanded) {
+ shadeTestUtil.setQsExpansion(1f)
+ } else {
+ fakeKeyguardRepository.setStatusBarState(StatusBarState.KEYGUARD)
+ shadeTestUtil.setQsExpansion(0f)
+ shadeTestUtil.setLockscreenShadeExpansion(0f)
+ }
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DozingToPrimaryBouncerTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DozingToPrimaryBouncerTransitionViewModelTest.kt
index bf71bec9e1f6..7a68d4eda654 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DozingToPrimaryBouncerTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DozingToPrimaryBouncerTransitionViewModelTest.kt
@@ -26,6 +26,7 @@ import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionState.RUNNING
import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.keyguard.ui.transitions.PrimaryBouncerTransition
import com.android.systemui.kosmos.testScope
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
@@ -70,13 +71,27 @@ class DozingToPrimaryBouncerTransitionViewModelTest : SysuiTestCase() {
values.forEach { assertThat(it).isEqualTo(0f) }
}
+ @Test
+ fun windowBlurRadiusGoesFromMinToMax() =
+ testScope.runTest {
+ val values by collectValues(underTest.windowBlurRadius)
+
+ kosmos.bouncerWindowBlurTestUtil.assertTransitionToBlurRadius(
+ transitionProgress = listOf(0.0f, 0.2f, 0.3f, 0.65f, 0.7f, 1.0f),
+ startValue = PrimaryBouncerTransition.MIN_BACKGROUND_BLUR_RADIUS,
+ endValue = PrimaryBouncerTransition.MAX_BACKGROUND_BLUR_RADIUS,
+ actualValuesProvider = { values },
+ transitionFactory = ::step,
+ )
+ }
+
private fun step(value: Float, state: TransitionState = RUNNING): TransitionStep {
return TransitionStep(
from = KeyguardState.DOZING,
to = KeyguardState.PRIMARY_BOUNCER,
value = value,
transitionState = state,
- ownerName = "DozingToPrimaryBouncerTransitionViewModelTest"
+ ownerName = "DozingToPrimaryBouncerTransitionViewModelTest",
)
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModelTest.kt
index 6f74ed34c4e9..242ee3a783f2 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModelTest.kt
@@ -20,7 +20,6 @@ import android.platform.test.annotations.DisableFlags
import android.platform.test.flag.junit.FlagsParameterization
import androidx.test.filters.SmallTest
import com.android.compose.animation.scene.ObservableTransitionState
-import com.android.systemui.Flags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR
import com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT
import com.android.systemui.SysuiTestCase
import com.android.systemui.common.ui.domain.interactor.configurationInteractor
@@ -31,7 +30,6 @@ import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.doze.util.BurnInHelperWrapper
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.keyguard.domain.interactor.BurnInInteractor
-import com.android.systemui.keyguard.domain.interactor.keyguardBottomAreaInteractor
import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.BurnInModel
@@ -61,8 +59,6 @@ import platform.test.runner.parameterized.Parameters
class KeyguardIndicationAreaViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
-
- private val bottomAreaInteractor = kosmos.keyguardBottomAreaInteractor
private lateinit var underTest: KeyguardIndicationAreaViewModel
private val keyguardRepository = kosmos.fakeKeyguardRepository
private val communalSceneRepository = kosmos.fakeCommunalSceneRepository
@@ -87,12 +83,6 @@ class KeyguardIndicationAreaViewModelTest(flags: FlagsParameterization) : SysuiT
@Before
fun setUp() {
- val bottomAreaViewModel =
- mock<KeyguardBottomAreaViewModel> {
- on { startButton } doReturn startButtonFlow
- on { endButton } doReturn endButtonFlow
- on { alpha } doReturn alphaFlow
- }
val burnInInteractor =
mock<BurnInInteractor> {
on { burnIn(anyInt(), anyInt()) } doReturn flowOf(BurnInModel())
@@ -109,8 +99,6 @@ class KeyguardIndicationAreaViewModelTest(flags: FlagsParameterization) : SysuiT
underTest =
KeyguardIndicationAreaViewModel(
keyguardInteractor = kosmos.keyguardInteractor,
- bottomAreaInteractor = bottomAreaInteractor,
- keyguardBottomAreaViewModel = bottomAreaViewModel,
burnInHelperWrapper = burnInHelperWrapper,
burnInInteractor = burnInInteractor,
shortcutsCombinedViewModel = shortcutsCombinedViewModel,
@@ -123,23 +111,6 @@ class KeyguardIndicationAreaViewModelTest(flags: FlagsParameterization) : SysuiT
}
@Test
- fun alpha() =
- testScope.runTest {
- val alpha by collectLastValue(underTest.alpha)
-
- assertThat(alpha).isEqualTo(1f)
- alphaFlow.value = 0.1f
- assertThat(alpha).isEqualTo(0.1f)
- alphaFlow.value = 0.5f
- assertThat(alpha).isEqualTo(0.5f)
- alphaFlow.value = 0.2f
- assertThat(alpha).isEqualTo(0.2f)
- alphaFlow.value = 0f
- assertThat(alpha).isEqualTo(0f)
- }
-
- @Test
- @DisableFlags(FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR)
fun isIndicationAreaPadded() =
testScope.runTest {
keyguardRepository.setKeyguardShowing(true)
@@ -157,23 +128,6 @@ class KeyguardIndicationAreaViewModelTest(flags: FlagsParameterization) : SysuiT
}
@Test
- @DisableFlags(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT, FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR)
- fun indicationAreaTranslationX() =
- testScope.runTest {
- val translationX by collectLastValue(underTest.indicationAreaTranslationX)
-
- assertThat(translationX).isEqualTo(0f)
- bottomAreaInteractor.setClockPosition(100, 100)
- assertThat(translationX).isEqualTo(100f)
- bottomAreaInteractor.setClockPosition(200, 100)
- assertThat(translationX).isEqualTo(200f)
- bottomAreaInteractor.setClockPosition(200, 200)
- assertThat(translationX).isEqualTo(200f)
- bottomAreaInteractor.setClockPosition(300, 100)
- assertThat(translationX).isEqualTo(300f)
- }
-
- @Test
@DisableFlags(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
fun indicationAreaTranslationY() =
testScope.runTest {
@@ -236,7 +190,6 @@ class KeyguardIndicationAreaViewModelTest(flags: FlagsParameterization) : SysuiT
@Parameters(name = "{0}")
fun getParams(): List<FlagsParameterization> {
return FlagsParameterization.allCombinationsOf(
- FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR,
FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT,
)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
index b5e670c4bbcc..95ffc962797d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
@@ -24,7 +24,6 @@ import android.platform.test.flag.junit.FlagsParameterization
import android.view.View
import androidx.test.filters.SmallTest
import com.android.compose.animation.scene.ObservableTransitionState
-import com.android.systemui.Flags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR
import com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT
import com.android.systemui.SysuiTestCase
import com.android.systemui.communal.data.repository.communalSceneRepository
@@ -74,7 +73,7 @@ import platform.test.runner.parameterized.Parameters
@SmallTest
@RunWith(ParameterizedAndroidJunit4::class)
-@EnableFlags(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT, FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR)
+@EnableFlags(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
class KeyguardRootViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModelTest.kt
index 1c1fcc450d73..fba39970ddb2 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModelTest.kt
@@ -21,6 +21,7 @@ import androidx.test.filters.SmallTest
import com.android.compose.animation.scene.ObservableTransitionState
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.coroutines.collectValues
import com.android.systemui.flags.BrokenWithSceneContainer
import com.android.systemui.flags.Flags
import com.android.systemui.flags.andSceneContainer
@@ -31,6 +32,7 @@ import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.StatusBarState
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.keyguard.ui.transitions.PrimaryBouncerTransition
import com.android.systemui.kosmos.testScope
import com.android.systemui.scene.data.repository.sceneContainerRepository
import com.android.systemui.scene.shared.flag.SceneContainerFlag
@@ -128,7 +130,7 @@ class LockscreenToPrimaryBouncerTransitionViewModelTest(flags: FlagsParameteriza
emptyFlow(),
emptyFlow(),
false,
- emptyFlow()
+ emptyFlow(),
)
runCurrent()
// fade out
@@ -150,6 +152,39 @@ class LockscreenToPrimaryBouncerTransitionViewModelTest(flags: FlagsParameteriza
Truth.assertThat(actual).isEqualTo(0f)
}
+ @Test
+ @BrokenWithSceneContainer(330311871)
+ fun blurRadiusIsMaxWhenShadeIsExpanded() =
+ testScope.runTest {
+ val values by collectValues(underTest.windowBlurRadius)
+ kosmos.bouncerWindowBlurTestUtil.shadeExpanded(true)
+
+ kosmos.bouncerWindowBlurTestUtil.assertTransitionToBlurRadius(
+ transitionProgress = listOf(0.0f, 0.2f, 0.3f, 0.65f, 0.7f, 1.0f),
+ startValue = PrimaryBouncerTransition.MAX_BACKGROUND_BLUR_RADIUS,
+ endValue = PrimaryBouncerTransition.MAX_BACKGROUND_BLUR_RADIUS,
+ actualValuesProvider = { values },
+ transitionFactory = ::step,
+ checkInterpolatedValues = false,
+ )
+ }
+
+ @Test
+ @BrokenWithSceneContainer(330311871)
+ fun blurRadiusGoesFromMinToMaxWhenShadeIsNotExpanded() =
+ testScope.runTest {
+ val values by collectValues(underTest.windowBlurRadius)
+ kosmos.bouncerWindowBlurTestUtil.shadeExpanded(false)
+
+ kosmos.bouncerWindowBlurTestUtil.assertTransitionToBlurRadius(
+ transitionProgress = listOf(0.0f, 0.2f, 0.3f, 0.65f, 0.7f, 1.0f),
+ startValue = PrimaryBouncerTransition.MIN_BACKGROUND_BLUR_RADIUS,
+ endValue = PrimaryBouncerTransition.MAX_BACKGROUND_BLUR_RADIUS,
+ actualValuesProvider = { values },
+ transitionFactory = ::step,
+ )
+ }
+
private fun step(
value: Float,
state: TransitionState = TransitionState.RUNNING,
@@ -161,7 +196,7 @@ class LockscreenToPrimaryBouncerTransitionViewModelTest(flags: FlagsParameteriza
else KeyguardState.PRIMARY_BOUNCER,
value = value,
transitionState = state,
- ownerName = "LockscreenToPrimaryBouncerTransitionViewModelTest"
+ ownerName = "LockscreenToPrimaryBouncerTransitionViewModelTest",
)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToAodTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToAodTransitionViewModelTest.kt
index c55c27c3b516..b406e6cdef37 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToAodTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToAodTransitionViewModelTest.kt
@@ -21,11 +21,13 @@ import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.biometrics.data.repository.fingerprintPropertyRepository
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.coroutines.collectValues
import com.android.systemui.keyguard.data.repository.biometricSettingsRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.keyguard.ui.transitions.PrimaryBouncerTransition
import com.android.systemui.kosmos.testScope
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
@@ -138,16 +140,31 @@ class PrimaryBouncerToAodTransitionViewModelTest : SysuiTestCase() {
assertThat(deviceEntryParentViewAlpha).isNull()
}
+ @Test
+ fun blurRadiusGoesToMinImmediately() =
+ testScope.runTest {
+ val values by collectValues(underTest.windowBlurRadius)
+
+ kosmos.bouncerWindowBlurTestUtil.assertTransitionToBlurRadius(
+ transitionProgress = listOf(0.0f, 0.2f, 0.3f, 0.65f, 0.7f, 1.0f),
+ startValue = PrimaryBouncerTransition.MIN_BACKGROUND_BLUR_RADIUS,
+ endValue = PrimaryBouncerTransition.MIN_BACKGROUND_BLUR_RADIUS,
+ actualValuesProvider = { values },
+ transitionFactory = ::step,
+ checkInterpolatedValues = false,
+ )
+ }
+
private fun step(
value: Float,
- state: TransitionState = TransitionState.RUNNING
+ state: TransitionState = TransitionState.RUNNING,
): TransitionStep {
return TransitionStep(
from = KeyguardState.PRIMARY_BOUNCER,
to = KeyguardState.AOD,
value = value,
transitionState = state,
- ownerName = "PrimaryBouncerToAodTransitionViewModelTest"
+ ownerName = "PrimaryBouncerToAodTransitionViewModelTest",
)
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToDozingTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToDozingTransitionViewModelTest.kt
index 28473b204ce3..a8f0f2f453df 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToDozingTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToDozingTransitionViewModelTest.kt
@@ -30,6 +30,7 @@ import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionState.RUNNING
import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.keyguard.ui.transitions.PrimaryBouncerTransition
import com.android.systemui.kosmos.testScope
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
@@ -122,13 +123,28 @@ class PrimaryBouncerToDozingTransitionViewModelTest : SysuiTestCase() {
values.forEach { assertThat(it).isEqualTo(0f) }
}
+ @Test
+ fun blurRadiusGoesToMinImmediately() =
+ testScope.runTest {
+ val values by collectValues(underTest.windowBlurRadius)
+
+ kosmos.bouncerWindowBlurTestUtil.assertTransitionToBlurRadius(
+ transitionProgress = listOf(0.0f, 0.2f, 0.3f, 0.65f, 0.7f, 1.0f),
+ startValue = PrimaryBouncerTransition.MIN_BACKGROUND_BLUR_RADIUS,
+ endValue = PrimaryBouncerTransition.MIN_BACKGROUND_BLUR_RADIUS,
+ actualValuesProvider = { values },
+ transitionFactory = ::step,
+ checkInterpolatedValues = false,
+ )
+ }
+
private fun step(value: Float, state: TransitionState = RUNNING): TransitionStep {
return TransitionStep(
from = KeyguardState.PRIMARY_BOUNCER,
to = KeyguardState.DOZING,
value = value,
transitionState = state,
- ownerName = "PrimaryBouncerToDozingTransitionViewModelTest"
+ ownerName = "PrimaryBouncerToDozingTransitionViewModelTest",
)
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGlanceableHubTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGlanceableHubTransitionViewModelTest.kt
new file mode 100644
index 000000000000..2c6e553dae90
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGlanceableHubTransitionViewModelTest.kt
@@ -0,0 +1,65 @@
+/*
+ * 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.keyguard.ui.viewmodel
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectValues
+import com.android.systemui.flags.DisableSceneContainer
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.keyguard.ui.transitions.PrimaryBouncerTransition
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@ExperimentalCoroutinesApi
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class PrimaryBouncerToGlanceableHubTransitionViewModelTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val underTest by lazy { kosmos.primaryBouncerToGlanceableHubTransitionViewModel }
+
+ @Test
+ @DisableSceneContainer
+ fun blurBecomesMinValueImmediately() =
+ testScope.runTest {
+ val values by collectValues(underTest.windowBlurRadius)
+
+ kosmos.bouncerWindowBlurTestUtil.assertTransitionToBlurRadius(
+ transitionProgress = listOf(0.0f, 0.2f, 0.3f, 0.65f, 0.7f, 1.0f),
+ startValue = PrimaryBouncerTransition.MIN_BACKGROUND_BLUR_RADIUS,
+ endValue = PrimaryBouncerTransition.MIN_BACKGROUND_BLUR_RADIUS,
+ actualValuesProvider = { values },
+ transitionFactory = { step, transitionState ->
+ TransitionStep(
+ from = KeyguardState.PRIMARY_BOUNCER,
+ to = KeyguardState.GLANCEABLE_HUB,
+ value = step,
+ transitionState = transitionState,
+ ownerName = "PrimaryBouncerToGlanceableHubTransitionViewModelTest",
+ )
+ },
+ checkInterpolatedValues = false,
+ )
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModelTest.kt
index 5ec566bab6d5..9e5976f914ed 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModelTest.kt
@@ -21,11 +21,13 @@ import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.biometrics.data.repository.fingerprintPropertyRepository
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.coroutines.collectValues
import com.android.systemui.keyguard.data.repository.biometricSettingsRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.keyguard.ui.transitions.PrimaryBouncerTransition
import com.android.systemui.kosmos.testScope
import com.android.systemui.testKosmos
import com.google.common.collect.Range
@@ -110,16 +112,47 @@ class PrimaryBouncerToLockscreenTransitionViewModelTest : SysuiTestCase() {
assertThat(bgViewAlpha).isEqualTo(1f)
}
+ @Test
+ fun blurRadiusGoesFromMaxToMinWhenShadeIsNotExpanded() =
+ testScope.runTest {
+ val values by collectValues(underTest.windowBlurRadius)
+ kosmos.bouncerWindowBlurTestUtil.shadeExpanded(false)
+
+ kosmos.bouncerWindowBlurTestUtil.assertTransitionToBlurRadius(
+ transitionProgress = listOf(0.0f, 0.2f, 0.3f, 0.65f, 0.7f, 1.0f),
+ startValue = PrimaryBouncerTransition.MAX_BACKGROUND_BLUR_RADIUS,
+ endValue = PrimaryBouncerTransition.MIN_BACKGROUND_BLUR_RADIUS,
+ actualValuesProvider = { values },
+ transitionFactory = ::step,
+ )
+ }
+
+ @Test
+ fun blurRadiusRemainsAtMaxWhenShadeIsExpanded() =
+ testScope.runTest {
+ val values by collectValues(underTest.windowBlurRadius)
+ kosmos.bouncerWindowBlurTestUtil.shadeExpanded(true)
+
+ kosmos.bouncerWindowBlurTestUtil.assertTransitionToBlurRadius(
+ transitionProgress = listOf(0.0f, 0.2f, 0.3f, 0.65f, 0.7f, 1.0f),
+ startValue = PrimaryBouncerTransition.MAX_BACKGROUND_BLUR_RADIUS,
+ endValue = PrimaryBouncerTransition.MAX_BACKGROUND_BLUR_RADIUS,
+ actualValuesProvider = { values },
+ transitionFactory = ::step,
+ checkInterpolatedValues = false,
+ )
+ }
+
private fun step(
value: Float,
- state: TransitionState = TransitionState.RUNNING
+ state: TransitionState = TransitionState.RUNNING,
): TransitionStep {
return TransitionStep(
from = KeyguardState.PRIMARY_BOUNCER,
to = KeyguardState.LOCKSCREEN,
value = value,
transitionState = state,
- ownerName = "PrimaryBouncerToLockscreenTransitionViewModelTest"
+ ownerName = "PrimaryBouncerToLockscreenTransitionViewModelTest",
)
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/shared/flag/SceneContainerFlagParameterizationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/shared/flag/SceneContainerFlagParameterizationTest.kt
index f86337ec63dc..396f531b7e1b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/shared/flag/SceneContainerFlagParameterizationTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/shared/flag/SceneContainerFlagParameterizationTest.kt
@@ -20,7 +20,7 @@ import android.platform.test.flag.junit.FlagsParameterization
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.Flags.FLAG_EXAMPLE_FLAG
-import com.android.systemui.Flags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR
+import com.android.systemui.Flags.FLAG_NOTIFICATION_AVALANCHE_THROTTLE_HUN
import com.android.systemui.Flags.FLAG_SCENE_CONTAINER
import com.android.systemui.SysuiTestCase
import com.android.systemui.flags.andSceneContainer
@@ -66,7 +66,7 @@ internal class SceneContainerFlagParameterizationTest : SysuiTestCase() {
@Test
fun oneDependencyAndSceneContainer() {
- val dependentFlag = FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR
+ val dependentFlag = FLAG_NOTIFICATION_AVALANCHE_THROTTLE_HUN
val result = FlagsParameterization.allCombinationsOf(dependentFlag).andSceneContainer()
Truth.assertThat(result).hasSize(3)
Truth.assertThat(result[0].mOverrides[dependentFlag]).isFalse()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
index 0d8d57e52dbf..d3b58287e961 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
@@ -68,7 +68,6 @@ import com.android.internal.logging.UiEventLogger;
import com.android.internal.logging.testing.UiEventLoggerFake;
import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.util.LatencyTracker;
-import com.android.keyguard.EmptyLockIconViewController;
import com.android.keyguard.KeyguardClockSwitch;
import com.android.keyguard.KeyguardClockSwitchController;
import com.android.keyguard.KeyguardSliceViewController;
@@ -99,7 +98,6 @@ import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
import com.android.systemui.keyguard.KeyguardViewConfigurator;
import com.android.systemui.keyguard.data.repository.FakeKeyguardClockRepository;
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository;
-import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory;
@@ -108,7 +106,6 @@ import com.android.systemui.keyguard.domain.interactor.NaturalScrollingSettingOb
import com.android.systemui.keyguard.ui.view.KeyguardRootView;
import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel;
import com.android.systemui.keyguard.ui.viewmodel.GoneToDreamingTransitionViewModel;
-import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel;
import com.android.systemui.keyguard.ui.viewmodel.KeyguardTouchHandlingViewModel;
import com.android.systemui.keyguard.ui.viewmodel.LockscreenToDreamingTransitionViewModel;
import com.android.systemui.keyguard.ui.viewmodel.LockscreenToOccludedTransitionViewModel;
@@ -167,8 +164,6 @@ import com.android.systemui.statusbar.phone.CentralSurfaces;
import com.android.systemui.statusbar.phone.ConfigurationControllerImpl;
import com.android.systemui.statusbar.phone.DozeParameters;
import com.android.systemui.statusbar.phone.HeadsUpAppearanceController;
-import com.android.systemui.statusbar.phone.KeyguardBottomAreaView;
-import com.android.systemui.statusbar.phone.KeyguardBottomAreaViewController;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.phone.KeyguardClockPositionAlgorithm;
import com.android.systemui.statusbar.phone.KeyguardStatusBarView;
@@ -228,10 +223,7 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase {
@Mock protected CentralSurfaces mCentralSurfaces;
@Mock protected NotificationStackScrollLayout mNotificationStackScrollLayout;
- @Mock protected KeyguardBottomAreaView mKeyguardBottomArea;
- @Mock protected KeyguardBottomAreaViewController mKeyguardBottomAreaViewController;
@Mock protected ViewPropertyAnimator mViewPropertyAnimator;
- @Mock protected KeyguardBottomAreaView mQsFrame;
@Mock protected HeadsUpManager mHeadsUpManager;
@Mock protected NotificationGutsManager mGutsManager;
@Mock protected KeyguardStatusBarView mKeyguardStatusBar;
@@ -270,7 +262,6 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase {
@Mock protected KeyguardUserSwitcherController mKeyguardUserSwitcherController;
@Mock protected KeyguardStatusViewComponent mKeyguardStatusViewComponent;
@Mock protected KeyguardStatusBarViewComponent.Factory mKeyguardStatusBarViewComponentFactory;
- @Mock protected EmptyLockIconViewController mLockIconViewController;
@Mock protected KeyguardStatusBarViewComponent mKeyguardStatusBarViewComponent;
@Mock protected KeyguardClockSwitchController mKeyguardClockSwitchController;
@Mock protected KeyguardStatusBarViewController mKeyguardStatusBarViewController;
@@ -317,7 +308,6 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase {
@Mock protected ViewGroup mQsHeader;
@Mock protected ViewParent mViewParent;
@Mock protected ViewTreeObserver mViewTreeObserver;
- @Mock protected KeyguardBottomAreaViewModel mKeyguardBottomAreaViewModel;
@Mock protected DreamingToLockscreenTransitionViewModel
mDreamingToLockscreenTransitionViewModel;
@Mock protected OccludedToLockscreenTransitionViewModel
@@ -352,7 +342,6 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase {
@Mock private StatusBarLongPressGestureDetector mStatusBarLongPressGestureDetector;
protected final int mMaxUdfpsBurnInOffsetY = 5;
protected FakeFeatureFlagsClassic mFeatureFlags = new FakeFeatureFlagsClassic();
- protected KeyguardBottomAreaInteractor mKeyguardBottomAreaInteractor;
protected KeyguardClockInteractor mKeyguardClockInteractor;
protected FakeKeyguardRepository mFakeKeyguardRepository;
protected FakeKeyguardClockRepository mFakeKeyguardClockRepository;
@@ -397,13 +386,10 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase {
mFeatureFlags.set(Flags.LOCKSCREEN_ENABLE_LANDSCAPE, false);
mFeatureFlags.set(Flags.QS_USER_DETAIL_SHORTCUT, false);
- mSetFlagsRule.disableFlags(com.android.systemui.Flags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR);
-
mMainDispatcher = getMainDispatcher();
KeyguardInteractorFactory.WithDependencies keyguardInteractorDeps =
KeyguardInteractorFactory.create();
mFakeKeyguardRepository = keyguardInteractorDeps.getRepository();
- mKeyguardBottomAreaInteractor = new KeyguardBottomAreaInteractor(mFakeKeyguardRepository);
mFakeKeyguardClockRepository = new FakeKeyguardClockRepository();
mKeyguardClockInteractor = mKosmos.getKeyguardClockInteractor();
mKeyguardInteractor = keyguardInteractorDeps.getKeyguardInteractor();
@@ -500,9 +486,6 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase {
when(mNotificationStackScrollLayoutController.getHeight()).thenReturn(1000);
when(mNotificationStackScrollLayoutController.getHeadsUpCallback())
.thenReturn(mHeadsUpCallback);
- when(mKeyguardBottomAreaViewController.getView()).thenReturn(mKeyguardBottomArea);
- when(mView.findViewById(R.id.keyguard_bottom_area)).thenReturn(mKeyguardBottomArea);
- when(mKeyguardBottomArea.animate()).thenReturn(mViewPropertyAnimator);
when(mView.animate()).thenReturn(mViewPropertyAnimator);
when(mKeyguardStatusView.animate()).thenReturn(mViewPropertyAnimator);
when(mViewPropertyAnimator.translationX(anyFloat())).thenReturn(mViewPropertyAnimator);
@@ -513,7 +496,6 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase {
when(mViewPropertyAnimator.setListener(any())).thenReturn(mViewPropertyAnimator);
when(mViewPropertyAnimator.setUpdateListener(any())).thenReturn(mViewPropertyAnimator);
when(mViewPropertyAnimator.withEndAction(any())).thenReturn(mViewPropertyAnimator);
- when(mView.findViewById(R.id.qs_frame)).thenReturn(mQsFrame);
when(mView.findViewById(R.id.keyguard_status_view))
.thenReturn(mock(KeyguardStatusView.class));
ViewGroup rootView = mock(ViewGroup.class);
@@ -647,8 +629,6 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase {
.thenReturn(keyguardStatusView);
when(mLayoutInflater.inflate(eq(R.layout.keyguard_user_switcher), any(), anyBoolean()))
.thenReturn(mUserSwitcherView);
- when(mLayoutInflater.inflate(eq(R.layout.keyguard_bottom_area), any(), anyBoolean()))
- .thenReturn(mKeyguardBottomArea);
when(mNotificationRemoteInputManager.isRemoteInputActive())
.thenReturn(false);
doAnswer(invocation -> {
@@ -720,7 +700,6 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase {
mMediaDataManager,
mNotificationShadeDepthController,
mAmbientState,
- mLockIconViewController,
mKeyguardMediaController,
mTapAgainViewController,
mNavigationModeController,
@@ -736,15 +715,12 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase {
mShadeRepository,
mSysUIUnfoldComponent,
mSysUiState,
- () -> mKeyguardBottomAreaViewController,
mKeyguardUnlockAnimationController,
mKeyguardIndicationController,
mNotificationListContainer,
mNotificationStackSizeCalculator,
mUnlockedScreenOffAnimationController,
systemClock,
- mKeyguardBottomAreaViewModel,
- mKeyguardBottomAreaInteractor,
mKeyguardClockInteractor,
mAlternateBouncerInteractor,
mDreamingToLockscreenTransitionViewModel,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerWithCoroutinesTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerWithCoroutinesTest.kt
index 97441f01bcf5..5289554e9e18 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerWithCoroutinesTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerWithCoroutinesTest.kt
@@ -28,7 +28,6 @@ import androidx.test.filters.SmallTest
import com.android.internal.util.CollectionUtils
import com.android.keyguard.KeyguardClockSwitch.LARGE
import com.android.systemui.Flags
-import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.res.R
import com.android.systemui.statusbar.StatusBarState.KEYGUARD
import com.android.systemui.statusbar.StatusBarState.SHADE
@@ -215,31 +214,4 @@ class NotificationPanelViewControllerWithCoroutinesTest :
}
advanceUntilIdle()
}
-
- @Test
- fun onLayoutChange_shadeCollapsed_bottomAreaAlphaIsZero() = runTest {
- // GIVEN bottomAreaShadeAlpha was updated before
- mNotificationPanelViewController.maybeAnimateBottomAreaAlpha()
-
- // WHEN a layout change is triggered with the shade being closed
- triggerLayoutChange()
-
- // THEN the bottomAreaAlpha is zero
- val bottomAreaAlpha by collectLastValue(mFakeKeyguardRepository.bottomAreaAlpha)
- assertThat(bottomAreaAlpha).isEqualTo(0f)
- }
-
- @Test
- fun onShadeExpanded_bottomAreaAlphaIsFullyOpaque() = runTest {
- // GIVEN bottomAreaShadeAlpha was updated before
- mNotificationPanelViewController.maybeAnimateBottomAreaAlpha()
-
- // WHEN the shade expanded
- val transitionDistance = mNotificationPanelViewController.maxPanelTransitionDistance
- mNotificationPanelViewController.expandedHeight = transitionDistance.toFloat()
-
- // THEN the bottomAreaAlpha is fully opaque
- val bottomAreaAlpha by collectLastValue(mFakeKeyguardRepository.bottomAreaAlpha)
- assertThat(bottomAreaAlpha).isEqualTo(1f)
- }
}
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/chips/notification/domain/interactor/SingleNotificationChipInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/domain/interactor/SingleNotificationChipInteractorTest.kt
index 63efc55e1a09..9ad1f409a8ea 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/domain/interactor/SingleNotificationChipInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/domain/interactor/SingleNotificationChipInteractorTest.kt
@@ -26,7 +26,6 @@ import com.android.systemui.kosmos.collectLastValue
import com.android.systemui.kosmos.runTest
import com.android.systemui.kosmos.useUnconfinedTestDispatcher
import com.android.systemui.statusbar.StatusBarIconView
-import com.android.systemui.statusbar.chips.notification.domain.model.NotificationChipModel
import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
import com.android.systemui.statusbar.notification.data.model.activeNotificationModel
import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel
@@ -50,7 +49,6 @@ class SingleNotificationChipInteractorTest : SysuiTestCase() {
activeNotificationModel(
key = "notif1",
statusBarChipIcon = icon,
- whenTime = 5432,
promotedContent = PROMOTED_CONTENT,
)
@@ -60,7 +58,7 @@ class SingleNotificationChipInteractorTest : SysuiTestCase() {
assertThat(latest!!.key).isEqualTo("notif1")
assertThat(latest!!.statusBarChipIconView).isEqualTo(icon)
- assertThat(latest!!.whenTime).isEqualTo(5432)
+ assertThat(latest!!.promotedContent).isEqualTo(PROMOTED_CONTENT)
}
@Test
@@ -83,14 +81,12 @@ class SingleNotificationChipInteractorTest : SysuiTestCase() {
activeNotificationModel(
key = "notif1",
statusBarChipIcon = newIconView,
- whenTime = 6543,
promotedContent = PROMOTED_CONTENT,
)
)
assertThat(latest!!.key).isEqualTo("notif1")
assertThat(latest!!.statusBarChipIconView).isEqualTo(newIconView)
- assertThat(latest!!.whenTime).isEqualTo(6543)
}
@Test
@@ -174,22 +170,14 @@ class SingleNotificationChipInteractorTest : SysuiTestCase() {
activeNotificationModel(
key = "notif1",
statusBarChipIcon = null,
- whenTime = 123L,
promotedContent = PROMOTED_CONTENT,
)
)
val latest by collectLastValue(underTest.notificationChip)
- assertThat(latest)
- .isEqualTo(
- NotificationChipModel(
- "notif1",
- statusBarChipIconView = null,
- whenTime = 123L,
- promotedContent = PROMOTED_CONTENT,
- )
- )
+ assertThat(latest).isNotNull()
+ assertThat(latest!!.key).isEqualTo("notif1")
}
@Test
@@ -234,20 +222,12 @@ class SingleNotificationChipInteractorTest : SysuiTestCase() {
activeNotificationModel(
key = "notif1",
statusBarChipIcon = null,
- whenTime = 123L,
promotedContent = PROMOTED_CONTENT,
)
)
- assertThat(latest)
- .isEqualTo(
- NotificationChipModel(
- key = "notif1",
- statusBarChipIconView = null,
- whenTime = 123L,
- promotedContent = PROMOTED_CONTENT,
- )
- )
+ assertThat(latest).isNotNull()
+ assertThat(latest!!.key).isEqualTo("notif1")
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt
index d4910cec3df4..165e943a0cc0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt
@@ -116,7 +116,7 @@ class NotifChipsViewModelTest : SysuiTestCase() {
assertThat(latest).hasSize(1)
val chip = latest!![0]
- assertThat(chip).isInstanceOf(OngoingActivityChipModel.Shown.ShortTimeDelta::class.java)
+ assertThat(chip).isInstanceOf(OngoingActivityChipModel.Shown::class.java)
assertThat(chip.icon).isEqualTo(OngoingActivityChipModel.ChipIcon.StatusBarView(icon))
}
@@ -139,7 +139,7 @@ class NotifChipsViewModelTest : SysuiTestCase() {
assertThat(latest).hasSize(1)
val chip = latest!![0]
- assertThat(chip).isInstanceOf(OngoingActivityChipModel.Shown.ShortTimeDelta::class.java)
+ assertThat(chip).isInstanceOf(OngoingActivityChipModel.Shown::class.java)
assertThat(chip.icon)
.isEqualTo(OngoingActivityChipModel.ChipIcon.StatusBarNotificationIcon(notifKey))
}
@@ -242,15 +242,158 @@ class NotifChipsViewModelTest : SysuiTestCase() {
}
@Test
+ fun chips_hasShortCriticalText_usesTextInsteadOfTime() =
+ kosmos.runTest {
+ val latest by collectLastValue(underTest.chips)
+
+ val promotedContentBuilder =
+ PromotedNotificationContentModel.Builder("notif").apply {
+ this.shortCriticalText = "Arrived"
+ this.time =
+ PromotedNotificationContentModel.When(
+ time = 6543L,
+ mode = PromotedNotificationContentModel.When.Mode.BasicTime,
+ )
+ }
+ setNotifs(
+ listOf(
+ activeNotificationModel(
+ key = "notif",
+ statusBarChipIcon = mock<StatusBarIconView>(),
+ promotedContent = promotedContentBuilder.build(),
+ )
+ )
+ )
+
+ assertThat(latest).hasSize(1)
+ assertThat(latest!![0]).isInstanceOf(OngoingActivityChipModel.Shown.Text::class.java)
+ assertThat((latest!![0] as OngoingActivityChipModel.Shown.Text).text)
+ .isEqualTo("Arrived")
+ }
+
+ @Test
+ fun chips_noTime_isIconOnly() =
+ kosmos.runTest {
+ val latest by collectLastValue(underTest.chips)
+
+ val promotedContentBuilder =
+ PromotedNotificationContentModel.Builder("notif").apply { this.time = null }
+ setNotifs(
+ listOf(
+ activeNotificationModel(
+ key = "notif",
+ statusBarChipIcon = mock<StatusBarIconView>(),
+ promotedContent = promotedContentBuilder.build(),
+ )
+ )
+ )
+
+ assertThat(latest).hasSize(1)
+ assertThat(latest!![0])
+ .isInstanceOf(OngoingActivityChipModel.Shown.IconOnly::class.java)
+ }
+
+ @Test
+ fun chips_basicTime_isShortTimeDelta() =
+ kosmos.runTest {
+ val latest by collectLastValue(underTest.chips)
+
+ val promotedContentBuilder =
+ PromotedNotificationContentModel.Builder("notif").apply {
+ this.time =
+ PromotedNotificationContentModel.When(
+ time = 6543L,
+ mode = PromotedNotificationContentModel.When.Mode.BasicTime,
+ )
+ }
+ setNotifs(
+ listOf(
+ activeNotificationModel(
+ key = "notif",
+ statusBarChipIcon = mock<StatusBarIconView>(),
+ promotedContent = promotedContentBuilder.build(),
+ )
+ )
+ )
+
+ assertThat(latest).hasSize(1)
+ assertThat(latest!![0])
+ .isInstanceOf(OngoingActivityChipModel.Shown.ShortTimeDelta::class.java)
+ }
+
+ @Test
+ fun chips_countUpTime_isTimer() =
+ kosmos.runTest {
+ val latest by collectLastValue(underTest.chips)
+
+ val promotedContentBuilder =
+ PromotedNotificationContentModel.Builder("notif").apply {
+ this.time =
+ PromotedNotificationContentModel.When(
+ time = 6543L,
+ mode = PromotedNotificationContentModel.When.Mode.CountUp,
+ )
+ }
+ setNotifs(
+ listOf(
+ activeNotificationModel(
+ key = "notif",
+ statusBarChipIcon = mock<StatusBarIconView>(),
+ promotedContent = promotedContentBuilder.build(),
+ )
+ )
+ )
+
+ assertThat(latest).hasSize(1)
+ assertThat(latest!![0]).isInstanceOf(OngoingActivityChipModel.Shown.Timer::class.java)
+ }
+
+ @Test
+ fun chips_countDownTime_isTimer() =
+ kosmos.runTest {
+ val latest by collectLastValue(underTest.chips)
+
+ val promotedContentBuilder =
+ PromotedNotificationContentModel.Builder("notif").apply {
+ this.time =
+ PromotedNotificationContentModel.When(
+ time = 6543L,
+ mode = PromotedNotificationContentModel.When.Mode.CountDown,
+ )
+ }
+ setNotifs(
+ listOf(
+ activeNotificationModel(
+ key = "notif",
+ statusBarChipIcon = mock<StatusBarIconView>(),
+ promotedContent = promotedContentBuilder.build(),
+ )
+ )
+ )
+
+ assertThat(latest).hasSize(1)
+ assertThat(latest!![0]).isInstanceOf(OngoingActivityChipModel.Shown.Timer::class.java)
+ }
+
+ @Test
fun chips_noHeadsUp_showsTime() =
kosmos.runTest {
val latest by collectLastValue(underTest.chips)
+
+ val promotedContentBuilder =
+ PromotedNotificationContentModel.Builder("notif").apply {
+ this.time =
+ PromotedNotificationContentModel.When(
+ time = 6543L,
+ mode = PromotedNotificationContentModel.When.Mode.BasicTime,
+ )
+ }
setNotifs(
listOf(
activeNotificationModel(
key = "notif",
statusBarChipIcon = mock<StatusBarIconView>(),
- promotedContent = PromotedNotificationContentModel.Builder("notif").build(),
+ promotedContent = promotedContentBuilder.build(),
)
)
)
@@ -267,12 +410,21 @@ class NotifChipsViewModelTest : SysuiTestCase() {
fun chips_hasHeadsUpByUser_onlyShowsIcon() =
kosmos.runTest {
val latest by collectLastValue(underTest.chips)
+
+ val promotedContentBuilder =
+ PromotedNotificationContentModel.Builder("notif").apply {
+ this.time =
+ PromotedNotificationContentModel.When(
+ time = 6543L,
+ mode = PromotedNotificationContentModel.When.Mode.BasicTime,
+ )
+ }
setNotifs(
listOf(
activeNotificationModel(
key = "notif",
statusBarChipIcon = mock<StatusBarIconView>(),
- promotedContent = PromotedNotificationContentModel.Builder("notif").build(),
+ promotedContent = promotedContentBuilder.build(),
)
)
)
@@ -324,15 +476,11 @@ class NotifChipsViewModelTest : SysuiTestCase() {
companion object {
fun assertIsNotifChip(latest: OngoingActivityChipModel?, expectedIcon: StatusBarIconView) {
- assertThat(latest)
- .isInstanceOf(OngoingActivityChipModel.Shown.ShortTimeDelta::class.java)
assertThat((latest as OngoingActivityChipModel.Shown).icon)
.isEqualTo(OngoingActivityChipModel.ChipIcon.StatusBarView(expectedIcon))
}
fun assertIsNotifKey(latest: OngoingActivityChipModel?, expectedKey: String) {
- assertThat(latest)
- .isInstanceOf(OngoingActivityChipModel.Shown.ShortTimeDelta::class.java)
assertThat((latest as OngoingActivityChipModel.Shown).icon)
.isEqualTo(OngoingActivityChipModel.ChipIcon.StatusBarNotificationIcon(expectedKey))
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractorTest.kt
index 6736ccf739ce..abd0a284ae3d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractorTest.kt
@@ -101,6 +101,7 @@ class PromotedNotificationContentExtractorTest : SysuiTestCase() {
setSubText(TEST_SUB_TEXT)
setContentTitle(TEST_CONTENT_TITLE)
setContentText(TEST_CONTENT_TEXT)
+ setShortCriticalText(TEST_SHORT_CRITICAL_TEXT)
}
.also { provider.promotedEntries.add(it) }
@@ -114,6 +115,52 @@ class PromotedNotificationContentExtractorTest : SysuiTestCase() {
@Test
@EnableFlags(PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME)
+ @DisableFlags(android.app.Flags.FLAG_API_RICH_ONGOING)
+ fun extractContent_apiFlagOff_shortCriticalTextNotExtracted() {
+ val entry =
+ createEntry { setShortCriticalText(TEST_SHORT_CRITICAL_TEXT) }
+ .also { provider.promotedEntries.add(it) }
+
+ val content = extractContent(entry)
+
+ assertThat(content).isNotNull()
+ assertThat(content?.text).isNull()
+ }
+
+ @Test
+ @EnableFlags(
+ PromotedNotificationUi.FLAG_NAME,
+ StatusBarNotifChips.FLAG_NAME,
+ android.app.Flags.FLAG_API_RICH_ONGOING,
+ )
+ fun extractContent_apiFlagOn_shortCriticalTextExtracted() {
+ val entry =
+ createEntry { setShortCriticalText(TEST_SHORT_CRITICAL_TEXT) }
+ .also { provider.promotedEntries.add(it) }
+
+ val content = extractContent(entry)
+
+ assertThat(content).isNotNull()
+ assertThat(content?.shortCriticalText).isEqualTo(TEST_SHORT_CRITICAL_TEXT)
+ }
+
+ @Test
+ @EnableFlags(
+ PromotedNotificationUi.FLAG_NAME,
+ StatusBarNotifChips.FLAG_NAME,
+ android.app.Flags.FLAG_API_RICH_ONGOING,
+ )
+ fun extractContent_noShortCriticalTextSet_textIsNull() {
+ val entry = createEntry {}.also { provider.promotedEntries.add(it) }
+
+ val content = extractContent(entry)
+
+ assertThat(content).isNotNull()
+ assertThat(content?.shortCriticalText).isNull()
+ }
+
+ @Test
+ @EnableFlags(PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME)
fun extractContent_fromBigPictureStyle() {
val entry =
createEntry { setStyle(BigPictureStyle()) }.also { provider.promotedEntries.add(it) }
@@ -201,6 +248,7 @@ class PromotedNotificationContentExtractorTest : SysuiTestCase() {
private const val TEST_SUB_TEXT = "sub text"
private const val TEST_CONTENT_TITLE = "content title"
private const val TEST_CONTENT_TEXT = "content text"
+ private const val TEST_SHORT_CRITICAL_TEXT = "short"
private const val TEST_PERSON_NAME = "person name"
private const val TEST_PERSON_KEY = "person key"
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/plugin/src/com/android/systemui/plugins/clocks/ClockFaceLayout.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockFaceLayout.kt
index fb5ef02aa06a..843afb8d74a8 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockFaceLayout.kt
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockFaceLayout.kt
@@ -115,14 +115,25 @@ class DefaultClockFaceLayout(val view: View) : ClockFaceLayout {
// and we're not planning to add this vide in clockHostView
// so we only need position of device entry icon to constrain clock
// Copied calculation codes from applyConstraints in DefaultDeviceEntrySection
- val bottomPaddingPx = getDimen(context, "lock_icon_margin_bottom")
- val defaultDensity =
- DisplayMetrics.DENSITY_DEVICE_STABLE.toFloat() /
- DisplayMetrics.DENSITY_DEFAULT.toFloat()
- val lockIconRadiusPx = (defaultDensity * 36).toInt()
- val clockBottomMargin = bottomPaddingPx + 2 * lockIconRadiusPx
-
- connect(lockscreenClockViewLargeId, BOTTOM, PARENT_ID, BOTTOM, clockBottomMargin)
+ clockPreviewConfig.lockId?.let { lockId ->
+ connect(lockscreenClockViewLargeId, BOTTOM, lockId, TOP)
+ }
+ ?: run {
+ val bottomPaddingPx = getDimen(context, "lock_icon_margin_bottom")
+ val defaultDensity =
+ DisplayMetrics.DENSITY_DEVICE_STABLE.toFloat() /
+ DisplayMetrics.DENSITY_DEFAULT.toFloat()
+ val lockIconRadiusPx = (defaultDensity * 36).toInt()
+ val clockBottomMargin = bottomPaddingPx + 2 * lockIconRadiusPx
+ connect(
+ lockscreenClockViewLargeId,
+ BOTTOM,
+ PARENT_ID,
+ BOTTOM,
+ clockBottomMargin,
+ )
+ }
+
val smallClockViewId = getId(context, "lockscreen_clock_view")
constrainWidth(smallClockViewId, WRAP_CONTENT)
constrainHeight(smallClockViewId, getDimen(context, "small_clock_height"))
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockPreviewConfig.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockPreviewConfig.kt
index 544b705c55c5..94e669fc0ea2 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockPreviewConfig.kt
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockPreviewConfig.kt
@@ -22,4 +22,5 @@ data class ClockPreviewConfig(
val previewContext: Context,
val isShadeLayoutWide: Boolean,
val isSceneContainerFlagEnabled: Boolean = false,
+ val lockId: Int? = null,
)
diff --git a/packages/SystemUI/res/layout/ongoing_activity_chip.xml b/packages/SystemUI/res/layout/ongoing_activity_chip.xml
index 215e4e457060..7745af9dd408 100644
--- a/packages/SystemUI/res/layout/ongoing_activity_chip.xml
+++ b/packages/SystemUI/res/layout/ongoing_activity_chip.xml
@@ -49,27 +49,25 @@
ongoing_activity_chip_short_time_delta] will ever be shown at one time. -->
<!-- Shows a timer, like 00:01. -->
+ <!-- Don't use the LimitedWidth style for the timer because the end of the timer is often
+ the most important value. ChipChronometer has the correct logic for when the timer is
+ too large for the space allowed. -->
<com.android.systemui.statusbar.chips.ui.view.ChipChronometer
android:id="@+id/ongoing_activity_chip_time"
style="@style/StatusBar.Chip.Text"
/>
<!-- Shows generic text. -->
- <!-- Since there's so little room in the status bar chip area, don't ellipsize the text and
- instead just fade it out a bit at the end. -->
<TextView
android:id="@+id/ongoing_activity_chip_text"
- style="@style/StatusBar.Chip.Text"
- android:ellipsize="none"
- android:requiresFadingEdge="horizontal"
- android:fadingEdgeLength="@dimen/ongoing_activity_chip_text_fading_edge_length"
+ style="@style/StatusBar.Chip.Text.LimitedWidth"
android:visibility="gone"
/>
<!-- Shows a time delta in short form, like "15min" or "1hr". -->
<android.widget.DateTimeView
android:id="@+id/ongoing_activity_chip_short_time_delta"
- style="@style/StatusBar.Chip.Text"
+ style="@style/StatusBar.Chip.Text.LimitedWidth"
android:visibility="gone"
/>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 5894f297d2a7..35cfd082c537 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1760,6 +1760,7 @@
<dimen name="wallet_button_vertical_padding">8dp</dimen>
<!-- Ongoing activity chip -->
+ <dimen name="ongoing_activity_chip_max_text_width">74dp</dimen>
<!-- The activity chip side padding, used with the default phone icon. -->
<dimen name="ongoing_activity_chip_side_padding">12dp</dimen>
<!-- The activity chip side padding, used with an icon that has embedded padding (e.g. if the icon comes from the notification's smallIcon field). If the icon has padding, the chip itself can have less padding. -->
diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml
index 88ed4e353719..c06b0784092c 100644
--- a/packages/SystemUI/res/values/ids.xml
+++ b/packages/SystemUI/res/values/ids.xml
@@ -215,27 +215,30 @@
<item type="id" name="backlight_icon" />
<!-- IDs for use in the keyguard/lockscreen scene -->
- <item type="id" name="keyguard_root_view" />
+ <item type="id" name="accessibility_actions_view" />
+ <item type="id" name="ambient_indication_container" />
+ <item type="id" name="aod_notification_icon_container" />
+ <item type="id" name="burn_in_layer" />
+ <item type="id" name="burn_in_layer_empty_view" />
+ <item type="id" name="communal_tutorial_indicator" />
+ <item type="id" name="end_button" />
+ <item type="id" name="lock_icon" />
+ <item type="id" name="lock_icon_bg" />
<item type="id" name="keyguard_indication_area" />
<item type="id" name="keyguard_indication_text" />
<item type="id" name="keyguard_indication_text_bottom" />
+ <item type="id" name="keyguard_root_view" />
+ <item type="id" name="keyguard_settings_button" />
<item type="id" name="nssl_guideline" />
<item type="id" name="nssl_placeholder" />
- <item type="id" name="aod_notification_icon_container" />
- <item type="id" name="split_shade_guideline" />
- <item type="id" name="lock_icon" />
- <item type="id" name="lock_icon_bg" />
- <item type="id" name="burn_in_layer" />
- <item type="id" name="burn_in_layer_empty_view" />
- <item type="id" name="communal_tutorial_indicator" />
<item type="id" name="nssl_placeholder_barrier_bottom" />
- <item type="id" name="ambient_indication_container" />
- <item type="id" name="status_view_media_container" />
- <item type="id" name="smart_space_barrier_bottom" />
<item type="id" name="small_clock_guideline_top" />
+ <item type="id" name="smart_space_barrier_bottom" />
+ <item type="id" name="split_shade_guideline" />
+ <item type="id" name="start_button" />
+ <item type="id" name="status_view_media_container" />
<item type="id" name="weather_clock_date_and_icons_barrier_bottom" />
<item type="id" name="weather_clock_bc_smartspace_bottom" />
- <item type="id" name="accessibility_actions_view" />
<!-- Privacy dialog -->
<item type="id" name="privacy_dialog_close_app_button" />
@@ -280,7 +283,7 @@
<item type="id" name="udfps_accessibility_overlay_top_guideline" />
<!-- Ids for communal hub widgets -->
- <item type="id" name="communal_widget_disposable_tag"/>
+ <item type="id" name="communal_widget_listener_tag"/>
<!-- snapshot view-binding IDs -->
<item type="id" name="snapshot_view_binding" />
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 12f6e6958b42..3156a50df96f 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -84,6 +84,16 @@
<item name="android:textColor">?android:attr/colorPrimary</item>
</style>
+ <!-- Style for a status bar chip text that has a maximum width. Since there's so little room in
+ the status bar chip area, don't ellipsize the text and instead just fade it out a bit at
+ the end. -->
+ <style name="StatusBar.Chip.Text.LimitedWidth">
+ <item name="android:ellipsize">none</item>
+ <item name="android:requiresFadingEdge">horizontal</item>
+ <item name="android:fadingEdgeLength">@dimen/ongoing_activity_chip_text_fading_edge_length</item>
+ <item name="android:maxWidth">@dimen/ongoing_activity_chip_max_text_width</item>
+ </style>
+
<style name="Chipbar" />
<style name="Chipbar.Text" parent="@*android:style/TextAppearance.DeviceDefault.Notification.Title">
diff --git a/packages/SystemUI/src/com/android/keyguard/EmptyLockIconViewController.kt b/packages/SystemUI/src/com/android/keyguard/EmptyLockIconViewController.kt
deleted file mode 100644
index 306d68217e50..000000000000
--- a/packages/SystemUI/src/com/android/keyguard/EmptyLockIconViewController.kt
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * 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.keyguard
-
-import android.view.MotionEvent
-import android.view.View
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.keyguard.ui.view.KeyguardRootView
-import com.android.systemui.res.R
-import dagger.Lazy
-import javax.inject.Inject
-
-/**
- * Lock icon view logic now lives in DeviceEntryIconViewBinder and ViewModels. Icon is positioned in
- * [com.android.systemui.keyguard.ui.view.layout.sections.DefaultDeviceEntrySection].
- *
- * This class is to bridge the gap between the logic when the DeviceEntryUdfpsRefactor is enabled
- * and the KeyguardBottomAreaRefactor is NOT enabled. This class can and should be removed when both
- * flags are enabled.
- */
-@SysUISingleton
-class EmptyLockIconViewController
-@Inject
-constructor(private val keyguardRootView: Lazy<KeyguardRootView>) : LockIconViewController {
- private val deviceEntryIconViewId = R.id.device_entry_icon_view
-
- override fun setLockIconView(lockIconView: View) {
- // no-op
- }
-
- override fun getTop(): Float {
- return keyguardRootView.get().getViewById(deviceEntryIconViewId)?.top?.toFloat() ?: 0f
- }
-
- override fun getBottom(): Float {
- return keyguardRootView.get().getViewById(deviceEntryIconViewId)?.bottom?.toFloat() ?: 0f
- }
-
- override fun dozeTimeTick() {
- // no-op
- }
-
- override fun setAlpha(alpha: Float) {
- // no-op
- }
-
- override fun willHandleTouchWhileDozing(event: MotionEvent): Boolean {
- return false
- }
-}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
index 36afe1e0fe18..63d4fe3f1b01 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
@@ -51,6 +51,7 @@ import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BlendMode;
import android.graphics.Canvas;
+import android.graphics.Color;
import android.graphics.Rect;
import android.graphics.Typeface;
import android.graphics.drawable.BitmapDrawable;
@@ -97,6 +98,7 @@ import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
import com.android.settingslib.Utils;
import com.android.settingslib.drawable.CircleFramedDrawable;
+import com.android.systemui.Flags;
import com.android.systemui.Gefingerpoken;
import com.android.systemui.classifier.FalsingA11yDelegate;
import com.android.systemui.plugins.FalsingManager;
@@ -346,8 +348,7 @@ public class KeyguardSecurityContainer extends ConstraintLayout {
setPadding(getPaddingLeft(), getPaddingTop() + getResources().getDimensionPixelSize(
R.dimen.keyguard_security_container_padding_top), getPaddingRight(),
getPaddingBottom());
- setBackgroundColor(
- getContext().getColor(com.android.internal.R.color.materialColorSurfaceDim));
+ reloadBackgroundColor();
}
void onResume(SecurityMode securityMode, boolean faceAuthEnabled) {
@@ -812,10 +813,20 @@ public class KeyguardSecurityContainer extends ConstraintLayout {
mDisappearAnimRunning = false;
}
+ private void reloadBackgroundColor() {
+ if (Flags.bouncerUiRevamp()) {
+ // Keep the background transparent, otherwise the background color looks like a box
+ // while scaling the bouncer for back animation or while transitioning to the bouncer.
+ setBackgroundColor(Color.TRANSPARENT);
+ } else {
+ setBackgroundColor(
+ getContext().getColor(com.android.internal.R.color.materialColorSurfaceDim));
+ }
+ }
+
void reloadColors() {
mViewMode.reloadColors();
- setBackgroundColor(getContext().getColor(
- com.android.internal.R.color.materialColorSurfaceDim));
+ reloadBackgroundColor();
}
/** Handles density or font scale changes. */
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/communal/ui/binder/CommunalAppWidgetHostViewBinder.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/binder/CommunalAppWidgetHostViewBinder.kt
deleted file mode 100644
index 71bfe0c057e4..000000000000
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/binder/CommunalAppWidgetHostViewBinder.kt
+++ /dev/null
@@ -1,110 +0,0 @@
-/*
- * 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.communal.ui.binder
-
-import android.content.Context
-import android.os.Bundle
-import android.util.SizeF
-import android.view.View
-import android.view.ViewGroup
-import android.widget.FrameLayout
-import androidx.compose.ui.unit.IntSize
-import androidx.core.view.doOnLayout
-import com.android.app.tracing.coroutines.launchTraced as launch
-import com.android.systemui.Flags.communalWidgetResizing
-import com.android.systemui.common.ui.view.onLayoutChanged
-import com.android.systemui.communal.domain.model.CommunalContentModel
-import com.android.systemui.communal.util.WidgetViewFactory
-import com.android.systemui.util.kotlin.DisposableHandles
-import com.android.systemui.util.kotlin.toDp
-import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
-import kotlin.coroutines.CoroutineContext
-import kotlin.coroutines.resume
-import kotlin.coroutines.suspendCoroutine
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.DisposableHandle
-import kotlinx.coroutines.channels.awaitClose
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.distinctUntilChanged
-import kotlinx.coroutines.flow.flowOn
-
-object CommunalAppWidgetHostViewBinder {
- private const val TAG = "CommunalAppWidgetHostViewBinder"
-
- fun bind(
- context: Context,
- applicationScope: CoroutineScope,
- mainContext: CoroutineContext,
- backgroundContext: CoroutineContext,
- container: FrameLayout,
- model: CommunalContentModel.WidgetContent.Widget,
- size: SizeF?,
- factory: WidgetViewFactory,
- ): DisposableHandle {
- val disposables = DisposableHandles()
-
- val loadingJob =
- applicationScope.launch("$TAG#createWidgetView") {
- val widget = factory.createWidget(context, model, size)
- waitForLayout(container)
- container.post { container.setView(widget) }
- if (communalWidgetResizing()) {
- // Update the app widget size in the background.
- launch("$TAG#updateSize", backgroundContext) {
- container.sizeFlow().flowOn(mainContext).distinctUntilChanged().collect {
- (width, height) ->
- widget.updateAppWidgetSize(
- /* newOptions = */ Bundle(),
- /* minWidth = */ width,
- /* minHeight = */ height,
- /* maxWidth = */ width,
- /* maxHeight = */ height,
- /* ignorePadding = */ true,
- )
- }
- }
- }
- }
-
- disposables += DisposableHandle { loadingJob.cancel() }
- disposables += DisposableHandle { container.removeAllViews() }
-
- return disposables
- }
-
- private suspend fun waitForLayout(container: FrameLayout) = suspendCoroutine { cont ->
- container.doOnLayout { cont.resume(Unit) }
- }
-}
-
-private fun ViewGroup.setView(view: View) {
- if (view.parent == this) {
- return
- }
- (view.parent as? ViewGroup)?.removeView(view)
- addView(view)
-}
-
-private fun View.sizeAsDp(): IntSize = IntSize(width.toDp(context), height.toDp(context))
-
-private fun View.sizeFlow(): Flow<IntSize> = conflatedCallbackFlow {
- if (isLaidOut && !isLayoutRequested) {
- trySend(sizeAsDp())
- }
- val disposable = onLayoutChanged { trySend(sizeAsDp()) }
- awaitClose { disposable.dispose() }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/view/layout/sections/CommunalAppWidgetSection.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/view/layout/sections/CommunalAppWidgetSection.kt
index 2e12bad744f0..9f19562bb668 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/view/layout/sections/CommunalAppWidgetSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/view/layout/sections/CommunalAppWidgetSection.kt
@@ -16,98 +16,132 @@
package com.android.systemui.communal.ui.view.layout.sections
+import android.os.Bundle
import android.util.SizeF
+import android.view.View
import android.view.View.IMPORTANT_FOR_ACCESSIBILITY_AUTO
import android.view.View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
-import android.widget.FrameLayout
+import android.view.accessibility.AccessibilityNodeInfo
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.getValue
+import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.stringResource
import androidx.compose.ui.viewinterop.AndroidView
-import androidx.lifecycle.compose.collectAsStateWithLifecycle
-import com.android.systemui.Flags.communalWidgetResizing
+import com.android.systemui.Flags.communalHubUseThreadPoolForWidgets
import com.android.systemui.communal.domain.model.CommunalContentModel
-import com.android.systemui.communal.ui.binder.CommunalAppWidgetHostViewBinder
-import com.android.systemui.communal.ui.viewmodel.BaseCommunalViewModel
-import com.android.systemui.communal.util.WidgetViewFactory
-import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.communal.ui.viewmodel.CommunalAppWidgetViewModel
+import com.android.systemui.communal.widgets.CommunalAppWidgetHostView
+import com.android.systemui.communal.widgets.WidgetInteractionHandler
import com.android.systemui.dagger.qualifiers.UiBackground
+import com.android.systemui.lifecycle.rememberViewModel
import com.android.systemui.res.R
+import java.util.concurrent.Executor
+import java.util.concurrent.LinkedBlockingQueue
+import java.util.concurrent.ThreadPoolExecutor
+import java.util.concurrent.TimeUnit
import javax.inject.Inject
-import kotlin.coroutines.CoroutineContext
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.DisposableHandle
class CommunalAppWidgetSection
@Inject
constructor(
- @Application private val applicationScope: CoroutineScope,
- @Main private val mainContext: CoroutineContext,
- @UiBackground private val backgroundContext: CoroutineContext,
- private val factory: WidgetViewFactory,
+ @UiBackground private val uiBgExecutor: Executor,
+ private val interactionHandler: WidgetInteractionHandler,
+ private val viewModelFactory: CommunalAppWidgetViewModel.Factory,
) {
private companion object {
- val DISPOSABLE_TAG = R.id.communal_widget_disposable_tag
+ const val TAG = "CommunalAppWidgetSection"
+ val LISTENER_TAG = R.id.communal_widget_listener_tag
+
+ val poolSize by lazy { Runtime.getRuntime().availableProcessors().coerceAtLeast(2) }
+
+ /**
+ * This executor is used for widget inflation. Parameters match what launcher uses. See
+ * [com.android.launcher3.util.Executors.THREAD_POOL_EXECUTOR].
+ */
+ val widgetExecutor by lazy {
+ ThreadPoolExecutor(
+ /*corePoolSize*/ poolSize,
+ /*maxPoolSize*/ poolSize,
+ /*keepAlive*/ 1,
+ /*unit*/ TimeUnit.SECONDS,
+ /*workQueue*/ LinkedBlockingQueue(),
+ )
+ }
}
@Composable
fun Widget(
- viewModel: BaseCommunalViewModel,
+ isFocusable: Boolean,
+ openWidgetEditor: () -> Unit,
model: CommunalContentModel.WidgetContent.Widget,
size: SizeF,
modifier: Modifier = Modifier,
) {
- val isFocusable by viewModel.isFocusable.collectAsStateWithLifecycle(initialValue = false)
+ val viewModel = rememberViewModel("$TAG#viewModel") { viewModelFactory.create() }
+ val longClickLabel = stringResource(R.string.accessibility_action_label_edit_widgets)
+ val accessibilityDelegate =
+ remember(longClickLabel, openWidgetEditor) {
+ WidgetAccessibilityDelegate(longClickLabel, openWidgetEditor)
+ }
AndroidView(
factory = { context ->
- FrameLayout(context).apply {
- layoutParams =
- FrameLayout.LayoutParams(
- FrameLayout.LayoutParams.MATCH_PARENT,
- FrameLayout.LayoutParams.MATCH_PARENT,
- )
-
- // Need to attach the disposable handle to the view here instead of storing
- // the state in the composable in order to properly support lazy lists. In a
- // lazy list, when the composable is no longer in view - it will exit
- // composition and any state inside the composable will be lost. However,
- // the View instance will be re-used. Therefore we can store data on the view
- // in order to preserve it.
- setTag(
- DISPOSABLE_TAG,
- CommunalAppWidgetHostViewBinder.bind(
- context = context,
- container = this,
- model = model,
- size = if (!communalWidgetResizing()) size else null,
- factory = factory,
- applicationScope = applicationScope,
- mainContext = mainContext,
- backgroundContext = backgroundContext,
- ),
- )
-
- accessibilityDelegate = viewModel.widgetAccessibilityDelegate
+ CommunalAppWidgetHostView(context, interactionHandler).apply {
+ if (communalHubUseThreadPoolForWidgets()) {
+ setExecutor(widgetExecutor)
+ } else {
+ setExecutor(uiBgExecutor)
+ }
}
},
- update = { container ->
- container.importantForAccessibility =
+ update = { view ->
+ view.accessibilityDelegate = accessibilityDelegate
+ view.importantForAccessibility =
if (isFocusable) {
IMPORTANT_FOR_ACCESSIBILITY_AUTO
} else {
IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
}
- },
- onRelease = { view ->
- val disposable = (view.getTag(DISPOSABLE_TAG) as? DisposableHandle)
- disposable?.dispose()
+ view.setAppWidget(model.appWidgetId, model.providerInfo)
+ // To avoid calling the expensive setListener method on every recomposition if
+ // the appWidgetId hasn't changed, we store the current appWidgetId of the view in
+ // a tag.
+ if ((view.getTag(LISTENER_TAG) as? Int) != model.appWidgetId) {
+ viewModel.setListener(model.appWidgetId, view)
+ view.setTag(LISTENER_TAG, model.appWidgetId)
+ }
+ viewModel.updateSize(size, view)
},
modifier = modifier,
// For reusing composition in lazy lists.
onReset = {},
)
}
+
+ private class WidgetAccessibilityDelegate(
+ private val longClickLabel: String,
+ private val longClickAction: () -> Unit,
+ ) : View.AccessibilityDelegate() {
+ override fun onInitializeAccessibilityNodeInfo(host: View, info: AccessibilityNodeInfo) {
+ super.onInitializeAccessibilityNodeInfo(host, info)
+ // Hint user to long press in order to enter edit mode
+ info.addAction(
+ AccessibilityNodeInfo.AccessibilityAction(
+ AccessibilityNodeInfo.AccessibilityAction.ACTION_LONG_CLICK.id,
+ longClickLabel.lowercase(),
+ )
+ )
+ }
+
+ override fun performAccessibilityAction(host: View, action: Int, args: Bundle?): Boolean {
+ when (action) {
+ AccessibilityNodeInfo.AccessibilityAction.ACTION_LONG_CLICK.id -> {
+ longClickAction()
+ return true
+ }
+ }
+ return super.performAccessibilityAction(host, action, args)
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
index a339af3694e7..099a85926020 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
@@ -19,7 +19,6 @@ package com.android.systemui.communal.ui.viewmodel
import android.appwidget.AppWidgetProviderInfo
import android.content.ComponentName
import android.os.UserHandle
-import android.view.View
import com.android.compose.animation.scene.ObservableTransitionState
import com.android.compose.animation.scene.SceneKey
import com.android.compose.animation.scene.TransitionKey
@@ -80,9 +79,6 @@ abstract class BaseCommunalViewModel(
*/
val glanceableTouchAvailable: Flow<Boolean> = anyOf(not(isTouchConsumed), isNestedScrolling)
- /** Accessibility delegate to be set on CommunalAppWidgetHostView. */
- open val widgetAccessibilityDelegate: View.AccessibilityDelegate? = null
-
/**
* The up-to-date value of the grid scroll offset. persisted to interactor on
* {@link #persistScrollPosition}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalAppWidgetViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalAppWidgetViewModel.kt
new file mode 100644
index 000000000000..6bafd14f9359
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalAppWidgetViewModel.kt
@@ -0,0 +1,133 @@
+/*
+ * 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.communal.ui.viewmodel
+
+import android.appwidget.AppWidgetHost.AppWidgetHostListener
+import android.appwidget.AppWidgetHostView
+import android.os.Bundle
+import android.util.SizeF
+import com.android.app.tracing.coroutines.coroutineScopeTraced
+import com.android.app.tracing.coroutines.withContextTraced
+import com.android.systemui.communal.shared.model.GlanceableHubMultiUserHelper
+import com.android.systemui.communal.widgets.AppWidgetHostListenerDelegate
+import com.android.systemui.communal.widgets.CommunalAppWidgetHost
+import com.android.systemui.communal.widgets.GlanceableHubWidgetManager
+import com.android.systemui.dagger.qualifiers.UiBackground
+import com.android.systemui.lifecycle.ExclusiveActivatable
+import dagger.Lazy
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.awaitCancellation
+import kotlinx.coroutines.channels.BufferOverflow
+import kotlinx.coroutines.channels.Channel
+import kotlinx.coroutines.flow.receiveAsFlow
+
+/** View model for showing a widget. */
+class CommunalAppWidgetViewModel
+@AssistedInject
+constructor(
+ @UiBackground private val backgroundContext: CoroutineContext,
+ private val appWidgetHostLazy: Lazy<CommunalAppWidgetHost>,
+ private val listenerDelegateFactory: AppWidgetHostListenerDelegate.Factory,
+ private val glanceableHubWidgetManagerLazy: Lazy<GlanceableHubWidgetManager>,
+ private val multiUserHelper: GlanceableHubMultiUserHelper,
+) : ExclusiveActivatable() {
+
+ private companion object {
+ const val TAG = "CommunalAppWidgetViewModel"
+ const val CHANNEL_CAPACITY = 10
+ }
+
+ @AssistedFactory
+ interface Factory {
+ fun create(): CommunalAppWidgetViewModel
+ }
+
+ private val requests =
+ Channel<Request>(capacity = CHANNEL_CAPACITY, onBufferOverflow = BufferOverflow.DROP_OLDEST)
+
+ fun setListener(appWidgetId: Int, listener: AppWidgetHostListener) {
+ requests.trySend(SetListener(appWidgetId, listener))
+ }
+
+ fun updateSize(size: SizeF, view: AppWidgetHostView) {
+ requests.trySend(UpdateSize(size, view))
+ }
+
+ override suspend fun onActivated(): Nothing {
+ coroutineScopeTraced("$TAG#onActivated") {
+ requests.receiveAsFlow().collect { request ->
+ when (request) {
+ is SetListener -> handleSetListener(request.appWidgetId, request.listener)
+ is UpdateSize -> handleUpdateSize(request.size, request.view)
+ }
+ }
+ }
+
+ awaitCancellation()
+ }
+
+ private suspend fun handleSetListener(appWidgetId: Int, listener: AppWidgetHostListener) =
+ withContextTraced("$TAG#setListenerInner", backgroundContext) {
+ if (
+ multiUserHelper.glanceableHubHsumFlagEnabled &&
+ multiUserHelper.isInHeadlessSystemUser()
+ ) {
+ // If the widget view is created in the headless system user, the widget host lives
+ // remotely in the foreground user, and therefore the host listener needs to be
+ // registered through the widget manager.
+ with(glanceableHubWidgetManagerLazy.get()) {
+ setAppWidgetHostListener(appWidgetId, listenerDelegateFactory.create(listener))
+ }
+ } else {
+ // Instead of setting the view as the listener directly, we wrap the view in a
+ // delegate which ensures the callbacks always get called on the main thread.
+ with(appWidgetHostLazy.get()) {
+ setListener(appWidgetId, listenerDelegateFactory.create(listener))
+ }
+ }
+ }
+
+ private suspend fun handleUpdateSize(size: SizeF, view: AppWidgetHostView) =
+ withContextTraced("$TAG#updateSizeInner", backgroundContext) {
+ view.updateAppWidgetSize(
+ /* newOptions = */ Bundle(),
+ /* minWidth = */ size.width.toInt(),
+ /* minHeight = */ size.height.toInt(),
+ /* maxWidth = */ size.width.toInt(),
+ /* maxHeight = */ size.height.toInt(),
+ /* ignorePadding = */ true,
+ )
+ }
+}
+
+private sealed interface Request
+
+/**
+ * [Request] to call [CommunalAppWidgetHost.setListener] to tie this view to a particular widget.
+ * Since this is involves an IPC to system_server, the call is asynchronous and happens in the
+ * background.
+ */
+private data class SetListener(val appWidgetId: Int, val listener: AppWidgetHostListener) : Request
+
+/**
+ * [Request] to call [AppWidgetHostView.updateAppWidgetSize] to notify the widget provider of the
+ * new size. Since this is involves an IPC to system_server, the call is asynchronous and happens in
+ * the background.
+ */
+private data class UpdateSize(val size: SizeF, val view: AppWidgetHostView) : Request
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTutorialIndicatorViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTutorialIndicatorViewModel.kt
index 63a497213255..ce3a2be9229e 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTutorialIndicatorViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTutorialIndicatorViewModel.kt
@@ -17,21 +17,17 @@
package com.android.systemui.communal.ui.viewmodel
import com.android.systemui.communal.domain.interactor.CommunalTutorialInteractor
-import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
-import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flowOf
/** View model for communal tutorial indicator on keyguard */
class CommunalTutorialIndicatorViewModel
@Inject
-constructor(
- private val communalTutorialInteractor: CommunalTutorialInteractor,
- bottomAreaInteractor: KeyguardBottomAreaInteractor,
-) {
+constructor(private val communalTutorialInteractor: CommunalTutorialInteractor) {
/**
* An observable for whether the tutorial indicator view should be visible.
*
@@ -46,5 +42,6 @@ constructor(
}
/** An observable for the alpha level for the tutorial indicator. */
- val alpha: Flow<Float> = bottomAreaInteractor.alpha.distinctUntilChanged()
+ // TODO("b/383587536") find replacement for keyguardBottomAreaInteractor alpha
+ val alpha: Flow<Float> = flowOf(0f)
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
index 83bd265db5f6..ddc4d1c10690 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
@@ -17,10 +17,6 @@
package com.android.systemui.communal.ui.viewmodel
import android.content.ComponentName
-import android.content.res.Resources
-import android.os.Bundle
-import android.view.View
-import android.view.accessibility.AccessibilityNodeInfo
import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.systemui.Flags
import com.android.systemui.communal.domain.interactor.CommunalInteractor
@@ -45,7 +41,6 @@ import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager
import com.android.systemui.media.controls.ui.view.MediaHost
import com.android.systemui.media.controls.ui.view.MediaHostState
import com.android.systemui.media.dagger.MediaModule
-import com.android.systemui.res.R
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.statusbar.KeyguardIndicationController
@@ -85,7 +80,6 @@ constructor(
@Main val mainDispatcher: CoroutineDispatcher,
@Application private val scope: CoroutineScope,
@Background private val bgScope: CoroutineScope,
- @Main private val resources: Resources,
keyguardTransitionInteractor: KeyguardTransitionInteractor,
keyguardInteractor: KeyguardInteractor,
private val keyguardIndicationController: KeyguardIndicationController,
@@ -219,39 +213,6 @@ constructor(
}
.distinctUntilChanged()
- override val widgetAccessibilityDelegate =
- object : View.AccessibilityDelegate() {
- override fun onInitializeAccessibilityNodeInfo(
- host: View,
- info: AccessibilityNodeInfo,
- ) {
- super.onInitializeAccessibilityNodeInfo(host, info)
- // Hint user to long press in order to enter edit mode
- info.addAction(
- AccessibilityNodeInfo.AccessibilityAction(
- AccessibilityNodeInfo.AccessibilityAction.ACTION_LONG_CLICK.id,
- resources
- .getString(R.string.accessibility_action_label_edit_widgets)
- .lowercase(),
- )
- )
- }
-
- override fun performAccessibilityAction(
- host: View,
- action: Int,
- args: Bundle?,
- ): Boolean {
- when (action) {
- AccessibilityNodeInfo.AccessibilityAction.ACTION_LONG_CLICK.id -> {
- onOpenWidgetEditor()
- return true
- }
- }
- return super.performAccessibilityAction(host, action, args)
- }
- }
-
private val _isEnableWidgetDialogShowing: MutableStateFlow<Boolean> = MutableStateFlow(false)
val isEnableWidgetDialogShowing: Flow<Boolean> = _isEnableWidgetDialogShowing.asStateFlow()
diff --git a/packages/SystemUI/src/com/android/systemui/communal/util/WidgetViewFactory.kt b/packages/SystemUI/src/com/android/systemui/communal/util/WidgetViewFactory.kt
deleted file mode 100644
index 50d86a24be96..000000000000
--- a/packages/SystemUI/src/com/android/systemui/communal/util/WidgetViewFactory.kt
+++ /dev/null
@@ -1,117 +0,0 @@
-/*
- * 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.communal.util
-
-import android.content.Context
-import android.os.Bundle
-import android.util.SizeF
-import com.android.app.tracing.coroutines.withContextTraced as withContext
-import com.android.systemui.Flags
-import com.android.systemui.communal.domain.model.CommunalContentModel
-import com.android.systemui.communal.shared.model.GlanceableHubMultiUserHelper
-import com.android.systemui.communal.widgets.AppWidgetHostListenerDelegate
-import com.android.systemui.communal.widgets.CommunalAppWidgetHost
-import com.android.systemui.communal.widgets.CommunalAppWidgetHostView
-import com.android.systemui.communal.widgets.GlanceableHubWidgetManager
-import com.android.systemui.communal.widgets.WidgetInteractionHandler
-import com.android.systemui.dagger.qualifiers.UiBackground
-import dagger.Lazy
-import java.util.concurrent.Executor
-import java.util.concurrent.LinkedBlockingQueue
-import java.util.concurrent.ThreadPoolExecutor
-import java.util.concurrent.TimeUnit
-import javax.inject.Inject
-import kotlin.coroutines.CoroutineContext
-
-/** Factory for creating [CommunalAppWidgetHostView] in a background thread. */
-class WidgetViewFactory
-@Inject
-constructor(
- @UiBackground private val uiBgContext: CoroutineContext,
- @UiBackground private val uiBgExecutor: Executor,
- private val appWidgetHostLazy: Lazy<CommunalAppWidgetHost>,
- private val interactionHandler: WidgetInteractionHandler,
- private val listenerFactory: AppWidgetHostListenerDelegate.Factory,
- private val glanceableHubWidgetManagerLazy: Lazy<GlanceableHubWidgetManager>,
- private val multiUserHelper: GlanceableHubMultiUserHelper,
-) {
- suspend fun createWidget(
- context: Context,
- model: CommunalContentModel.WidgetContent.Widget,
- size: SizeF?,
- ): CommunalAppWidgetHostView =
- withContext("$TAG#createWidget", uiBgContext) {
- val view =
- CommunalAppWidgetHostView(context, interactionHandler).apply {
- if (Flags.communalHubUseThreadPoolForWidgets()) {
- setExecutor(widgetExecutor)
- } else {
- setExecutor(uiBgExecutor)
- }
- setAppWidget(model.appWidgetId, model.providerInfo)
- }
-
- if (
- multiUserHelper.glanceableHubHsumFlagEnabled &&
- multiUserHelper.isInHeadlessSystemUser()
- ) {
- // If the widget view is created in the headless system user, the widget host lives
- // remotely in the foreground user, and therefore the host listener needs to be
- // registered through the widget manager.
- with(glanceableHubWidgetManagerLazy.get()) {
- setAppWidgetHostListener(model.appWidgetId, listenerFactory.create(view))
- }
- } else {
- // Instead of setting the view as the listener directly, we wrap the view in a
- // delegate which ensures the callbacks always get called on the main thread.
- with(appWidgetHostLazy.get()) {
- setListener(model.appWidgetId, listenerFactory.create(view))
- }
- }
-
- if (size != null) {
- view.updateAppWidgetSize(
- /* newOptions = */ Bundle(),
- /* minWidth = */ size.width.toInt(),
- /* minHeight = */ size.height.toInt(),
- /* maxWidth = */ size.width.toInt(),
- /* maxHeight = */ size.height.toInt(),
- /* ignorePadding = */ true,
- )
- }
- view
- }
-
- private companion object {
- const val TAG = "WidgetViewFactory"
-
- val poolSize = Runtime.getRuntime().availableProcessors().coerceAtLeast(2)
-
- /**
- * This executor is used for widget inflation. Parameters match what launcher uses. See
- * [com.android.launcher3.util.Executors.THREAD_POOL_EXECUTOR].
- */
- val widgetExecutor =
- ThreadPoolExecutor(
- /*corePoolSize*/ poolSize,
- /*maxPoolSize*/ poolSize,
- /*keepAlive*/ 1,
- /*unit*/ TimeUnit.SECONDS,
- /*workQueue*/ LinkedBlockingQueue(),
- )
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/AppWidgetHostListenerDelegate.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/AppWidgetHostListenerDelegate.kt
index f3416216afdd..7d80acd1f439 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/AppWidgetHostListenerDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/AppWidgetHostListenerDelegate.kt
@@ -36,7 +36,7 @@ constructor(
) : AppWidgetHostListener {
@AssistedFactory
- interface Factory {
+ fun interface Factory {
fun create(listener: AppWidgetHostListener): AppWidgetHostListenerDelegate
}
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/DeviceEntryModule.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/DeviceEntryModule.kt
index 6c335e71cfde..0ab9661c0b4e 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/DeviceEntryModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/DeviceEntryModule.kt
@@ -16,18 +16,13 @@
package com.android.systemui.deviceentry
-import com.android.keyguard.EmptyLockIconViewController
-import com.android.keyguard.LockIconViewController
import com.android.systemui.CoreStartable
-import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.deviceentry.data.repository.DeviceEntryRepositoryModule
import com.android.systemui.deviceentry.data.repository.FaceWakeUpTriggersConfigModule
import com.android.systemui.deviceentry.domain.interactor.DeviceUnlockedInteractor
import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
import dagger.Binds
-import dagger.Lazy
import dagger.Module
-import dagger.Provides
import dagger.multibindings.ClassKey
import dagger.multibindings.IntoMap
import dagger.multibindings.Multibinds
@@ -45,14 +40,4 @@ abstract class DeviceEntryModule {
abstract fun deviceUnlockedInteractorActivator(
activator: DeviceUnlockedInteractor.Activator
): CoreStartable
-
- companion object {
- @Provides
- @SysUISingleton
- fun provideLockIconViewController(
- emptyLockIconViewController: Lazy<EmptyLockIconViewController>
- ): LockIconViewController {
- return emptyLockIconViewController.get()
- }
- }
}
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/keyguard/KeyguardBottomAreaRefactor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardBottomAreaRefactor.kt
deleted file mode 100644
index 779b27b25375..000000000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardBottomAreaRefactor.kt
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * 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.keyguard
-
-import com.android.systemui.Flags
-import com.android.systemui.flags.FlagToken
-import com.android.systemui.flags.RefactorFlagUtils
-
-/** Helper for reading or using the keyguard bottom area refactor flag. */
-@Suppress("NOTHING_TO_INLINE")
-object KeyguardBottomAreaRefactor {
- /** The aconfig flag name */
- const val FLAG_NAME = Flags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR
-
- /** A token used for dependency declaration */
- val token: FlagToken
- get() = FlagToken(FLAG_NAME, isEnabled)
-
- /** Is the refactor enabled */
- @JvmStatic
- inline val isEnabled
- get() = Flags.keyguardBottomAreaRefactor()
-
- /**
- * Called to ensure code is only run when the flag is enabled. This protects users from the
- * unintended behaviors caused by accidentally running new logic, while also crashing on an eng
- * build to ensure that the refactor author catches issues in testing.
- */
- @JvmStatic
- inline fun isUnexpectedlyInLegacyMode() =
- RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, FLAG_NAME)
-
- /**
- * Called to ensure code is only run when the flag is disabled. This will throw an exception if
- * the flag is enabled to ensure that the refactor author catches issues in testing.
- */
- @JvmStatic
- inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_NAME)
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndication.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndication.java
index a0b25b930d15..984541bcc60b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndication.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndication.java
@@ -27,7 +27,7 @@ import android.view.View;
* Data class containing display information (message, icon, styling) for indication to show at
* the bottom of the keyguard.
*
- * See {@link com.android.systemui.statusbar.phone.KeyguardBottomAreaView}.
+ * See {@link com.android.systemui.keyguard.ui.view.KeyguardRootView}.
*/
public class KeyguardIndication {
@Nullable
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
index 5ec6d37207b5..e8eb4976194a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
@@ -41,7 +41,6 @@ import com.android.systemui.deviceentry.domain.interactor.DeviceEntryHapticsInte
import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
import com.android.systemui.keyguard.shared.model.LockscreenSceneBlueprint
import com.android.systemui.keyguard.ui.binder.KeyguardBlueprintViewBinder
-import com.android.systemui.keyguard.ui.binder.KeyguardIndicationAreaBinder
import com.android.systemui.keyguard.ui.binder.KeyguardRootViewBinder
import com.android.systemui.keyguard.ui.binder.LightRevealScrimViewBinder
import com.android.systemui.keyguard.ui.composable.LockscreenContent
@@ -50,7 +49,6 @@ import com.android.systemui.keyguard.ui.view.KeyguardIndicationArea
import com.android.systemui.keyguard.ui.view.KeyguardRootView
import com.android.systemui.keyguard.ui.viewmodel.KeyguardBlueprintViewModel
import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
-import com.android.systemui.keyguard.ui.viewmodel.KeyguardIndicationAreaViewModel
import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel
import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel
import com.android.systemui.keyguard.ui.viewmodel.LightRevealScrimViewModel
@@ -59,7 +57,6 @@ import com.android.systemui.keyguard.ui.viewmodel.OccludingAppDeviceEntryMessage
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.res.R
import com.android.systemui.scene.shared.flag.SceneContainerFlag
-import com.android.systemui.shade.NotificationShadeWindowView
import com.android.systemui.shade.ShadeDisplayAware
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.statusbar.KeyguardIndicationController
@@ -86,9 +83,6 @@ class KeyguardViewConfigurator
constructor(
private val keyguardRootView: KeyguardRootView,
private val keyguardRootViewModel: KeyguardRootViewModel,
- private val keyguardIndicationAreaViewModel: KeyguardIndicationAreaViewModel,
- private val notificationShadeWindowView: NotificationShadeWindowView,
- private val indicationController: KeyguardIndicationController,
private val screenOffAnimationController: ScreenOffAnimationController,
private val occludingAppDeviceEntryMessageViewModel: OccludingAppDeviceEntryMessageViewModel,
private val chipbarCoordinator: ChipbarCoordinator,
@@ -163,23 +157,6 @@ constructor(
}
}
- fun bindIndicationArea() {
- indicationAreaHandle?.dispose()
-
- if (!KeyguardBottomAreaRefactor.isEnabled) {
- keyguardRootView.findViewById<View?>(R.id.keyguard_indication_area)?.let {
- keyguardRootView.removeView(it)
- }
- }
-
- indicationAreaHandle =
- KeyguardIndicationAreaBinder.bind(
- notificationShadeWindowView.requireViewById(R.id.keyguard_indication_area),
- keyguardIndicationAreaViewModel,
- indicationController,
- )
- }
-
/** Initialize views so that corresponding controllers have a view set. */
private fun initializeViews() {
val indicationArea = KeyguardIndicationArea(context, null)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
index 096439b1008d..4370abf9ce5a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
@@ -62,6 +62,7 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionBootInteractor;
import com.android.systemui.keyguard.domain.interactor.StartKeyguardTransitionModule;
import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransitionModule;
+import com.android.systemui.keyguard.ui.transitions.PrimaryBouncerTransitionModule;
import com.android.systemui.keyguard.ui.view.AlternateBouncerWindowViewBinder;
import com.android.systemui.keyguard.ui.viewmodel.KeyguardQuickAffordancesCombinedViewModelModule;
import com.android.systemui.log.SessionTracker;
@@ -112,6 +113,7 @@ import java.util.concurrent.Executor;
includes = {
DeviceEntryIconTransitionModule.class,
FalsingModule.class,
+ PrimaryBouncerTransitionModule.class,
KeyguardDataQuickAffordanceModule.class,
KeyguardQuickAffordancesCombinedViewModelModule.class,
KeyguardRepositoryModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
index d3c17ccd2d18..ac04dd5a7ec1 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
@@ -75,12 +75,6 @@ interface KeyguardRepository {
*/
val animateBottomAreaDozingTransitions: StateFlow<Boolean>
- /**
- * Observable for the current amount of alpha that should be used for rendering the bottom area.
- * UI.
- */
- val bottomAreaAlpha: StateFlow<Float>
-
val keyguardAlpha: StateFlow<Float>
val panelAlpha: MutableStateFlow<Float>
@@ -283,9 +277,6 @@ interface KeyguardRepository {
/** Sets whether the bottom area UI should animate the transition out of doze state. */
fun setAnimateDozingTransitions(animate: Boolean)
- /** Sets the current amount of alpha that should be used for rendering the bottom area. */
- @Deprecated("Deprecated as part of b/278057014") fun setBottomAreaAlpha(alpha: Float)
-
/** Sets the current amount of alpha that should be used for rendering the keyguard. */
fun setKeyguardAlpha(alpha: Float)
@@ -392,9 +383,6 @@ constructor(
override val animateBottomAreaDozingTransitions =
_animateBottomAreaDozingTransitions.asStateFlow()
- private val _bottomAreaAlpha = MutableStateFlow(1f)
- override val bottomAreaAlpha = _bottomAreaAlpha.asStateFlow()
-
private val _keyguardAlpha = MutableStateFlow(1f)
override val keyguardAlpha = _keyguardAlpha.asStateFlow()
@@ -675,10 +663,6 @@ constructor(
_animateBottomAreaDozingTransitions.value = animate
}
- override fun setBottomAreaAlpha(alpha: Float) {
- _bottomAreaAlpha.value = alpha
- }
-
override fun setKeyguardAlpha(alpha: Float) {
_keyguardAlpha.value = alpha
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBottomAreaInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBottomAreaInteractor.kt
deleted file mode 100644
index 53f241684a62..000000000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBottomAreaInteractor.kt
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-package com.android.systemui.keyguard.domain.interactor
-
-import com.android.systemui.common.shared.model.Position
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.keyguard.data.repository.KeyguardRepository
-import javax.inject.Inject
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.asStateFlow
-
-/** Encapsulates business-logic specifically related to the keyguard bottom area. */
-@SysUISingleton
-class KeyguardBottomAreaInteractor
-@Inject
-constructor(
- private val repository: KeyguardRepository,
-) {
- /** Whether to animate the next doze mode transition. */
- val animateDozingTransitions: Flow<Boolean> = repository.animateBottomAreaDozingTransitions
- /** The amount of alpha for the UI components of the bottom area. */
- val alpha: Flow<Float> = repository.bottomAreaAlpha
- /** The position of the keyguard clock. */
- private val _clockPosition = MutableStateFlow(Position(0, 0))
- /** See [ClockSection] */
- @Deprecated("with MigrateClocksToBlueprint.isEnabled")
- val clockPosition: Flow<Position> = _clockPosition.asStateFlow()
-
- fun setClockPosition(x: Int, y: Int) {
- _clockPosition.value = Position(x, y)
- }
-
- fun setAlpha(alpha: Float) {
- repository.setBottomAreaAlpha(alpha)
- }
-
- fun setAnimateDozingTransitions(animate: Boolean) {
- repository.setAnimateDozingTransitions(animate)
- }
-
- /**
- * Returns whether the keyguard bottom area should be constrained to the top of the lock icon
- */
- fun shouldConstrainToTopOfLockIcon(): Boolean = repository.isUdfpsSupported()
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt
index 5bad0168fe05..261c130d0d82 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt
@@ -24,7 +24,6 @@ import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.systemui.customization.R as customR
-import com.android.systemui.keyguard.KeyguardBottomAreaRefactor
import com.android.systemui.keyguard.shared.model.KeyguardBlueprint
import com.android.systemui.keyguard.ui.view.layout.blueprints.transitions.BaseBlueprintTransition
import com.android.systemui.keyguard.ui.view.layout.blueprints.transitions.IntraBlueprintTransition
@@ -51,26 +50,11 @@ object KeyguardBlueprintViewBinder {
(prevBlueprint, blueprint) ->
val config = Config.DEFAULT
val transition =
- if (
- !KeyguardBottomAreaRefactor.isEnabled &&
- prevBlueprint != null &&
- prevBlueprint != blueprint
- ) {
- BaseBlueprintTransition(clockViewModel)
- .addTransition(
- IntraBlueprintTransition(
- config,
- clockViewModel,
- smartspaceViewModel,
- )
- )
- } else {
- IntraBlueprintTransition(
- config,
- clockViewModel,
- smartspaceViewModel,
- )
- }
+ IntraBlueprintTransition(
+ config,
+ clockViewModel,
+ smartspaceViewModel,
+ )
viewModel.runTransition(constraintLayout, transition, config) {
// Replace sections from the previous blueprint with the new ones
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
deleted file mode 100644
index c59fe5357ccb..000000000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
+++ /dev/null
@@ -1,586 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.keyguard.ui.binder
-
-import android.annotation.SuppressLint
-import android.content.res.ColorStateList
-import android.graphics.Rect
-import android.graphics.drawable.Animatable2
-import android.util.Size
-import android.view.View
-import android.view.ViewGroup
-import android.view.ViewGroup.MarginLayoutParams
-import android.view.WindowInsets
-import android.widget.ImageView
-import androidx.core.animation.CycleInterpolator
-import androidx.core.animation.ObjectAnimator
-import androidx.core.view.isInvisible
-import androidx.core.view.isVisible
-import androidx.core.view.marginLeft
-import androidx.core.view.marginRight
-import androidx.core.view.marginTop
-import androidx.core.view.updateLayoutParams
-import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.repeatOnLifecycle
-import com.android.app.animation.Interpolators
-import com.android.app.tracing.coroutines.launchTraced as launch
-import com.android.systemui.animation.ActivityTransitionAnimator
-import com.android.systemui.animation.Expandable
-import com.android.systemui.animation.view.LaunchableLinearLayout
-import com.android.systemui.common.shared.model.Icon
-import com.android.systemui.common.ui.binder.IconViewBinder
-import com.android.systemui.common.ui.binder.TextViewBinder
-import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel
-import com.android.systemui.keyguard.ui.viewmodel.KeyguardQuickAffordanceViewModel
-import com.android.systemui.keyguard.util.WallpaperPickerIntentUtils
-import com.android.systemui.keyguard.util.WallpaperPickerIntentUtils.LAUNCH_SOURCE_KEYGUARD
-import com.android.systemui.lifecycle.repeatWhenAttached
-import com.android.systemui.plugins.ActivityStarter
-import com.android.systemui.plugins.FalsingManager
-import com.android.systemui.res.R
-import com.android.systemui.statusbar.VibratorHelper
-import com.android.systemui.util.doOnEnd
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.distinctUntilChanged
-import kotlinx.coroutines.flow.filter
-import kotlinx.coroutines.flow.flatMapLatest
-import kotlinx.coroutines.flow.map
-
-/**
- * Binds a keyguard bottom area view to its view-model.
- *
- * To use this properly, users should maintain a one-to-one relationship between the [View] and the
- * view-binding, binding each view only once. It is okay and expected for the same instance of the
- * view-model to be reused for multiple view/view-binder bindings.
- */
-@OptIn(ExperimentalCoroutinesApi::class)
-@Deprecated("Deprecated as part of b/278057014")
-object KeyguardBottomAreaViewBinder {
-
- private const val EXIT_DOZE_BUTTON_REVEAL_ANIMATION_DURATION_MS = 250L
- private const val SCALE_SELECTED_BUTTON = 1.23f
- private const val DIM_ALPHA = 0.3f
- private const val TAG = "KeyguardBottomAreaViewBinder"
-
- /**
- * Defines interface for an object that acts as the binding between the view and its view-model.
- *
- * Users of the [KeyguardBottomAreaViewBinder] class should use this to control the binder after
- * it is bound.
- */
- // If updated, be sure to update [KeyguardQuickAffordanceViewBinder.kt]
- @Deprecated("Deprecated as part of b/278057014")
- interface Binding {
- /** Notifies that device configuration has changed. */
- fun onConfigurationChanged()
-
- /**
- * Returns whether the keyguard bottom area should be constrained to the top of the lock
- * icon
- */
- fun shouldConstrainToTopOfLockIcon(): Boolean
-
- /** Destroys this binding, releases resources, and cancels any coroutines. */
- fun destroy()
- }
-
- /** Binds the view to the view-model, continuing to update the former based on the latter. */
- @Deprecated("Deprecated as part of b/278057014")
- @SuppressLint("ClickableViewAccessibility")
- @JvmStatic
- fun bind(
- view: ViewGroup,
- viewModel: KeyguardBottomAreaViewModel,
- falsingManager: FalsingManager?,
- vibratorHelper: VibratorHelper?,
- activityStarter: ActivityStarter?,
- messageDisplayer: (Int) -> Unit,
- ): Binding {
- val ambientIndicationArea: View? = view.findViewById(R.id.ambient_indication_container)
- val startButton: ImageView = view.requireViewById(R.id.start_button)
- val endButton: ImageView = view.requireViewById(R.id.end_button)
- val overlayContainer: View = view.requireViewById(R.id.overlay_container)
- val settingsMenu: LaunchableLinearLayout =
- view.requireViewById(R.id.keyguard_settings_button)
-
- startButton.setOnApplyWindowInsetsListener { inView, windowInsets ->
- val bottomInset = windowInsets.displayCutout?.safeInsetBottom ?: 0
- val marginBottom =
- inView.resources.getDimension(R.dimen.keyguard_affordance_vertical_offset).toInt()
- inView.layoutParams =
- (inView.layoutParams as MarginLayoutParams).apply {
- setMargins(
- inView.marginLeft,
- inView.marginTop,
- inView.marginRight,
- marginBottom + bottomInset
- )
- }
- WindowInsets.CONSUMED
- }
-
- endButton.setOnApplyWindowInsetsListener { inView, windowInsets ->
- val bottomInset = windowInsets.displayCutout?.safeInsetBottom ?: 0
- val marginBottom =
- inView.resources.getDimension(R.dimen.keyguard_affordance_vertical_offset).toInt()
- inView.layoutParams =
- (inView.layoutParams as MarginLayoutParams).apply {
- setMargins(
- inView.marginLeft,
- inView.marginTop,
- inView.marginRight,
- marginBottom + bottomInset
- )
- }
- WindowInsets.CONSUMED
- }
-
- view.clipChildren = false
- view.clipToPadding = false
- view.setOnTouchListener { _, event ->
- if (settingsMenu.isVisible) {
- val hitRect = Rect()
- settingsMenu.getHitRect(hitRect)
- if (!hitRect.contains(event.x.toInt(), event.y.toInt())) {
- viewModel.onTouchedOutsideLockScreenSettingsMenu()
- }
- }
-
- false
- }
-
- val configurationBasedDimensions = MutableStateFlow(loadFromResources(view))
-
- val disposableHandle =
- view.repeatWhenAttached {
- repeatOnLifecycle(Lifecycle.State.STARTED) {
- // If updated, be sure to update [KeyguardQuickAffordanceViewBinder.kt]
- launch("$TAG#viewModel.startButton") {
- viewModel.startButton.collect { buttonModel ->
- updateButton(
- view = startButton,
- viewModel = buttonModel,
- falsingManager = falsingManager,
- messageDisplayer = messageDisplayer,
- vibratorHelper = vibratorHelper,
- )
- }
- }
-
- // If updated, be sure to update [KeyguardQuickAffordanceViewBinder.kt]
- launch("$TAG#viewModel.endButton") {
- viewModel.endButton.collect { buttonModel ->
- updateButton(
- view = endButton,
- viewModel = buttonModel,
- falsingManager = falsingManager,
- messageDisplayer = messageDisplayer,
- vibratorHelper = vibratorHelper,
- )
- }
- }
-
- launch("$TAG#viewModel.isOverlayContainerVisible") {
- viewModel.isOverlayContainerVisible.collect { isVisible ->
- overlayContainer.visibility =
- if (isVisible) {
- View.VISIBLE
- } else {
- View.INVISIBLE
- }
- }
- }
-
- launch("$TAG#viewModel.alpha") {
- viewModel.alpha.collect { alpha ->
- ambientIndicationArea?.apply {
- this.importantForAccessibility =
- if (alpha == 0f) {
- View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
- } else {
- View.IMPORTANT_FOR_ACCESSIBILITY_AUTO
- }
- this.alpha = alpha
- }
- }
- }
-
- // If updated, be sure to update [KeyguardQuickAffordanceViewBinder.kt]
- launch("$TAG#updateButtonAlpha") {
- updateButtonAlpha(
- view = startButton,
- viewModel = viewModel.startButton,
- alphaFlow = viewModel.alpha,
- )
- }
-
- // If updated, be sure to update [KeyguardQuickAffordanceViewBinder.kt]
- launch("$TAG#updateButtonAlpha") {
- updateButtonAlpha(
- view = endButton,
- viewModel = viewModel.endButton,
- alphaFlow = viewModel.alpha,
- )
- }
-
- launch("$TAG#viewModel.indicationAreaTranslationX") {
- viewModel.indicationAreaTranslationX.collect { translationX ->
- ambientIndicationArea?.translationX = translationX
- }
- }
-
- launch("$TAG#viewModel.indicationAreaTranslationY") {
- configurationBasedDimensions
- .map { it.defaultBurnInPreventionYOffsetPx }
- .flatMapLatest { defaultBurnInOffsetY ->
- viewModel.indicationAreaTranslationY(defaultBurnInOffsetY)
- }
- .collect { translationY ->
- ambientIndicationArea?.translationY = translationY
- }
- }
-
- // If updated, be sure to update [KeyguardQuickAffordanceViewBinder.kt]
- launch("$TAG#startButton.updateLayoutParams<ViewGroup") {
- configurationBasedDimensions.collect { dimensions ->
- startButton.updateLayoutParams<ViewGroup.LayoutParams> {
- width = dimensions.buttonSizePx.width
- height = dimensions.buttonSizePx.height
- }
- endButton.updateLayoutParams<ViewGroup.LayoutParams> {
- width = dimensions.buttonSizePx.width
- height = dimensions.buttonSizePx.height
- }
- }
- }
-
- launch("$TAG#viewModel.settingsMenuViewModel") {
- viewModel.settingsMenuViewModel.isVisible.distinctUntilChanged().collect {
- isVisible ->
- settingsMenu.animateVisibility(visible = isVisible)
- if (isVisible) {
- vibratorHelper?.vibrate(KeyguardBottomAreaVibrations.Activated)
- settingsMenu.setOnTouchListener(
- KeyguardSettingsButtonOnTouchListener(
- viewModel = viewModel.settingsMenuViewModel,
- )
- )
- IconViewBinder.bind(
- icon = viewModel.settingsMenuViewModel.icon,
- view = settingsMenu.requireViewById(R.id.icon),
- )
- TextViewBinder.bind(
- view = settingsMenu.requireViewById(R.id.text),
- viewModel = viewModel.settingsMenuViewModel.text,
- )
- }
- }
- }
-
- // activityStarter will only be null when rendering the preview that
- // shows up in the Wallpaper Picker app. If we do that, then the
- // settings menu should never be visible.
- if (activityStarter != null) {
- launch("$TAG#viewModel.settingsMenuViewModel") {
- viewModel.settingsMenuViewModel.shouldOpenSettings
- .filter { it }
- .collect {
- navigateToLockScreenSettings(
- activityStarter = activityStarter,
- view = settingsMenu,
- )
- viewModel.settingsMenuViewModel.onSettingsShown()
- }
- }
- }
- }
- }
-
- return object : Binding {
- override fun onConfigurationChanged() {
- configurationBasedDimensions.value = loadFromResources(view)
- }
-
- override fun shouldConstrainToTopOfLockIcon(): Boolean =
- viewModel.shouldConstrainToTopOfLockIcon()
-
- override fun destroy() {
- disposableHandle.dispose()
- }
- }
- }
-
- @Deprecated("Deprecated as part of b/278057014")
- // If updated, be sure to update [KeyguardQuickAffordanceViewBinder.kt]
- @SuppressLint("ClickableViewAccessibility")
- private fun updateButton(
- view: ImageView,
- viewModel: KeyguardQuickAffordanceViewModel,
- falsingManager: FalsingManager?,
- messageDisplayer: (Int) -> Unit,
- vibratorHelper: VibratorHelper?,
- ) {
- if (!viewModel.isVisible) {
- view.isInvisible = true
- return
- }
-
- if (!view.isVisible) {
- view.isVisible = true
- if (viewModel.animateReveal) {
- view.alpha = 0f
- view.translationY = view.height / 2f
- view
- .animate()
- .alpha(1f)
- .translationY(0f)
- .setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN)
- .setDuration(EXIT_DOZE_BUTTON_REVEAL_ANIMATION_DURATION_MS)
- .start()
- }
- }
-
- IconViewBinder.bind(viewModel.icon, view)
-
- (view.drawable as? Animatable2)?.let { animatable ->
- (viewModel.icon as? Icon.Resource)?.res?.let { iconResourceId ->
- // Always start the animation (we do call stop() below, if we need to skip it).
- animatable.start()
-
- if (view.tag != iconResourceId) {
- // Here when we haven't run the animation on a previous update.
- //
- // Save the resource ID for next time, so we know not to re-animate the same
- // animation again.
- view.tag = iconResourceId
- } else {
- // Here when we've already done this animation on a previous update and want to
- // skip directly to the final frame of the animation to avoid running it.
- //
- // By calling stop after start, we go to the final frame of the animation.
- animatable.stop()
- }
- }
- }
-
- view.isActivated = viewModel.isActivated
- view.drawable.setTint(
- view.context.getColor(
- if (viewModel.isActivated) {
- com.android.internal.R.color.materialColorOnPrimaryFixed
- } else {
- com.android.internal.R.color.materialColorOnSurface
- }
- )
- )
-
- view.backgroundTintList =
- if (!viewModel.isSelected) {
- ColorStateList.valueOf(
- view.context.getColor(
- if (viewModel.isActivated) {
- com.android.internal.R.color.materialColorPrimaryFixed
- } else {
- com.android.internal.R.color.materialColorSurfaceContainerHigh
- }
- )
- )
- } else {
- null
- }
- view
- .animate()
- .scaleX(if (viewModel.isSelected) SCALE_SELECTED_BUTTON else 1f)
- .scaleY(if (viewModel.isSelected) SCALE_SELECTED_BUTTON else 1f)
- .start()
-
- view.isClickable = viewModel.isClickable
- if (viewModel.isClickable) {
- if (viewModel.useLongPress) {
- val onTouchListener =
- KeyguardQuickAffordanceOnTouchListener(
- view,
- viewModel,
- messageDisplayer,
- vibratorHelper,
- falsingManager,
- )
- view.setOnTouchListener(onTouchListener)
- view.setOnClickListener {
- messageDisplayer.invoke(R.string.keyguard_affordance_press_too_short)
- val amplitude =
- view.context.resources
- .getDimensionPixelSize(R.dimen.keyguard_affordance_shake_amplitude)
- .toFloat()
- val shakeAnimator =
- ObjectAnimator.ofFloat(
- view,
- "translationX",
- -amplitude / 2,
- amplitude / 2,
- )
- shakeAnimator.duration =
- KeyguardBottomAreaVibrations.ShakeAnimationDuration.inWholeMilliseconds
- shakeAnimator.interpolator =
- CycleInterpolator(KeyguardBottomAreaVibrations.ShakeAnimationCycles)
- shakeAnimator.doOnEnd { view.translationX = 0f }
- shakeAnimator.start()
-
- vibratorHelper?.vibrate(KeyguardBottomAreaVibrations.Shake)
- }
- view.onLongClickListener =
- OnLongClickListener(falsingManager, viewModel, vibratorHelper, onTouchListener)
- } else {
- view.setOnClickListener(OnClickListener(viewModel, checkNotNull(falsingManager)))
- }
- } else {
- view.onLongClickListener = null
- view.setOnClickListener(null)
- view.setOnTouchListener(null)
- }
-
- view.isSelected = viewModel.isSelected
- }
-
- @Deprecated("Deprecated as part of b/278057014")
- // If updated, be sure to update [KeyguardQuickAffordanceViewBinder.kt]
- private suspend fun updateButtonAlpha(
- view: View,
- viewModel: Flow<KeyguardQuickAffordanceViewModel>,
- alphaFlow: Flow<Float>,
- ) {
- combine(viewModel.map { it.isDimmed }, alphaFlow) { isDimmed, alpha ->
- if (isDimmed) DIM_ALPHA else alpha
- }
- .collect { view.alpha = it }
- }
-
- @Deprecated("Deprecated as part of b/278057014")
- private fun View.animateVisibility(visible: Boolean) {
- animate()
- .withStartAction {
- if (visible) {
- alpha = 0f
- isVisible = true
- }
- }
- .alpha(if (visible) 1f else 0f)
- .withEndAction {
- if (!visible) {
- isVisible = false
- }
- }
- .start()
- }
-
- @Deprecated("Deprecated as part of b/278057014")
- // If updated, be sure to update [KeyguardQuickAffordanceViewBinder.kt]
- private class OnLongClickListener(
- private val falsingManager: FalsingManager?,
- private val viewModel: KeyguardQuickAffordanceViewModel,
- private val vibratorHelper: VibratorHelper?,
- private val onTouchListener: KeyguardQuickAffordanceOnTouchListener
- ) : View.OnLongClickListener {
- override fun onLongClick(view: View): Boolean {
- if (falsingManager?.isFalseLongTap(FalsingManager.MODERATE_PENALTY) == true) {
- return true
- }
-
- if (viewModel.configKey != null) {
- viewModel.onClicked(
- KeyguardQuickAffordanceViewModel.OnClickedParameters(
- configKey = viewModel.configKey,
- expandable = Expandable.fromView(view),
- slotId = viewModel.slotId,
- )
- )
- vibratorHelper?.vibrate(
- if (viewModel.isActivated) {
- KeyguardBottomAreaVibrations.Activated
- } else {
- KeyguardBottomAreaVibrations.Deactivated
- }
- )
- }
-
- onTouchListener.cancel()
- return true
- }
-
- override fun onLongClickUseDefaultHapticFeedback(view: View) = false
- }
-
- @Deprecated("Deprecated as part of b/278057014")
- // If updated, be sure to update [KeyguardQuickAffordanceViewBinder.kt]
- private class OnClickListener(
- private val viewModel: KeyguardQuickAffordanceViewModel,
- private val falsingManager: FalsingManager,
- ) : View.OnClickListener {
- override fun onClick(view: View) {
- if (falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
- return
- }
-
- if (viewModel.configKey != null) {
- viewModel.onClicked(
- KeyguardQuickAffordanceViewModel.OnClickedParameters(
- configKey = viewModel.configKey,
- expandable = Expandable.fromView(view),
- slotId = viewModel.slotId,
- )
- )
- }
- }
- }
-
- @Deprecated("Deprecated as part of b/278057014")
- private fun loadFromResources(view: View): ConfigurationBasedDimensions {
- return ConfigurationBasedDimensions(
- defaultBurnInPreventionYOffsetPx =
- view.resources.getDimensionPixelOffset(R.dimen.default_burn_in_prevention_offset),
- buttonSizePx =
- Size(
- view.resources.getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_width),
- view.resources.getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_height),
- ),
- )
- }
-
- @Deprecated("Deprecated as part of b/278057014")
- /** Opens the wallpaper picker screen after the device is unlocked by the user. */
- private fun navigateToLockScreenSettings(
- activityStarter: ActivityStarter,
- view: View,
- ) {
- activityStarter.postStartActivityDismissingKeyguard(
- WallpaperPickerIntentUtils.getIntent(view.context, LAUNCH_SOURCE_KEYGUARD),
- /* delay= */ 0,
- /* animationController= */ ActivityTransitionAnimator.Controller.fromView(view),
- /* customMessage= */ view.context.getString(R.string.keyguard_unlock_to_customize_ls)
- )
- }
-
- @Deprecated("Deprecated as part of b/278057014")
- // If updated, be sure to update [KeyguardQuickAffordanceViewBinder.kt]
- private data class ConfigurationBasedDimensions(
- val defaultBurnInPreventionYOffsetPx: Int,
- val buttonSizePx: Size,
- )
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardIndicationAreaBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardIndicationAreaBinder.kt
index 8b947a3bcb1e..92b49ed6156c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardIndicationAreaBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardIndicationAreaBinder.kt
@@ -23,8 +23,6 @@ import android.widget.TextView
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
import com.android.app.tracing.coroutines.launchTraced as launch
-import com.android.systemui.keyguard.KeyguardBottomAreaRefactor
-import com.android.systemui.keyguard.MigrateClocksToBlueprint
import com.android.systemui.keyguard.ui.viewmodel.KeyguardIndicationAreaViewModel
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.res.R
@@ -75,16 +73,6 @@ object KeyguardIndicationAreaBinder {
disposables +=
view.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.STARTED) {
- launch("$TAG#viewModel.alpha") {
- // Do not independently apply alpha, as [KeyguardRootViewModel] should work
- // for this and all its children
- if (
- !(MigrateClocksToBlueprint.isEnabled ||
- KeyguardBottomAreaRefactor.isEnabled)
- ) {
- viewModel.alpha.collect { alpha -> view.alpha = alpha }
- }
- }
launch("$TAG#viewModel.indicationAreaTranslationX") {
viewModel.indicationAreaTranslationX.collect { translationX ->
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockViewBinder.kt
index c0b3d8345249..1964cb2f811f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockViewBinder.kt
@@ -136,8 +136,16 @@ object KeyguardPreviewClockViewBinder {
viewModel: KeyguardPreviewClockViewModel,
) {
val cs = ConstraintSet().apply { clone(rootView) }
- previewClock.largeClock.layout.applyPreviewConstraints(clockPreviewConfig, cs)
- previewClock.smallClock.layout.applyPreviewConstraints(clockPreviewConfig, cs)
+
+ val configWithUpdatedLockId =
+ if (rootView.getViewById(lockId) != null) {
+ clockPreviewConfig.copy(lockId = lockId)
+ } else {
+ clockPreviewConfig
+ }
+
+ previewClock.largeClock.layout.applyPreviewConstraints(configWithUpdatedLockId, cs)
+ previewClock.smallClock.layout.applyPreviewConstraints(configWithUpdatedLockId, cs)
// When selectedClockSize is the initial value, make both clocks invisible to avoid
// flickering
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt
index 5c8a234ec6c4..8725cdd273df 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt
@@ -71,9 +71,6 @@ constructor(
/**
* Defines interface for an object that acts as the binding between the view and its view-model.
- *
- * Users of the [KeyguardBottomAreaViewBinder] class should use this to control the binder after
- * it is bound.
*/
interface Binding {
/** Notifies that device configuration has changed. */
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
index cd4d9dcf366c..a2ce4ec5ce9b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
@@ -52,7 +52,6 @@ import com.android.systemui.common.ui.view.onLayoutChanged
import com.android.systemui.common.ui.view.onTouchListener
import com.android.systemui.customization.R as customR
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryHapticsInteractor
-import com.android.systemui.keyguard.KeyguardBottomAreaRefactor
import com.android.systemui.keyguard.KeyguardViewMediator
import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
@@ -119,35 +118,33 @@ object KeyguardRootViewBinder {
val disposables = DisposableHandles()
val childViews = mutableMapOf<Int, View>()
- if (KeyguardBottomAreaRefactor.isEnabled) {
- disposables +=
- view.onTouchListener { _, event ->
- var consumed = false
- if (falsingManager?.isFalseTap(FalsingManager.LOW_PENALTY) == false) {
- // signifies a primary button click down has reached keyguardrootview
- // we need to return true here otherwise an ACTION_UP will never arrive
- if (Flags.nonTouchscreenDevicesBypassFalsing()) {
- if (
- event.action == MotionEvent.ACTION_DOWN &&
- event.buttonState == MotionEvent.BUTTON_PRIMARY &&
- !event.isTouchscreenSource()
- ) {
- consumed = true
- } else if (
- event.action == MotionEvent.ACTION_UP &&
- !event.isTouchscreenSource()
- ) {
- statusBarKeyguardViewManager?.showBouncer(true)
- consumed = true
- }
+ disposables +=
+ view.onTouchListener { _, event ->
+ var consumed = false
+ if (falsingManager?.isFalseTap(FalsingManager.LOW_PENALTY) == false) {
+ // signifies a primary button click down has reached keyguardrootview
+ // we need to return true here otherwise an ACTION_UP will never arrive
+ if (Flags.nonTouchscreenDevicesBypassFalsing()) {
+ if (
+ event.action == MotionEvent.ACTION_DOWN &&
+ event.buttonState == MotionEvent.BUTTON_PRIMARY &&
+ !event.isTouchscreenSource()
+ ) {
+ consumed = true
+ } else if (
+ event.action == MotionEvent.ACTION_UP &&
+ !event.isTouchscreenSource()
+ ) {
+ statusBarKeyguardViewManager?.showBouncer(true)
+ consumed = true
}
- viewModel.setRootViewLastTapPosition(
- Point(event.x.toInt(), event.y.toInt())
- )
}
- consumed
+ viewModel.setRootViewLastTapPosition(
+ Point(event.x.toInt(), event.y.toInt())
+ )
}
- }
+ consumed
+ }
val burnInParams = MutableStateFlow(BurnInParameters())
val viewState = ViewStateAccessor(alpha = { view.alpha })
@@ -175,10 +172,8 @@ object KeyguardRootViewBinder {
launch("$TAG#alpha") {
viewModel.alpha(viewState).collect { alpha ->
view.alpha = alpha
- if (KeyguardBottomAreaRefactor.isEnabled) {
- childViews[statusViewId]?.alpha = alpha
- childViews[burnInLayerId]?.alpha = alpha
- }
+ childViews[statusViewId]?.alpha = alpha
+ childViews[burnInLayerId]?.alpha = alpha
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
index 85725d24758d..090b65922d2d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
@@ -48,39 +48,28 @@ import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
import androidx.constraintlayout.widget.ConstraintSet.START
import androidx.constraintlayout.widget.ConstraintSet.TOP
import androidx.core.view.isInvisible
-import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.internal.policy.SystemBarUtils
import com.android.keyguard.ClockEventController
import com.android.keyguard.KeyguardClockSwitch
import com.android.systemui.animation.view.LaunchableImageView
import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor
import com.android.systemui.broadcast.BroadcastDispatcher
-import com.android.systemui.common.ui.ConfigurationState
import com.android.systemui.communal.ui.binder.CommunalTutorialIndicatorViewBinder
import com.android.systemui.communal.ui.viewmodel.CommunalTutorialIndicatorViewModel
import com.android.systemui.coroutines.newTracingContext
-import com.android.systemui.customization.R as customR
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.keyguard.KeyguardBottomAreaRefactor
import com.android.systemui.keyguard.MigrateClocksToBlueprint
-import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
import com.android.systemui.keyguard.shared.model.ClockSizeSetting
import com.android.systemui.keyguard.ui.binder.KeyguardPreviewClockViewBinder
import com.android.systemui.keyguard.ui.binder.KeyguardPreviewSmartspaceViewBinder
import com.android.systemui.keyguard.ui.binder.KeyguardQuickAffordanceViewBinder
-import com.android.systemui.keyguard.ui.binder.KeyguardRootViewBinder
import com.android.systemui.keyguard.ui.view.KeyguardRootView
import com.android.systemui.keyguard.ui.view.layout.sections.DefaultShortcutsSection
-import com.android.systemui.keyguard.ui.viewmodel.KeyguardBlueprintViewModel
-import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel
-import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
import com.android.systemui.keyguard.ui.viewmodel.KeyguardPreviewClockViewModel
import com.android.systemui.keyguard.ui.viewmodel.KeyguardPreviewSmartspaceViewModel
import com.android.systemui.keyguard.ui.viewmodel.KeyguardQuickAffordancesCombinedViewModel
-import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel
-import com.android.systemui.keyguard.ui.viewmodel.OccludingAppDeviceEntryMessageViewModel
import com.android.systemui.monet.ColorScheme
import com.android.systemui.monet.Style
import com.android.systemui.plugins.clocks.ClockController
@@ -97,9 +86,6 @@ import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordance
import com.android.systemui.shared.quickaffordance.shared.model.KeyguardPreviewConstants
import com.android.systemui.statusbar.KeyguardIndicationController
import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController
-import com.android.systemui.statusbar.phone.KeyguardBottomAreaView
-import com.android.systemui.statusbar.phone.ScreenOffAnimationController
-import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator
import com.android.systemui.util.kotlin.DisposableHandles
import com.android.systemui.util.settings.SecureSettings
import dagger.assisted.Assisted
@@ -115,6 +101,8 @@ import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
import org.json.JSONException
import org.json.JSONObject
+import com.android.app.tracing.coroutines.launchTraced as launch
+import com.android.systemui.customization.R as customR
/** Renders the preview of the lock screen. */
class KeyguardPreviewRenderer
@@ -128,29 +116,20 @@ constructor(
@Background private val backgroundDispatcher: CoroutineDispatcher,
private val clockViewModel: KeyguardPreviewClockViewModel,
private val smartspaceViewModel: KeyguardPreviewSmartspaceViewModel,
- private val bottomAreaViewModel: KeyguardBottomAreaViewModel,
private val quickAffordancesCombinedViewModel: KeyguardQuickAffordancesCombinedViewModel,
displayManager: DisplayManager,
private val windowManager: WindowManager,
- private val configuration: ConfigurationState,
private val clockController: ClockEventController,
private val clockRegistry: ClockRegistry,
private val broadcastDispatcher: BroadcastDispatcher,
private val lockscreenSmartspaceController: LockscreenSmartspaceController,
private val udfpsOverlayInteractor: UdfpsOverlayInteractor,
private val indicationController: KeyguardIndicationController,
- private val keyguardRootViewModel: KeyguardRootViewModel,
- private val keyguardBlueprintViewModel: KeyguardBlueprintViewModel,
@Assisted bundle: Bundle,
- private val occludingAppDeviceEntryMessageViewModel: OccludingAppDeviceEntryMessageViewModel,
- private val chipbarCoordinator: ChipbarCoordinator,
- private val screenOffAnimationController: ScreenOffAnimationController,
private val shadeInteractor: ShadeInteractor,
private val secureSettings: SecureSettings,
private val communalTutorialViewModel: CommunalTutorialIndicatorViewModel,
private val defaultShortcutsSection: DefaultShortcutsSection,
- private val keyguardClockInteractor: KeyguardClockInteractor,
- private val keyguardClockViewModel: KeyguardClockViewModel,
private val keyguardQuickAffordanceViewBinder: KeyguardQuickAffordanceViewBinder,
) {
val hostToken: IBinder? = bundle.getBinder(KEY_HOST_TOKEN)
@@ -201,20 +180,12 @@ constructor(
disposables += DisposableHandle { coroutineScope.cancel() }
clockController.setFallbackWeatherData(WeatherData.getPlaceholderWeatherData())
- if (KeyguardBottomAreaRefactor.isEnabled) {
- quickAffordancesCombinedViewModel.enablePreviewMode(
- initiallySelectedSlotId =
- bundle.getString(KeyguardPreviewConstants.KEY_INITIALLY_SELECTED_SLOT_ID)
- ?: KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START,
- shouldHighlightSelectedAffordance = shouldHighlightSelectedAffordance,
- )
- } else {
- bottomAreaViewModel.enablePreviewMode(
- initiallySelectedSlotId =
- bundle.getString(KeyguardPreviewConstants.KEY_INITIALLY_SELECTED_SLOT_ID),
- shouldHighlightSelectedAffordance = shouldHighlightSelectedAffordance,
- )
- }
+ quickAffordancesCombinedViewModel.enablePreviewMode(
+ initiallySelectedSlotId =
+ bundle.getString(KeyguardPreviewConstants.KEY_INITIALLY_SELECTED_SLOT_ID)
+ ?: KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START,
+ shouldHighlightSelectedAffordance = shouldHighlightSelectedAffordance,
+ )
if (MigrateClocksToBlueprint.isEnabled) {
clockViewModel.shouldHighlightSelectedAffordance = shouldHighlightSelectedAffordance
}
@@ -241,10 +212,6 @@ constructor(
setupKeyguardRootView(previewContext, rootView)
- if (!KeyguardBottomAreaRefactor.isEnabled) {
- setUpBottomArea(rootView)
- }
-
var displayInfo: DisplayInfo? = null
display?.let {
displayInfo = DisplayInfo()
@@ -292,11 +259,7 @@ constructor(
}
fun onSlotSelected(slotId: String) {
- if (KeyguardBottomAreaRefactor.isEnabled) {
- quickAffordancesCombinedViewModel.onPreviewSlotSelected(slotId = slotId)
- } else {
- bottomAreaViewModel.onPreviewSlotSelected(slotId = slotId)
- }
+ quickAffordancesCombinedViewModel.onPreviewSlotSelected(slotId = slotId)
}
fun onPreviewQuickAffordanceSelected(slotId: String, quickAffordanceId: String) {
@@ -322,9 +285,7 @@ constructor(
isDestroyed = true
lockscreenSmartspaceController.disconnect()
disposables.dispose()
- if (KeyguardBottomAreaRefactor.isEnabled) {
- shortcutsBindings.forEach { it.destroy() }
- }
+ shortcutsBindings.forEach { it.destroy() }
}
/**
@@ -387,47 +348,8 @@ constructor(
smartSpaceView?.alpha = if (shouldHighlightSelectedAffordance) DIM_ALPHA else 1.0f
}
- @Deprecated("Deprecated as part of b/278057014")
- private fun setUpBottomArea(parentView: ViewGroup) {
- val bottomAreaView =
- LayoutInflater.from(context).inflate(R.layout.keyguard_bottom_area, parentView, false)
- as KeyguardBottomAreaView
- bottomAreaView.init(viewModel = bottomAreaViewModel)
- parentView.addView(
- bottomAreaView,
- FrameLayout.LayoutParams(
- FrameLayout.LayoutParams.MATCH_PARENT,
- FrameLayout.LayoutParams.MATCH_PARENT,
- ),
- )
- }
-
- @OptIn(ExperimentalCoroutinesApi::class)
private fun setupKeyguardRootView(previewContext: Context, rootView: FrameLayout) {
val keyguardRootView = KeyguardRootView(previewContext, null)
- if (!KeyguardBottomAreaRefactor.isEnabled) {
- disposables +=
- KeyguardRootViewBinder.bind(
- keyguardRootView,
- keyguardRootViewModel,
- keyguardBlueprintViewModel,
- configuration,
- occludingAppDeviceEntryMessageViewModel,
- chipbarCoordinator,
- screenOffAnimationController,
- shadeInteractor,
- keyguardClockInteractor,
- keyguardClockViewModel,
- null, // jank monitor not required for preview mode
- null, // device entry haptics not required preview mode
- null, // device entry haptics not required for preview mode
- null, // falsing manager not required for preview mode
- null, // keyguard view mediator is not required for preview mode
- null, // primary bouncer interactor is not required for preview mode
- mainDispatcher,
- null,
- )
- }
rootView.addView(
keyguardRootView,
FrameLayout.LayoutParams(
@@ -441,9 +363,7 @@ constructor(
if (MigrateClocksToBlueprint.isEnabled) keyguardRootView else rootView,
)
- if (KeyguardBottomAreaRefactor.isEnabled) {
- setupShortcuts(keyguardRootView)
- }
+ setupShortcuts(keyguardRootView)
if (!shouldHideClock) {
setUpClock(previewContext, rootView)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/PrimaryBouncerTransition.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/PrimaryBouncerTransition.kt
new file mode 100644
index 000000000000..cafc90930432
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/PrimaryBouncerTransition.kt
@@ -0,0 +1,98 @@
+/*
+ * 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.keyguard.ui.transitions
+
+import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerToPrimaryBouncerTransitionViewModel
+import com.android.systemui.keyguard.ui.viewmodel.AodToPrimaryBouncerTransitionViewModel
+import com.android.systemui.keyguard.ui.viewmodel.DozingToPrimaryBouncerTransitionViewModel
+import com.android.systemui.keyguard.ui.viewmodel.LockscreenToPrimaryBouncerTransitionViewModel
+import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToAodTransitionViewModel
+import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToDozingTransitionViewModel
+import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGlanceableHubTransitionViewModel
+import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel
+import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToLockscreenTransitionViewModel
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.IntoSet
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+
+/**
+ * Each PrimaryBouncerTransition is responsible for updating various UI states based on the nature
+ * of the transition.
+ *
+ * MUST list implementing classes in dagger module [PrimaryBouncerTransitionModule].
+ */
+interface PrimaryBouncerTransition {
+ /** Radius of blur applied to the window's root view. */
+ val windowBlurRadius: Flow<Float>
+
+ companion object {
+ const val MAX_BACKGROUND_BLUR_RADIUS = 150f
+ const val MIN_BACKGROUND_BLUR_RADIUS = 0f
+ }
+}
+
+/**
+ * Module that installs all the transitions from different keyguard states to and away from the
+ * primary bouncer.
+ */
+@ExperimentalCoroutinesApi
+@Module
+interface PrimaryBouncerTransitionModule {
+ @Binds
+ @IntoSet
+ fun fromAod(impl: AodToPrimaryBouncerTransitionViewModel): PrimaryBouncerTransition
+
+ @Binds
+ @IntoSet
+ fun fromAlternateBouncer(
+ impl: AlternateBouncerToPrimaryBouncerTransitionViewModel
+ ): PrimaryBouncerTransition
+
+ @Binds
+ @IntoSet
+ fun fromDozing(impl: DozingToPrimaryBouncerTransitionViewModel): PrimaryBouncerTransition
+
+ @Binds
+ @IntoSet
+ fun fromLockscreen(
+ impl: LockscreenToPrimaryBouncerTransitionViewModel
+ ): PrimaryBouncerTransition
+
+ @Binds
+ @IntoSet
+ fun toAod(impl: PrimaryBouncerToAodTransitionViewModel): PrimaryBouncerTransition
+
+ @Binds
+ @IntoSet
+ fun toLockscreen(impl: PrimaryBouncerToLockscreenTransitionViewModel): PrimaryBouncerTransition
+
+ @Binds
+ @IntoSet
+ fun toDozing(impl: PrimaryBouncerToDozingTransitionViewModel): PrimaryBouncerTransition
+
+ @Binds
+ @IntoSet
+ fun toGlanceableHub(
+ impl: PrimaryBouncerToGlanceableHubTransitionViewModel
+ ): PrimaryBouncerTransition
+
+ @Binds
+ @IntoSet
+ fun toGone(impl: PrimaryBouncerToGoneTransitionViewModel): PrimaryBouncerTransition
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt
index aa7eb2933992..e8fce9ca4aa8 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt
@@ -22,7 +22,6 @@ import android.graphics.Point
import android.graphics.Rect
import android.util.DisplayMetrics
import android.util.Log
-import android.view.View
import android.view.WindowManager
import androidx.annotation.VisibleForTesting
import androidx.constraintlayout.widget.ConstraintLayout
@@ -32,8 +31,6 @@ import com.android.systemui.customization.R as customR
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
-import com.android.systemui.keyguard.KeyguardBottomAreaRefactor
-import com.android.systemui.keyguard.MigrateClocksToBlueprint
import com.android.systemui.keyguard.shared.model.KeyguardSection
import com.android.systemui.keyguard.ui.binder.DeviceEntryIconViewBinder
import com.android.systemui.keyguard.ui.view.DeviceEntryIconView
@@ -76,10 +73,6 @@ constructor(
private var disposableHandle: DisposableHandle? = null
override fun addViews(constraintLayout: ConstraintLayout) {
- if (!KeyguardBottomAreaRefactor.isEnabled && !MigrateClocksToBlueprint.isEnabled) {
- return
- }
-
val view =
DeviceEntryIconView(
context,
@@ -194,38 +187,6 @@ constructor(
sensorRect.left,
)
}
-
- // This is only intended to be here until the KeyguardBottomAreaRefactor flag is enabled
- // Without this logic, the lock icon location changes but the KeyguardBottomAreaView is not
- // updated and visible ui layout jank occurs. This is due to AmbientIndicationContainer
- // being in NPVC and laying out prior to the KeyguardRootView.
- // Remove when KeyguardBottomAreaRefactor is enabled.
- if (!KeyguardBottomAreaRefactor.isEnabled) {
- with(notificationPanelView) {
- val isUdfpsSupported = deviceEntryIconViewModel.get().isUdfpsSupported.value
- val bottomAreaViewRight = findViewById<View>(R.id.keyguard_bottom_area)?.right ?: 0
- findViewById<View>(R.id.ambient_indication_container)?.let {
- val (ambientLeft, ambientTop) = it.locationOnScreen
- if (isUdfpsSupported) {
- // make top of ambient indication view the bottom of the lock icon
- it.layout(
- ambientLeft,
- sensorRect.bottom,
- bottomAreaViewRight - ambientLeft,
- ambientTop + it.measuredHeight,
- )
- } else {
- // make bottom of ambient indication view the top of the lock icon
- it.layout(
- ambientLeft,
- sensorRect.top - it.measuredHeight,
- bottomAreaViewRight - ambientLeft,
- sensorRect.top,
- )
- }
- }
- }
- }
}
companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultIndicationAreaSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultIndicationAreaSection.kt
index 2d9dac41ba6e..5bf56e8de9a7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultIndicationAreaSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultIndicationAreaSection.kt
@@ -21,7 +21,6 @@ import android.content.Context
import android.view.ViewGroup
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet
-import com.android.systemui.keyguard.KeyguardBottomAreaRefactor
import com.android.systemui.keyguard.shared.model.KeyguardSection
import com.android.systemui.keyguard.ui.binder.KeyguardIndicationAreaBinder
import com.android.systemui.keyguard.ui.view.KeyguardIndicationArea
@@ -43,21 +42,17 @@ constructor(
private var indicationAreaHandle: DisposableHandle? = null
override fun addViews(constraintLayout: ConstraintLayout) {
- if (KeyguardBottomAreaRefactor.isEnabled) {
- val view = KeyguardIndicationArea(context, null)
- constraintLayout.addView(view)
- }
+ val view = KeyguardIndicationArea(context, null)
+ constraintLayout.addView(view)
}
override fun bindData(constraintLayout: ConstraintLayout) {
- if (KeyguardBottomAreaRefactor.isEnabled) {
- indicationAreaHandle =
- KeyguardIndicationAreaBinder.bind(
- constraintLayout.requireViewById(R.id.keyguard_indication_area),
- keyguardIndicationAreaViewModel,
- indicationController,
- )
- }
+ indicationAreaHandle =
+ KeyguardIndicationAreaBinder.bind(
+ constraintLayout.requireViewById(R.id.keyguard_indication_area),
+ keyguardIndicationAreaViewModel,
+ indicationController,
+ )
}
override fun applyConstraints(constraintSet: ConstraintSet) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultSettingsPopupMenuSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultSettingsPopupMenuSection.kt
index 5cd5172d1851..f973ced59dcf 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultSettingsPopupMenuSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultSettingsPopupMenuSection.kt
@@ -31,7 +31,6 @@ import androidx.constraintlayout.widget.ConstraintSet.WRAP_CONTENT
import androidx.core.view.isVisible
import com.android.systemui.animation.view.LaunchableLinearLayout
import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.keyguard.KeyguardBottomAreaRefactor
import com.android.systemui.keyguard.shared.model.KeyguardSection
import com.android.systemui.keyguard.ui.binder.KeyguardSettingsViewBinder
import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel
@@ -56,9 +55,6 @@ constructor(
private var settingsPopupMenuHandle: DisposableHandle? = null
override fun addViews(constraintLayout: ConstraintLayout) {
- if (!KeyguardBottomAreaRefactor.isEnabled) {
- return
- }
val view =
LayoutInflater.from(constraintLayout.context)
.inflate(R.layout.keyguard_settings_popup_menu, constraintLayout, false)
@@ -71,17 +67,15 @@ constructor(
}
override fun bindData(constraintLayout: ConstraintLayout) {
- if (KeyguardBottomAreaRefactor.isEnabled) {
- settingsPopupMenuHandle =
- KeyguardSettingsViewBinder.bind(
- constraintLayout.requireViewById<View>(R.id.keyguard_settings_button),
- keyguardSettingsMenuViewModel,
- keyguardTouchHandlingViewModel,
- keyguardRootViewModel,
- vibratorHelper,
- activityStarter,
- )
- }
+ settingsPopupMenuHandle =
+ KeyguardSettingsViewBinder.bind(
+ constraintLayout.requireViewById<View>(R.id.keyguard_settings_button),
+ keyguardSettingsMenuViewModel,
+ keyguardTouchHandlingViewModel,
+ keyguardRootViewModel,
+ vibratorHelper,
+ activityStarter,
+ )
}
override fun applyConstraints(constraintSet: ConstraintSet) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultShortcutsSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultShortcutsSection.kt
index d3895def28e0..82f142b03323 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultShortcutsSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultShortcutsSection.kt
@@ -28,7 +28,6 @@ import androidx.constraintlayout.widget.ConstraintSet.RIGHT
import androidx.constraintlayout.widget.ConstraintSet.VISIBILITY_MODE_IGNORE
import com.android.systemui.animation.view.LaunchableImageView
import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.keyguard.KeyguardBottomAreaRefactor
import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.ui.binder.KeyguardQuickAffordanceViewBinder
@@ -49,7 +48,6 @@ constructor(
@Named(LOCKSCREEN_INSTANCE)
private val keyguardQuickAffordancesCombinedViewModel:
KeyguardQuickAffordancesCombinedViewModel,
- private val keyguardRootViewModel: KeyguardRootViewModel,
private val indicationController: KeyguardIndicationController,
private val keyguardBlueprintInteractor: Lazy<KeyguardBlueprintInteractor>,
private val keyguardQuickAffordanceViewBinder: KeyguardQuickAffordanceViewBinder,
@@ -60,46 +58,42 @@ constructor(
private var safeInsetBottom = 0
override fun addViews(constraintLayout: ConstraintLayout) {
- if (KeyguardBottomAreaRefactor.isEnabled) {
- addLeftShortcut(constraintLayout)
- addRightShortcut(constraintLayout)
+ addLeftShortcut(constraintLayout)
+ addRightShortcut(constraintLayout)
- constraintLayout
- .requireViewById<LaunchableImageView>(R.id.start_button)
- .setOnApplyWindowInsetsListener { _, windowInsets ->
- val tempSafeInset = windowInsets?.displayCutout?.safeInsetBottom ?: 0
- if (safeInsetBottom != tempSafeInset) {
- safeInsetBottom = tempSafeInset
- keyguardBlueprintInteractor
- .get()
- .refreshBlueprint(IntraBlueprintTransition.Type.DefaultTransition)
- }
- WindowInsets.CONSUMED
+ constraintLayout
+ .requireViewById<LaunchableImageView>(R.id.start_button)
+ .setOnApplyWindowInsetsListener { _, windowInsets ->
+ val tempSafeInset = windowInsets?.displayCutout?.safeInsetBottom ?: 0
+ if (safeInsetBottom != tempSafeInset) {
+ safeInsetBottom = tempSafeInset
+ keyguardBlueprintInteractor
+ .get()
+ .refreshBlueprint(IntraBlueprintTransition.Type.DefaultTransition)
}
- }
+ WindowInsets.CONSUMED
+ }
}
override fun bindData(constraintLayout: ConstraintLayout) {
- if (KeyguardBottomAreaRefactor.isEnabled) {
- leftShortcutHandle?.destroy()
- leftShortcutHandle =
- keyguardQuickAffordanceViewBinder.bind(
- constraintLayout.requireViewById(R.id.start_button),
- keyguardQuickAffordancesCombinedViewModel.startButton,
- keyguardQuickAffordancesCombinedViewModel.transitionAlpha,
- ) {
- indicationController.showTransientIndication(it)
- }
- rightShortcutHandle?.destroy()
- rightShortcutHandle =
- keyguardQuickAffordanceViewBinder.bind(
- constraintLayout.requireViewById(R.id.end_button),
- keyguardQuickAffordancesCombinedViewModel.endButton,
- keyguardQuickAffordancesCombinedViewModel.transitionAlpha,
- ) {
- indicationController.showTransientIndication(it)
- }
- }
+ leftShortcutHandle?.destroy()
+ leftShortcutHandle =
+ keyguardQuickAffordanceViewBinder.bind(
+ constraintLayout.requireViewById(R.id.start_button),
+ keyguardQuickAffordancesCombinedViewModel.startButton,
+ keyguardQuickAffordancesCombinedViewModel.transitionAlpha,
+ ) {
+ indicationController.showTransientIndication(it)
+ }
+ rightShortcutHandle?.destroy()
+ rightShortcutHandle =
+ keyguardQuickAffordanceViewBinder.bind(
+ constraintLayout.requireViewById(R.id.end_button),
+ keyguardQuickAffordancesCombinedViewModel.endButton,
+ keyguardQuickAffordancesCombinedViewModel.transitionAlpha,
+ ) {
+ indicationController.showTransientIndication(it)
+ }
}
override fun applyConstraints(constraintSet: ConstraintSet) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultUdfpsAccessibilityOverlaySection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultUdfpsAccessibilityOverlaySection.kt
index 0ae1400b1906..8186aa3746cf 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultUdfpsAccessibilityOverlaySection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultUdfpsAccessibilityOverlaySection.kt
@@ -23,7 +23,6 @@ import androidx.constraintlayout.widget.ConstraintSet
import com.android.systemui.deviceentry.ui.binder.UdfpsAccessibilityOverlayBinder
import com.android.systemui.deviceentry.ui.view.UdfpsAccessibilityOverlay
import com.android.systemui.deviceentry.ui.viewmodel.DeviceEntryUdfpsAccessibilityOverlayViewModel
-import com.android.systemui.keyguard.KeyguardBottomAreaRefactor
import com.android.systemui.keyguard.shared.model.KeyguardSection
import com.android.systemui.res.R
import com.android.systemui.shade.ShadeDisplayAware
@@ -67,16 +66,12 @@ constructor(
ConstraintSet.BOTTOM,
)
- if (KeyguardBottomAreaRefactor.isEnabled) {
- connect(
- viewId,
- ConstraintSet.BOTTOM,
- R.id.keyguard_indication_area,
- ConstraintSet.TOP,
- )
- } else {
- connect(viewId, ConstraintSet.BOTTOM, ConstraintSet.PARENT_ID, ConstraintSet.BOTTOM)
- }
+ connect(
+ viewId,
+ ConstraintSet.BOTTOM,
+ R.id.keyguard_indication_area,
+ ConstraintSet.TOP,
+ )
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModel.kt
index 85ce5cd0f9fd..5b23e4796fae 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModel.kt
@@ -16,6 +16,7 @@
package com.android.systemui.keyguard.ui.viewmodel
+import android.util.MathUtils
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.domain.interactor.FromAlternateBouncerTransitionInteractor
import com.android.systemui.keyguard.shared.model.Edge
@@ -23,6 +24,9 @@ import com.android.systemui.keyguard.shared.model.KeyguardState.ALTERNATE_BOUNCE
import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER
import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
+import com.android.systemui.keyguard.ui.transitions.PrimaryBouncerTransition
+import com.android.systemui.keyguard.ui.transitions.PrimaryBouncerTransition.Companion.MAX_BACKGROUND_BLUR_RADIUS
+import com.android.systemui.keyguard.ui.transitions.PrimaryBouncerTransition.Companion.MIN_BACKGROUND_BLUR_RADIUS
import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.scene.ui.composable.transitions.TO_BOUNCER_FADE_FRACTION
@@ -38,7 +42,10 @@ import kotlinx.coroutines.flow.Flow
@SysUISingleton
class AlternateBouncerToPrimaryBouncerTransitionViewModel
@Inject
-constructor(animationFlow: KeyguardTransitionAnimationFlow) : DeviceEntryIconTransition {
+constructor(
+ animationFlow: KeyguardTransitionAnimationFlow,
+ shadeDependentFlows: ShadeDependentFlows,
+) : DeviceEntryIconTransition, PrimaryBouncerTransition {
private val transitionAnimation =
animationFlow
.setup(
@@ -65,4 +72,18 @@ constructor(animationFlow: KeyguardTransitionAnimationFlow) : DeviceEntryIconTra
override val deviceEntryParentViewAlpha: Flow<Float> =
transitionAnimation.immediatelyTransitionTo(0f)
+
+ override val windowBlurRadius: Flow<Float> =
+ shadeDependentFlows.transitionFlow(
+ flowWhenShadeIsExpanded =
+ transitionAnimation.immediatelyTransitionTo(MAX_BACKGROUND_BLUR_RADIUS),
+ flowWhenShadeIsNotExpanded =
+ transitionAnimation.sharedFlow(
+ duration = FromAlternateBouncerTransitionInteractor.TO_PRIMARY_BOUNCER_DURATION,
+ onStep = { step ->
+ MathUtils.lerp(MIN_BACKGROUND_BLUR_RADIUS, MAX_BACKGROUND_BLUR_RADIUS, step)
+ },
+ onFinish = { MAX_BACKGROUND_BLUR_RADIUS },
+ ),
+ )
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToPrimaryBouncerTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToPrimaryBouncerTransitionViewModel.kt
index 35f05f55caa1..e6b796fb92c5 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToPrimaryBouncerTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToPrimaryBouncerTransitionViewModel.kt
@@ -23,6 +23,8 @@ import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER
import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
+import com.android.systemui.keyguard.ui.transitions.PrimaryBouncerTransition
+import com.android.systemui.keyguard.ui.transitions.PrimaryBouncerTransition.Companion.MAX_BACKGROUND_BLUR_RADIUS
import com.android.systemui.scene.shared.model.Scenes
import javax.inject.Inject
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -36,19 +38,19 @@ import kotlinx.coroutines.flow.Flow
@SysUISingleton
class AodToPrimaryBouncerTransitionViewModel
@Inject
-constructor(
- animationFlow: KeyguardTransitionAnimationFlow,
-) : DeviceEntryIconTransition {
+constructor(animationFlow: KeyguardTransitionAnimationFlow) :
+ DeviceEntryIconTransition, PrimaryBouncerTransition {
private val transitionAnimation =
animationFlow
.setup(
duration = FromAodTransitionInteractor.TO_PRIMARY_BOUNCER_DURATION,
edge = Edge.create(from = AOD, to = Scenes.Bouncer),
)
- .setupWithoutSceneContainer(
- edge = Edge.create(from = AOD, to = PRIMARY_BOUNCER),
- )
+ .setupWithoutSceneContainer(edge = Edge.create(from = AOD, to = PRIMARY_BOUNCER))
override val deviceEntryParentViewAlpha: Flow<Float> =
transitionAnimation.immediatelyTransitionTo(0f)
+
+ override val windowBlurRadius: Flow<Float> =
+ transitionAnimation.immediatelyTransitionTo(MAX_BACKGROUND_BLUR_RADIUS)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToPrimaryBouncerTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToPrimaryBouncerTransitionViewModel.kt
index 7ddf641e9e8e..c1670c3814dc 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToPrimaryBouncerTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToPrimaryBouncerTransitionViewModel.kt
@@ -16,6 +16,7 @@
package com.android.systemui.keyguard.ui.viewmodel
+import android.util.MathUtils
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.domain.interactor.FromDozingTransitionInteractor.Companion.TO_PRIMARY_BOUNCER_DURATION
import com.android.systemui.keyguard.shared.model.Edge
@@ -23,6 +24,9 @@ import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING
import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER
import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
+import com.android.systemui.keyguard.ui.transitions.PrimaryBouncerTransition
+import com.android.systemui.keyguard.ui.transitions.PrimaryBouncerTransition.Companion.MAX_BACKGROUND_BLUR_RADIUS
+import com.android.systemui.keyguard.ui.transitions.PrimaryBouncerTransition.Companion.MIN_BACKGROUND_BLUR_RADIUS
import com.android.systemui.scene.shared.model.Scenes
import javax.inject.Inject
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -36,9 +40,8 @@ import kotlinx.coroutines.flow.Flow
@SysUISingleton
class DozingToPrimaryBouncerTransitionViewModel
@Inject
-constructor(
- animationFlow: KeyguardTransitionAnimationFlow,
-) : DeviceEntryIconTransition {
+constructor(animationFlow: KeyguardTransitionAnimationFlow) :
+ DeviceEntryIconTransition, PrimaryBouncerTransition {
private val transitionAnimation =
animationFlow
@@ -46,10 +49,17 @@ constructor(
duration = TO_PRIMARY_BOUNCER_DURATION,
edge = Edge.create(from = DOZING, to = Scenes.Bouncer),
)
- .setupWithoutSceneContainer(
- edge = Edge.create(from = DOZING, to = PRIMARY_BOUNCER),
- )
+ .setupWithoutSceneContainer(edge = Edge.create(from = DOZING, to = PRIMARY_BOUNCER))
override val deviceEntryParentViewAlpha: Flow<Float> =
transitionAnimation.immediatelyTransitionTo(0f)
+
+ override val windowBlurRadius: Flow<Float> =
+ transitionAnimation.sharedFlow(
+ TO_PRIMARY_BOUNCER_DURATION,
+ onStep = { step ->
+ MathUtils.lerp(MIN_BACKGROUND_BLUR_RADIUS, MAX_BACKGROUND_BLUR_RADIUS, step)
+ },
+ onFinish = { MAX_BACKGROUND_BLUR_RADIUS },
+ )
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt
deleted file mode 100644
index 6fe51ae885be..000000000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt
+++ /dev/null
@@ -1,252 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.keyguard.ui.viewmodel
-
-import androidx.annotation.VisibleForTesting
-import com.android.systemui.doze.util.BurnInHelperWrapper
-import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor
-import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
-import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor
-import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordanceModel
-import com.android.systemui.keyguard.shared.quickaffordance.ActivationState
-import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition
-import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
-import javax.inject.Inject
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.distinctUntilChanged
-import kotlinx.coroutines.flow.flatMapLatest
-import kotlinx.coroutines.flow.flowOf
-import kotlinx.coroutines.flow.map
-
-/** View-model for the keyguard bottom area view */
-@OptIn(ExperimentalCoroutinesApi::class)
-class KeyguardBottomAreaViewModel
-@Inject
-constructor(
- private val keyguardInteractor: KeyguardInteractor,
- private val quickAffordanceInteractor: KeyguardQuickAffordanceInteractor,
- private val bottomAreaInteractor: KeyguardBottomAreaInteractor,
- private val burnInHelperWrapper: BurnInHelperWrapper,
- private val keyguardTouchHandlingViewModel: KeyguardTouchHandlingViewModel,
- val settingsMenuViewModel: KeyguardSettingsMenuViewModel,
-) {
- data class PreviewMode(
- val isInPreviewMode: Boolean = false,
- val shouldHighlightSelectedAffordance: Boolean = false,
- )
-
- /**
- * Whether this view-model instance is powering the preview experience that renders exclusively
- * in the wallpaper picker application. This should _always_ be `false` for the real lock screen
- * experience.
- */
- val previewMode = MutableStateFlow(PreviewMode())
-
- /**
- * ID of the slot that's currently selected in the preview that renders exclusively in the
- * wallpaper picker application. This is ignored for the actual, real lock screen experience.
- */
- private val selectedPreviewSlotId =
- MutableStateFlow(KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START)
-
- /**
- * Whether quick affordances are "opaque enough" to be considered visible to and interactive by
- * the user. If they are not interactive, user input should not be allowed on them.
- *
- * Note that there is a margin of error, where we allow very, very slightly transparent views to
- * be considered "fully opaque" for the purpose of being interactive. This is to accommodate the
- * error margin of floating point arithmetic.
- *
- * A view that is visible but with an alpha of less than our threshold either means it's not
- * fully done fading in or is fading/faded out. Either way, it should not be
- * interactive/clickable unless "fully opaque" to avoid issues like in b/241830987.
- */
- private val areQuickAffordancesFullyOpaque: Flow<Boolean> =
- bottomAreaInteractor.alpha
- .map { alpha -> alpha >= AFFORDANCE_FULLY_OPAQUE_ALPHA_THRESHOLD }
- .distinctUntilChanged()
-
- /** An observable for the view-model of the "start button" quick affordance. */
- val startButton: Flow<KeyguardQuickAffordanceViewModel> =
- button(KeyguardQuickAffordancePosition.BOTTOM_START)
- /** An observable for the view-model of the "end button" quick affordance. */
- val endButton: Flow<KeyguardQuickAffordanceViewModel> =
- button(KeyguardQuickAffordancePosition.BOTTOM_END)
- /** An observable for whether the overlay container should be visible. */
- val isOverlayContainerVisible: Flow<Boolean> =
- keyguardInteractor.isDozing.map { !it }.distinctUntilChanged()
- /** An observable for the alpha level for the entire bottom area. */
- val alpha: Flow<Float> =
- previewMode.flatMapLatest {
- if (it.isInPreviewMode) {
- flowOf(1f)
- } else {
- bottomAreaInteractor.alpha.distinctUntilChanged()
- }
- }
- /** An observable for the x-offset by which the indication area should be translated. */
- val indicationAreaTranslationX: Flow<Float> =
- bottomAreaInteractor.clockPosition.map { it.x.toFloat() }.distinctUntilChanged()
-
- /** Returns an observable for the y-offset by which the indication area should be translated. */
- fun indicationAreaTranslationY(defaultBurnInOffset: Int): Flow<Float> {
- return keyguardInteractor.dozeAmount
- .map { dozeAmount ->
- dozeAmount *
- (burnInHelperWrapper.burnInOffset(
- /* amplitude = */ defaultBurnInOffset * 2,
- /* xAxis= */ false,
- ) - defaultBurnInOffset)
- }
- .distinctUntilChanged()
- }
-
- /**
- * Returns whether the keyguard bottom area should be constrained to the top of the lock icon
- */
- fun shouldConstrainToTopOfLockIcon(): Boolean =
- bottomAreaInteractor.shouldConstrainToTopOfLockIcon()
-
- /**
- * Puts this view-model in "preview mode", which means it's being used for UI that is rendering
- * the lock screen preview in wallpaper picker / settings and not the real experience on the
- * lock screen.
- *
- * @param initiallySelectedSlotId The ID of the initial slot to render as the selected one.
- * @param shouldHighlightSelectedAffordance Whether the selected quick affordance should be
- * highlighted (while all others are dimmed to make the selected one stand out).
- */
- fun enablePreviewMode(
- initiallySelectedSlotId: String?,
- shouldHighlightSelectedAffordance: Boolean,
- ) {
- previewMode.value =
- PreviewMode(
- isInPreviewMode = true,
- shouldHighlightSelectedAffordance = shouldHighlightSelectedAffordance,
- )
- onPreviewSlotSelected(
- initiallySelectedSlotId ?: KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START
- )
- }
-
- /**
- * Notifies that a slot with the given ID has been selected in the preview experience that is
- * rendering in the wallpaper picker. This is ignored for the real lock screen experience.
- *
- * @see enablePreviewMode
- */
- fun onPreviewSlotSelected(slotId: String) {
- selectedPreviewSlotId.value = slotId
- }
-
- /**
- * Notifies that some input gesture has started somewhere in the bottom area that's outside of
- * the lock screen settings menu item pop-up.
- */
- fun onTouchedOutsideLockScreenSettingsMenu() {
- keyguardTouchHandlingViewModel.onTouchedOutside()
- }
-
- private fun button(
- position: KeyguardQuickAffordancePosition
- ): Flow<KeyguardQuickAffordanceViewModel> {
- return previewMode.flatMapLatest { previewMode ->
- combine(
- if (previewMode.isInPreviewMode) {
- quickAffordanceInteractor.quickAffordanceAlwaysVisible(position = position)
- } else {
- quickAffordanceInteractor.quickAffordance(position = position)
- },
- bottomAreaInteractor.animateDozingTransitions.distinctUntilChanged(),
- areQuickAffordancesFullyOpaque,
- selectedPreviewSlotId,
- quickAffordanceInteractor.useLongPress(),
- ) { model, animateReveal, isFullyOpaque, selectedPreviewSlotId, useLongPress ->
- val slotId = position.toSlotId()
- val isSelected = selectedPreviewSlotId == slotId
- model.toViewModel(
- animateReveal = !previewMode.isInPreviewMode && animateReveal,
- isClickable = isFullyOpaque && !previewMode.isInPreviewMode,
- isSelected =
- previewMode.isInPreviewMode &&
- previewMode.shouldHighlightSelectedAffordance &&
- isSelected,
- isDimmed =
- previewMode.isInPreviewMode &&
- previewMode.shouldHighlightSelectedAffordance &&
- !isSelected,
- forceInactive = previewMode.isInPreviewMode,
- slotId = slotId,
- useLongPress = useLongPress,
- )
- }
- .distinctUntilChanged()
- }
- }
-
- private fun KeyguardQuickAffordanceModel.toViewModel(
- animateReveal: Boolean,
- isClickable: Boolean,
- isSelected: Boolean,
- isDimmed: Boolean,
- forceInactive: Boolean,
- slotId: String,
- useLongPress: Boolean,
- ): KeyguardQuickAffordanceViewModel {
- return when (this) {
- is KeyguardQuickAffordanceModel.Visible ->
- KeyguardQuickAffordanceViewModel(
- configKey = configKey,
- isVisible = true,
- animateReveal = animateReveal,
- icon = icon,
- onClicked = { parameters ->
- quickAffordanceInteractor.onQuickAffordanceTriggered(
- configKey = parameters.configKey,
- expandable = parameters.expandable,
- slotId = parameters.slotId,
- )
- },
- isClickable = isClickable,
- isActivated = !forceInactive && activationState is ActivationState.Active,
- isSelected = isSelected,
- useLongPress = useLongPress,
- isDimmed = isDimmed,
- slotId = slotId,
- )
- is KeyguardQuickAffordanceModel.Hidden ->
- KeyguardQuickAffordanceViewModel(
- slotId = slotId,
- )
- }
- }
-
- companion object {
- // We select a value that's less than 1.0 because we want floating point math precision to
- // not be a factor in determining whether the affordance UI is fully opaque. The number we
- // choose needs to be close enough 1.0 such that the user can't easily tell the difference
- // between the UI with an alpha at the threshold and when the alpha is 1.0. At the same
- // time, we don't want the number to be too close to 1.0 such that there is a chance that we
- // never treat the affordance UI as "fully opaque" as that would risk making it forever not
- // clickable.
- @VisibleForTesting const val AFFORDANCE_FULLY_OPAQUE_ALPHA_THRESHOLD = 0.95f
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModel.kt
index bc3ef02a0ec5..4663a2b3c20c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModel.kt
@@ -21,10 +21,8 @@ import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.doze.util.BurnInHelperWrapper
-import com.android.systemui.keyguard.KeyguardBottomAreaRefactor
import com.android.systemui.keyguard.MigrateClocksToBlueprint
import com.android.systemui.keyguard.domain.interactor.BurnInInteractor
-import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.BurnInModel
@@ -48,8 +46,6 @@ class KeyguardIndicationAreaViewModel
@Inject
constructor(
private val keyguardInteractor: KeyguardInteractor,
- bottomAreaInteractor: KeyguardBottomAreaInteractor,
- keyguardBottomAreaViewModel: KeyguardBottomAreaViewModel,
private val burnInHelperWrapper: BurnInHelperWrapper,
burnInInteractor: BurnInInteractor,
@Named(KeyguardQuickAffordancesCombinedViewModelModule.Companion.LOCKSCREEN_INSTANCE)
@@ -64,9 +60,6 @@ constructor(
/** Notifies when a new configuration is set */
val configurationChange: Flow<Unit> = configurationInteractor.onAnyConfigurationChange
- /** An observable for the alpha level for the entire bottom area. */
- val alpha: Flow<Float> = keyguardBottomAreaViewModel.alpha
-
/** An observable for the visibility value for the indication area view. */
val visible: Flow<Boolean> =
anyOf(
@@ -76,22 +69,12 @@ constructor(
/** An observable for whether the indication area should be padded. */
val isIndicationAreaPadded: Flow<Boolean> =
- if (KeyguardBottomAreaRefactor.isEnabled) {
- combine(shortcutsCombinedViewModel.startButton, shortcutsCombinedViewModel.endButton) {
- startButtonModel,
- endButtonModel ->
- startButtonModel.isVisible || endButtonModel.isVisible
- }
- .distinctUntilChanged()
- } else {
- combine(
- keyguardBottomAreaViewModel.startButton,
- keyguardBottomAreaViewModel.endButton,
- ) { startButtonModel, endButtonModel ->
- startButtonModel.isVisible || endButtonModel.isVisible
- }
- .distinctUntilChanged()
+ combine(shortcutsCombinedViewModel.startButton, shortcutsCombinedViewModel.endButton) {
+ startButtonModel,
+ endButtonModel ->
+ startButtonModel.isVisible || endButtonModel.isVisible
}
+ .distinctUntilChanged()
@OptIn(ExperimentalCoroutinesApi::class)
private val burnIn: Flow<BurnInModel> =
@@ -114,11 +97,7 @@ constructor(
/** An observable for the x-offset by which the indication area should be translated. */
val indicationAreaTranslationX: Flow<Float> =
- if (MigrateClocksToBlueprint.isEnabled || KeyguardBottomAreaRefactor.isEnabled) {
- burnIn.map { it.translationX.toFloat() }.flowOn(mainDispatcher)
- } else {
- bottomAreaInteractor.clockPosition.map { it.x.toFloat() }.distinctUntilChanged()
- }
+ burnIn.map { it.translationX.toFloat() }.flowOn(mainDispatcher)
/** Returns an observable for the y-offset by which the indication area should be translated. */
fun indicationAreaTranslationY(defaultBurnInOffset: Int): Flow<Float> {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModel.kt
index 914730e1ea4a..48cc8ad5321a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModel.kt
@@ -16,6 +16,7 @@
package com.android.systemui.keyguard.ui.viewmodel
+import android.util.MathUtils
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor
import com.android.systemui.keyguard.shared.model.Edge
@@ -23,6 +24,9 @@ import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER
import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
+import com.android.systemui.keyguard.ui.transitions.PrimaryBouncerTransition
+import com.android.systemui.keyguard.ui.transitions.PrimaryBouncerTransition.Companion.MAX_BACKGROUND_BLUR_RADIUS
+import com.android.systemui.keyguard.ui.transitions.PrimaryBouncerTransition.Companion.MIN_BACKGROUND_BLUR_RADIUS
import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.scene.ui.composable.transitions.TO_BOUNCER_FADE_FRACTION
@@ -42,7 +46,7 @@ class LockscreenToPrimaryBouncerTransitionViewModel
constructor(
shadeDependentFlows: ShadeDependentFlows,
animationFlow: KeyguardTransitionAnimationFlow,
-) : DeviceEntryIconTransition {
+) : DeviceEntryIconTransition, PrimaryBouncerTransition {
private val transitionAnimation =
animationFlow
.setup(
@@ -78,4 +82,16 @@ constructor(
),
flowWhenShadeIsExpanded = transitionAnimation.immediatelyTransitionTo(0f),
)
+ override val windowBlurRadius: Flow<Float> =
+ shadeDependentFlows.transitionFlow(
+ flowWhenShadeIsExpanded =
+ transitionAnimation.immediatelyTransitionTo(MAX_BACKGROUND_BLUR_RADIUS),
+ flowWhenShadeIsNotExpanded =
+ transitionAnimation.sharedFlow(
+ duration = FromLockscreenTransitionInteractor.TO_PRIMARY_BOUNCER_DURATION,
+ onStep = {
+ MathUtils.lerp(MIN_BACKGROUND_BLUR_RADIUS, MAX_BACKGROUND_BLUR_RADIUS, it)
+ },
+ ),
+ )
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToAodTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToAodTransitionViewModel.kt
index 501feca8c4f7..f14144e36fb1 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToAodTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToAodTransitionViewModel.kt
@@ -16,6 +16,7 @@
package com.android.systemui.keyguard.ui.viewmodel
+import android.util.MathUtils
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor
import com.android.systemui.keyguard.domain.interactor.FromPrimaryBouncerTransitionInteractor
@@ -24,6 +25,9 @@ import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER
import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
+import com.android.systemui.keyguard.ui.transitions.PrimaryBouncerTransition
+import com.android.systemui.keyguard.ui.transitions.PrimaryBouncerTransition.Companion.MAX_BACKGROUND_BLUR_RADIUS
+import com.android.systemui.keyguard.ui.transitions.PrimaryBouncerTransition.Companion.MIN_BACKGROUND_BLUR_RADIUS
import com.android.systemui.scene.shared.model.Scenes
import javax.inject.Inject
import kotlin.time.Duration.Companion.milliseconds
@@ -43,16 +47,14 @@ class PrimaryBouncerToAodTransitionViewModel
constructor(
deviceEntryUdfpsInteractor: DeviceEntryUdfpsInteractor,
animationFlow: KeyguardTransitionAnimationFlow,
-) : DeviceEntryIconTransition {
+) : DeviceEntryIconTransition, PrimaryBouncerTransition {
private val transitionAnimation =
animationFlow
.setup(
duration = FromPrimaryBouncerTransitionInteractor.TO_AOD_DURATION,
edge = Edge.create(from = Scenes.Bouncer, to = AOD),
)
- .setupWithoutSceneContainer(
- edge = Edge.create(from = PRIMARY_BOUNCER, to = AOD),
- )
+ .setupWithoutSceneContainer(edge = Edge.create(from = PRIMARY_BOUNCER, to = AOD))
val deviceEntryBackgroundViewAlpha: Flow<Float> =
transitionAnimation.immediatelyTransitionTo(0f)
@@ -60,7 +62,7 @@ constructor(
val lockscreenAlpha: Flow<Float> =
transitionAnimation.sharedFlow(
duration = FromPrimaryBouncerTransitionInteractor.TO_AOD_DURATION,
- onStep = { it }
+ onStep = { it },
)
override val deviceEntryParentViewAlpha: Flow<Float> =
@@ -77,4 +79,13 @@ constructor(
emptyFlow()
}
}
+
+ override val windowBlurRadius: Flow<Float> =
+ transitionAnimation.sharedFlow(
+ duration = FromPrimaryBouncerTransitionInteractor.TO_AOD_DURATION,
+ onStep = { step ->
+ MathUtils.lerp(MAX_BACKGROUND_BLUR_RADIUS, MIN_BACKGROUND_BLUR_RADIUS, step)
+ },
+ onFinish = { MIN_BACKGROUND_BLUR_RADIUS },
+ )
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToDozingTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToDozingTransitionViewModel.kt
index e5bb46432226..a24ed264e4c1 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToDozingTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToDozingTransitionViewModel.kt
@@ -24,6 +24,7 @@ import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING
import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER
import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
+import com.android.systemui.keyguard.ui.transitions.PrimaryBouncerTransition
import com.android.systemui.scene.shared.model.Scenes
import javax.inject.Inject
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -42,7 +43,7 @@ class PrimaryBouncerToDozingTransitionViewModel
constructor(
deviceEntryUdfpsInteractor: DeviceEntryUdfpsInteractor,
animationFlow: KeyguardTransitionAnimationFlow,
-) : DeviceEntryIconTransition {
+) : DeviceEntryIconTransition, PrimaryBouncerTransition {
private val transitionAnimation =
animationFlow
@@ -50,9 +51,7 @@ constructor(
duration = TO_DOZING_DURATION,
edge = Edge.create(from = Scenes.Bouncer, to = DOZING),
)
- .setupWithoutSceneContainer(
- edge = Edge.create(from = PRIMARY_BOUNCER, to = DOZING),
- )
+ .setupWithoutSceneContainer(edge = Edge.create(from = PRIMARY_BOUNCER, to = DOZING))
val deviceEntryBackgroundViewAlpha: Flow<Float> =
transitionAnimation.immediatelyTransitionTo(0f)
@@ -66,4 +65,9 @@ constructor(
emptyFlow()
}
}
+
+ override val windowBlurRadius: Flow<Float> =
+ transitionAnimation.immediatelyTransitionTo(
+ PrimaryBouncerTransition.MIN_BACKGROUND_BLUR_RADIUS
+ )
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGlanceableHubTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGlanceableHubTransitionViewModel.kt
index 9ec15dcff5f4..b52a3905a263 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGlanceableHubTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGlanceableHubTransitionViewModel.kt
@@ -23,13 +23,16 @@ import com.android.systemui.keyguard.shared.model.KeyguardState.GLANCEABLE_HUB
import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER
import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
+import com.android.systemui.keyguard.ui.transitions.PrimaryBouncerTransition
+import com.android.systemui.keyguard.ui.transitions.PrimaryBouncerTransition.Companion.MIN_BACKGROUND_BLUR_RADIUS
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
@SysUISingleton
class PrimaryBouncerToGlanceableHubTransitionViewModel
@Inject
-constructor(animationFlow: KeyguardTransitionAnimationFlow) : DeviceEntryIconTransition {
+constructor(animationFlow: KeyguardTransitionAnimationFlow) :
+ DeviceEntryIconTransition, PrimaryBouncerTransition {
private val transitionAnimation =
animationFlow
.setup(duration = TO_GLANCEABLE_HUB_DURATION, edge = Edge.INVALID)
@@ -37,4 +40,7 @@ constructor(animationFlow: KeyguardTransitionAnimationFlow) : DeviceEntryIconTra
override val deviceEntryParentViewAlpha: Flow<Float> =
transitionAnimation.immediatelyTransitionTo(1f)
+
+ override val windowBlurRadius: Flow<Float> =
+ transitionAnimation.immediatelyTransitionTo(MIN_BACKGROUND_BLUR_RADIUS)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt
index 17c678e79d8b..713ac1527d5b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt
@@ -16,16 +16,21 @@
package com.android.systemui.keyguard.ui.viewmodel
+import android.util.MathUtils
import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
import com.android.systemui.bouncer.shared.flag.ComposeBouncerFlags
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.domain.interactor.FromPrimaryBouncerTransitionInteractor.Companion.TO_GONE_DURATION
+import com.android.systemui.keyguard.domain.interactor.FromPrimaryBouncerTransitionInteractor.Companion.TO_GONE_SHORT_DURATION
import com.android.systemui.keyguard.domain.interactor.KeyguardDismissActionInteractor
import com.android.systemui.keyguard.shared.model.Edge
import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER
import com.android.systemui.keyguard.shared.model.ScrimAlpha
import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
+import com.android.systemui.keyguard.ui.transitions.PrimaryBouncerTransition
+import com.android.systemui.keyguard.ui.transitions.PrimaryBouncerTransition.Companion.MAX_BACKGROUND_BLUR_RADIUS
+import com.android.systemui.keyguard.ui.transitions.PrimaryBouncerTransition.Companion.MIN_BACKGROUND_BLUR_RADIUS
import com.android.systemui.statusbar.SysuiStatusBarStateController
import dagger.Lazy
import javax.inject.Inject
@@ -48,16 +53,11 @@ constructor(
keyguardDismissActionInteractor: Lazy<KeyguardDismissActionInteractor>,
bouncerToGoneFlows: BouncerToGoneFlows,
animationFlow: KeyguardTransitionAnimationFlow,
-) {
+) : PrimaryBouncerTransition {
private val transitionAnimation =
animationFlow
- .setup(
- duration = TO_GONE_DURATION,
- edge = Edge.INVALID,
- )
- .setupWithoutSceneContainer(
- edge = Edge.create(from = PRIMARY_BOUNCER, to = GONE),
- )
+ .setup(duration = TO_GONE_DURATION, edge = Edge.INVALID)
+ .setupWithoutSceneContainer(edge = Edge.create(from = PRIMARY_BOUNCER, to = GONE))
private var leaveShadeOpen: Boolean = false
private var willRunDismissFromKeyguard: Boolean = false
@@ -96,7 +96,7 @@ constructor(
private fun createBouncerAlphaFlow(willRunAnimationOnKeyguard: () -> Boolean): Flow<Float> {
return transitionAnimation.sharedFlow(
- duration = 200.milliseconds,
+ duration = TO_GONE_SHORT_DURATION,
onStart = { willRunDismissFromKeyguard = willRunAnimationOnKeyguard() },
onStep = {
if (willRunDismissFromKeyguard) {
@@ -108,6 +108,22 @@ constructor(
)
}
+ private fun createBouncerWindowBlurFlow(
+ willRunAnimationOnKeyguard: () -> Boolean
+ ): Flow<Float> {
+ return transitionAnimation.sharedFlow(
+ duration = TO_GONE_SHORT_DURATION,
+ onStart = { willRunDismissFromKeyguard = willRunAnimationOnKeyguard() },
+ onStep = {
+ if (willRunDismissFromKeyguard) {
+ MIN_BACKGROUND_BLUR_RADIUS
+ } else {
+ MathUtils.lerp(MAX_BACKGROUND_BLUR_RADIUS, MIN_BACKGROUND_BLUR_RADIUS, it)
+ }
+ },
+ )
+ }
+
/** Lockscreen alpha */
val lockscreenAlpha: Flow<Float> =
if (ComposeBouncerFlags.isEnabled) {
@@ -137,6 +153,16 @@ constructor(
)
}
+ override val windowBlurRadius: Flow<Float> =
+ if (ComposeBouncerFlags.isEnabled) {
+ keyguardDismissActionInteractor
+ .get()
+ .willAnimateDismissActionOnLockscreen
+ .flatMapLatest { createBouncerWindowBlurFlow { it } }
+ } else {
+ createBouncerWindowBlurFlow(primaryBouncerInteractor::willRunDismissFromKeyguard)
+ }
+
val scrimAlpha: Flow<ScrimAlpha> =
bouncerToGoneFlows.scrimAlpha(TO_GONE_DURATION, PRIMARY_BOUNCER)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModel.kt
index d29f5129bd59..e737fcebe211 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModel.kt
@@ -25,6 +25,9 @@ import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER
import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
+import com.android.systemui.keyguard.ui.transitions.PrimaryBouncerTransition
+import com.android.systemui.keyguard.ui.transitions.PrimaryBouncerTransition.Companion.MAX_BACKGROUND_BLUR_RADIUS
+import com.android.systemui.keyguard.ui.transitions.PrimaryBouncerTransition.Companion.MIN_BACKGROUND_BLUR_RADIUS
import com.android.systemui.scene.shared.model.Scenes
import javax.inject.Inject
import kotlin.time.Duration.Companion.milliseconds
@@ -41,22 +44,21 @@ class PrimaryBouncerToLockscreenTransitionViewModel
@Inject
constructor(
animationFlow: KeyguardTransitionAnimationFlow,
-) : DeviceEntryIconTransition {
+ shadeDependentFlows: ShadeDependentFlows,
+) : DeviceEntryIconTransition, PrimaryBouncerTransition {
private val transitionAnimation =
animationFlow
.setup(
duration = FromPrimaryBouncerTransitionInteractor.TO_LOCKSCREEN_DURATION,
edge = Edge.create(from = Scenes.Bouncer, to = LOCKSCREEN),
)
- .setupWithoutSceneContainer(
- edge = Edge.create(from = PRIMARY_BOUNCER, to = LOCKSCREEN),
- )
+ .setupWithoutSceneContainer(edge = Edge.create(from = PRIMARY_BOUNCER, to = LOCKSCREEN))
val shortcutsAlpha: Flow<Float> =
transitionAnimation.sharedFlow(
duration = 250.milliseconds,
interpolator = EMPHASIZED_ACCELERATE,
- onStep = { it }
+ onStep = { it },
)
fun lockscreenAlpha(viewState: ViewStateAccessor): Flow<Float> {
@@ -72,4 +74,17 @@ constructor(
transitionAnimation.immediatelyTransitionTo(1f)
override val deviceEntryParentViewAlpha: Flow<Float> =
transitionAnimation.immediatelyTransitionTo(1f)
+
+ override val windowBlurRadius: Flow<Float> =
+ shadeDependentFlows.transitionFlow(
+ flowWhenShadeIsExpanded =
+ transitionAnimation.immediatelyTransitionTo(MAX_BACKGROUND_BLUR_RADIUS),
+ flowWhenShadeIsNotExpanded =
+ transitionAnimation.sharedFlow(
+ duration = FromPrimaryBouncerTransitionInteractor.TO_LOCKSCREEN_DURATION,
+ onStep = {
+ MathUtils.lerp(MAX_BACKGROUND_BLUR_RADIUS, MIN_BACKGROUND_BLUR_RADIUS, it)
+ },
+ ),
+ )
}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlag.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlag.kt
index 6097ef53f8df..33bffc2e35ab 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlag.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlag.kt
@@ -22,7 +22,6 @@ import com.android.systemui.Flags.FLAG_SCENE_CONTAINER
import com.android.systemui.Flags.sceneContainer
import com.android.systemui.flags.FlagToken
import com.android.systemui.flags.RefactorFlagUtils
-import com.android.systemui.keyguard.KeyguardBottomAreaRefactor
import com.android.systemui.keyguard.KeyguardWmStateRefactor
import com.android.systemui.keyguard.MigrateClocksToBlueprint
import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun
@@ -37,7 +36,6 @@ object SceneContainerFlag {
inline val isEnabled
get() =
sceneContainer() && // mainAconfigFlag
- KeyguardBottomAreaRefactor.isEnabled &&
KeyguardWmStateRefactor.isEnabled &&
MigrateClocksToBlueprint.isEnabled &&
NotificationThrottleHun.isEnabled &&
@@ -51,7 +49,6 @@ object SceneContainerFlag {
/** The set of secondary flags which must be enabled for scene container to work properly */
inline fun getSecondaryFlags(): Sequence<FlagToken> =
sequenceOf(
- KeyguardBottomAreaRefactor.token,
KeyguardWmStateRefactor.token,
MigrateClocksToBlueprint.token,
NotificationThrottleHun.token,
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/DebugDrawable.java b/packages/SystemUI/src/com/android/systemui/shade/DebugDrawable.java
index b24edd9beece..d78f4d8238b1 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/DebugDrawable.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/DebugDrawable.java
@@ -24,7 +24,6 @@ import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.drawable.Drawable;
-import com.android.keyguard.LockIconViewController;
import com.android.systemui.scene.shared.flag.SceneContainerFlag;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
@@ -39,7 +38,6 @@ public class DebugDrawable extends Drawable {
private final NotificationPanelViewController mNotificationPanelViewController;
private final NotificationPanelView mView;
private final NotificationStackScrollLayoutController mNotificationStackScrollLayoutController;
- private final LockIconViewController mLockIconViewController;
private final QuickSettingsController mQsController;
private final Set<Integer> mDebugTextUsedYPositions;
private final Paint mDebugPaint;
@@ -48,13 +46,11 @@ public class DebugDrawable extends Drawable {
NotificationPanelViewController notificationPanelViewController,
NotificationPanelView notificationPanelView,
NotificationStackScrollLayoutController notificationStackScrollLayoutController,
- LockIconViewController lockIconViewController,
QuickSettingsController quickSettingsController
) {
mNotificationPanelViewController = notificationPanelViewController;
mView = notificationPanelView;
mNotificationStackScrollLayoutController = notificationStackScrollLayoutController;
- mLockIconViewController = lockIconViewController;
mQsController = quickSettingsController;
mDebugTextUsedYPositions = new HashSet<>();
mDebugPaint = new Paint();
@@ -91,8 +87,6 @@ public class DebugDrawable extends Drawable {
}
drawDebugInfo(canvas, mNotificationPanelViewController.getClockPositionResult().clockY,
Color.GRAY, "mClockPositionResult.clockY");
- drawDebugInfo(canvas, (int) mLockIconViewController.getTop(), Color.GRAY,
- "mLockIconViewController.getTop()");
if (mNotificationPanelViewController.isKeyguardShowing()) {
// Notifications have the space between those two lines.
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index d347084f435d..fe5380423ebd 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -25,7 +25,6 @@ import static com.android.keyguard.KeyguardClockSwitch.LARGE;
import static com.android.keyguard.KeyguardClockSwitch.SMALL;
import static com.android.systemui.Flags.msdlFeedback;
import static com.android.systemui.Flags.predictiveBackAnimateShade;
-import static com.android.systemui.Flags.smartspaceRelocateToBottom;
import static com.android.systemui.classifier.Classifier.BOUNCER_UNLOCK;
import static com.android.systemui.classifier.Classifier.GENERIC;
import static com.android.systemui.classifier.Classifier.QUICK_SETTINGS;
@@ -105,7 +104,6 @@ import com.android.keyguard.KeyguardStatusView;
import com.android.keyguard.KeyguardStatusViewController;
import com.android.keyguard.KeyguardUnfoldTransition;
import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.keyguard.LockIconViewController;
import com.android.keyguard.dagger.KeyguardQsUserSwitchComponent;
import com.android.keyguard.dagger.KeyguardStatusBarViewComponent;
import com.android.keyguard.dagger.KeyguardStatusViewComponent;
@@ -128,11 +126,9 @@ import com.android.systemui.dump.DumpsysTableLogger;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
import com.android.systemui.fragments.FragmentService;
-import com.android.systemui.keyguard.KeyguardBottomAreaRefactor;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
import com.android.systemui.keyguard.KeyguardViewConfigurator;
import com.android.systemui.keyguard.MigrateClocksToBlueprint;
-import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
@@ -143,7 +139,6 @@ import com.android.systemui.keyguard.shared.model.TransitionStep;
import com.android.systemui.keyguard.ui.binder.KeyguardLongPressViewBinder;
import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel;
import com.android.systemui.keyguard.ui.viewmodel.GoneToDreamingTransitionViewModel;
-import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel;
import com.android.systemui.keyguard.ui.viewmodel.KeyguardTouchHandlingViewModel;
import com.android.systemui.keyguard.ui.viewmodel.LockscreenToDreamingTransitionViewModel;
import com.android.systemui.keyguard.ui.viewmodel.LockscreenToOccludedTransitionViewModel;
@@ -211,8 +206,6 @@ import com.android.systemui.statusbar.phone.BounceInterpolator;
import com.android.systemui.statusbar.phone.CentralSurfaces;
import com.android.systemui.statusbar.phone.DozeParameters;
import com.android.systemui.statusbar.phone.HeadsUpAppearanceController;
-import com.android.systemui.statusbar.phone.KeyguardBottomAreaView;
-import com.android.systemui.statusbar.phone.KeyguardBottomAreaViewController;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.phone.KeyguardClockPositionAlgorithm;
import com.android.systemui.statusbar.phone.KeyguardStatusBarViewController;
@@ -374,8 +367,6 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
private float mExpandedHeight = 0;
/** The current squish amount for the predictive back animation */
private float mCurrentBackProgress = 0.0f;
- @Deprecated
- private KeyguardBottomAreaView mKeyguardBottomArea;
private boolean mExpanding;
private boolean mSplitShadeEnabled;
/** The bottom padding reserved for elements of the keyguard measuring notifications. */
@@ -391,11 +382,8 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
private KeyguardUserSwitcherController mKeyguardUserSwitcherController;
private KeyguardStatusBarViewController mKeyguardStatusBarViewController;
private KeyguardStatusViewController mKeyguardStatusViewController;
- private final LockIconViewController mLockIconViewController;
private NotificationsQuickSettingsContainer mNotificationContainerParent;
private final NotificationsQSContainerController mNotificationsQSContainerController;
- private final Provider<KeyguardBottomAreaViewController>
- mKeyguardBottomAreaViewControllerProvider;
private boolean mAnimateNextPositionUpdate;
private final ScreenOffAnimationController mScreenOffAnimationController;
private final UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController;
@@ -547,8 +535,6 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
private final NotificationListContainer mNotificationListContainer;
private final NotificationStackSizeCalculator mNotificationStackSizeCalculator;
private final NPVCDownEventState.Buffer mLastDownEvents;
- private final KeyguardBottomAreaViewModel mKeyguardBottomAreaViewModel;
- private final KeyguardBottomAreaInteractor mKeyguardBottomAreaInteractor;
private final KeyguardClockInteractor mKeyguardClockInteractor;
private float mMinExpandHeight;
private boolean mPanelUpdateWhenAnimatorEnds;
@@ -624,8 +610,6 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
private final SplitShadeStateController mSplitShadeStateController;
private final Runnable mFlingCollapseRunnable = () -> fling(0, false /* expand */,
mNextCollapseSpeedUpFactor, false /* expandBecauseOfFalsing */);
- private final Runnable mAnimateKeyguardBottomAreaInvisibleEndRunnable =
- () -> mKeyguardBottomArea.setVisibility(View.GONE);
private final Runnable mHeadsUpExistenceChangedRunnable = () -> {
setHeadsUpAnimatingAway(false);
updateExpansionAndVisibility();
@@ -714,7 +698,6 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
MediaDataManager mediaDataManager,
NotificationShadeDepthController notificationShadeDepthController,
AmbientState ambientState,
- LockIconViewController lockIconViewController,
KeyguardMediaController keyguardMediaController,
TapAgainViewController tapAgainViewController,
NavigationModeController navigationModeController,
@@ -730,15 +713,12 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
ShadeRepository shadeRepository,
Optional<SysUIUnfoldComponent> unfoldComponent,
SysUiState sysUiState,
- Provider<KeyguardBottomAreaViewController> keyguardBottomAreaViewControllerProvider,
KeyguardUnlockAnimationController keyguardUnlockAnimationController,
KeyguardIndicationController keyguardIndicationController,
NotificationListContainer notificationListContainer,
NotificationStackSizeCalculator notificationStackSizeCalculator,
UnlockedScreenOffAnimationController unlockedScreenOffAnimationController,
SystemClock systemClock,
- KeyguardBottomAreaViewModel keyguardBottomAreaViewModel,
- KeyguardBottomAreaInteractor keyguardBottomAreaInteractor,
KeyguardClockInteractor keyguardClockInteractor,
AlternateBouncerInteractor alternateBouncerInteractor,
DreamingToLockscreenTransitionViewModel dreamingToLockscreenTransitionViewModel,
@@ -852,7 +832,6 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
mNotificationListContainer = notificationListContainer;
mNotificationStackSizeCalculator = notificationStackSizeCalculator;
mNavigationBarController = navigationBarController;
- mKeyguardBottomAreaViewControllerProvider = keyguardBottomAreaViewControllerProvider;
mNotificationsQSContainerController.init();
mNotificationStackScrollLayoutController = notificationStackScrollLayoutController;
mKeyguardStatusViewComponentFactory = keyguardStatusViewComponentFactory;
@@ -908,7 +887,6 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
mBottomAreaShadeAlphaAnimator.setInterpolator(Interpolators.ALPHA_OUT);
mConversationNotificationManager = conversationNotificationManager;
mAuthController = authController;
- mLockIconViewController = lockIconViewController;
mScreenOffAnimationController = screenOffAnimationController;
mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController;
mLastDownEvents = new NPVCDownEventState.Buffer(MAX_DOWN_EVENT_BUFFER_SIZE);
@@ -930,16 +908,13 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
if (DEBUG_DRAWABLE) {
mView.getOverlay().add(new DebugDrawable(this, mView,
- mNotificationStackScrollLayoutController, mLockIconViewController,
- mQsController));
+ mNotificationStackScrollLayoutController, mQsController));
}
mKeyguardUnfoldTransition = unfoldComponent.map(
SysUIUnfoldComponent::getKeyguardUnfoldTransition);
updateUserSwitcherFlags();
- mKeyguardBottomAreaViewModel = keyguardBottomAreaViewModel;
- mKeyguardBottomAreaInteractor = keyguardBottomAreaInteractor;
mKeyguardClockInteractor = keyguardClockInteractor;
KeyguardLongPressViewBinder.bind(
mView.requireViewById(R.id.keyguard_long_press),
@@ -1062,12 +1037,6 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
mQsController.init();
mShadeHeadsUpTracker.addTrackingHeadsUpListener(
mNotificationStackScrollLayoutController::setTrackingHeadsUp);
- if (!KeyguardBottomAreaRefactor.isEnabled()) {
- setKeyguardBottomArea(mView.findViewById(R.id.keyguard_bottom_area));
- }
-
- initBottomArea();
-
mWakeUpCoordinator.setStackScroller(mNotificationStackScrollLayoutController);
mWakeUpCoordinator.addListener(new NotificationWakeUpCoordinator.WakeUpListener() {
@Override
@@ -1421,23 +1390,6 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
showKeyguardUserSwitcher /* enabled */);
updateViewControllers(userAvatarView, keyguardUserSwitcherView);
-
- if (!KeyguardBottomAreaRefactor.isEnabled()) {
- // Update keyguard bottom area
- int index = mView.indexOfChild(mKeyguardBottomArea);
- mView.removeView(mKeyguardBottomArea);
- KeyguardBottomAreaView oldBottomArea = mKeyguardBottomArea;
- KeyguardBottomAreaViewController keyguardBottomAreaViewController =
- mKeyguardBottomAreaViewControllerProvider.get();
- if (smartspaceRelocateToBottom()) {
- keyguardBottomAreaViewController.init();
- }
- setKeyguardBottomArea(keyguardBottomAreaViewController.getView());
- mKeyguardBottomArea.initFrom(oldBottomArea);
- mView.addView(mKeyguardBottomArea, index);
-
- initBottomArea();
- }
mStatusBarStateListener.onDozeAmountChanged(mStatusBarStateController.getDozeAmount(),
mStatusBarStateController.getInterpolatedDozeAmount());
@@ -1462,10 +1414,6 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
false,
mBarState);
}
-
- if (!KeyguardBottomAreaRefactor.isEnabled()) {
- setKeyguardBottomAreaVisibility(mBarState, false);
- }
}
private void attachSplitShadeMediaPlayerContainer(FrameLayout container) {
@@ -1475,22 +1423,6 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
mKeyguardMediaController.attachSplitShadeContainer(container);
}
- private void initBottomArea() {
- if (!KeyguardBottomAreaRefactor.isEnabled()) {
- mKeyguardBottomArea.init(
- mKeyguardBottomAreaViewModel,
- mFalsingManager,
- mLockIconViewController,
- stringResourceId ->
- mKeyguardIndicationController.showTransientIndication(stringResourceId),
- mVibratorHelper,
- mActivityStarter);
-
- // Rebind (for now), as a new bottom area and indication area may have been created
- mKeyguardViewConfigurator.bindIndicationArea();
- }
- }
-
@VisibleForTesting
void setMaxDisplayedNotifications(int maxAllowed) {
mMaxAllowedKeyguardNotifications = maxAllowed;
@@ -1528,11 +1460,6 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
return mUnlockedScreenOffAnimationController.isAnimationPlaying();
}
- @Deprecated
- private void setKeyguardBottomArea(KeyguardBottomAreaView keyguardBottomArea) {
- mKeyguardBottomArea = keyguardBottomArea;
- }
-
/** Sets a listener to be notified when the shade starts opening or finishes closing. */
public void setOpenCloseListener(OpenCloseListener openCloseListener) {
SceneContainerFlag.assertInLegacyMode();
@@ -1660,10 +1587,6 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
mKeyguardStatusViewController.setLockscreenClockY(
mClockPositionAlgorithm.getExpandedPreferredClockY());
}
- if (!(MigrateClocksToBlueprint.isEnabled() || KeyguardBottomAreaRefactor.isEnabled())) {
- mKeyguardBottomAreaInteractor.setClockPosition(
- mClockPositionResult.clockX, mClockPositionResult.clockY);
- }
boolean animate = !SceneContainerFlag.isEnabled()
&& mNotificationStackScrollLayoutController.isAddOrRemoveAnimationPending();
@@ -2382,25 +2305,6 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
}
}
- @Deprecated
- private void setKeyguardBottomAreaVisibility(int statusBarState, boolean goingToFullShade) {
- mKeyguardBottomArea.animate().cancel();
- if (goingToFullShade) {
- mKeyguardBottomArea.animate().alpha(0f).setStartDelay(
- mKeyguardStateController.getKeyguardFadingAwayDelay()).setDuration(
- mKeyguardStateController.getShortenedFadingAwayDuration()).setInterpolator(
- Interpolators.ALPHA_OUT).withEndAction(
- mAnimateKeyguardBottomAreaInvisibleEndRunnable).start();
- } else if (statusBarState == KEYGUARD || statusBarState == StatusBarState.SHADE_LOCKED) {
- mKeyguardBottomArea.setVisibility(View.VISIBLE);
- if (!mIsOcclusionTransitionRunning) {
- mKeyguardBottomArea.setAlpha(1f);
- }
- } else {
- mKeyguardBottomArea.setVisibility(View.GONE);
- }
- }
-
/**
* When the back gesture triggers a fully-expanded shade --> QQS shade collapse transition,
* the expansionFraction goes down from 1.0 --> 0.0 (collapsing), so the current "squish" amount
@@ -2755,12 +2659,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
float alpha = Math.min(expansionAlpha, 1 - mQsController.computeExpansionFraction());
alpha *= mBottomAreaShadeAlpha;
- if (KeyguardBottomAreaRefactor.isEnabled()) {
- mKeyguardInteractor.setAlpha(alpha);
- } else {
- mKeyguardBottomAreaInteractor.setAlpha(alpha);
- }
- mLockIconViewController.setAlpha(alpha);
+ mKeyguardInteractor.setAlpha(alpha);
}
private void onExpandingFinished() {
@@ -2967,11 +2866,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
}
private void updateDozingVisibilities(boolean animate) {
- if (KeyguardBottomAreaRefactor.isEnabled()) {
- mKeyguardInteractor.setAnimateDozingTransitions(animate);
- } else {
- mKeyguardBottomAreaInteractor.setAnimateDozingTransitions(animate);
- }
+ mKeyguardInteractor.setAnimateDozingTransitions(animate);
if (!mDozing && animate) {
mKeyguardStatusBarViewController.animateKeyguardStatusBarIn();
}
@@ -3212,11 +3107,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
mDozing = dozing;
// TODO (b/) make listeners for this
mNotificationStackScrollLayoutController.setDozing(mDozing, animate);
- if (KeyguardBottomAreaRefactor.isEnabled()) {
- mKeyguardInteractor.setAnimateDozingTransitions(animate);
- } else {
- mKeyguardBottomAreaInteractor.setAnimateDozingTransitions(animate);
- }
+ mKeyguardInteractor.setAnimateDozingTransitions(animate);
mKeyguardStatusBarViewController.setDozing(mDozing);
mQsController.setDozing(mDozing);
@@ -3267,7 +3158,6 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
}
public void dozeTimeTick() {
- mLockIconViewController.dozeTimeTick();
if (!MigrateClocksToBlueprint.isEnabled()) {
mKeyguardStatusViewController.dozeTimeTick();
}
@@ -4544,10 +4434,6 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
mBarState);
}
- if (!KeyguardBottomAreaRefactor.isEnabled()) {
- setKeyguardBottomAreaVisibility(statusBarState, goingToFullShade);
- }
-
// TODO: maybe add a listener for barstate
mBarState = statusBarState;
mQsController.setBarState(statusBarState);
@@ -4813,12 +4699,8 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
stackScroller.setMaxAlphaForKeyguard(alpha, "NPVC.setTransitionAlpha()");
}
- if (KeyguardBottomAreaRefactor.isEnabled()) {
- mKeyguardInteractor.setAlpha(alpha);
- } else {
- mKeyguardBottomAreaInteractor.setAlpha(alpha);
- }
- mLockIconViewController.setAlpha(alpha);
+ mKeyguardInteractor.setAlpha(alpha);
+ //todo was this needed?
if (mKeyguardQsUserSwitchController != null) {
mKeyguardQsUserSwitchController.setAlpha(alpha);
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java
index bf672be3c8d0..48bbb0407ee3 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.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.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..45161331c0d9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeTraceLogger.kt
@@ -0,0 +1,61 @@
+/*
+ * 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"
+
+ @JvmStatic
+ fun logOnMovedToDisplay(displayId: Int, config: Configuration) {
+ if (!Trace.isEnabled()) return
+ Trace.instantForTrack(
+ Trace.TRACE_TAG_APP,
+ TAG,
+ "onMovedToDisplay(displayId=$displayId, dpi=" + config.densityDpi + ")",
+ )
+ }
+
+ @JvmStatic
+ 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/ShadeViewProviderModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt
index 8937ce33cd38..e19112047d2a 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt
@@ -50,7 +50,6 @@ import com.android.systemui.statusbar.NotificationInsetsController
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout
import com.android.systemui.statusbar.notification.stack.ui.view.NotificationScrollView
import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer
-import com.android.systemui.statusbar.phone.KeyguardBottomAreaView
import com.android.systemui.statusbar.phone.StatusBarLocation
import com.android.systemui.statusbar.phone.StatusIconContainer
import com.android.systemui.statusbar.phone.TapAgainView
@@ -145,20 +144,6 @@ abstract class ShadeViewProviderModule {
return notificationShadeWindowView.requireViewById(R.id.notification_panel)
}
- /**
- * Constructs a new, unattached [KeyguardBottomAreaView].
- *
- * Note that this is explicitly _not_ a singleton, as we want to be able to reinflate it
- */
- @Provides
- fun providesKeyguardBottomAreaView(
- npv: NotificationPanelView,
- @ShadeDisplayAware layoutInflater: LayoutInflater,
- ): KeyguardBottomAreaView {
- return layoutInflater.inflate(R.layout.keyguard_bottom_area, npv, false)
- as KeyguardBottomAreaView
- }
-
@Provides
@SysUISingleton
fun providesLightRevealScrim(
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/shade/domain/interactor/ShadeLockscreenInteractorImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeLockscreenInteractorImpl.kt
index 50b5607f1955..2d7476c0433c 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeLockscreenInteractorImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeLockscreenInteractorImpl.kt
@@ -16,7 +16,6 @@
package com.android.systemui.shade.domain.interactor
-import com.android.keyguard.LockIconViewController
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.keyguard.shared.model.KeyguardState
@@ -38,7 +37,6 @@ constructor(
@Background private val backgroundScope: CoroutineScope,
private val shadeInteractor: ShadeInteractor,
private val sceneInteractor: SceneInteractor,
- private val lockIconViewController: LockIconViewController,
shadeRepository: ShadeRepository,
) : ShadeLockscreenInteractor {
@@ -61,7 +59,7 @@ constructor(
}
override fun dozeTimeTick() {
- lockIconViewController.dozeTimeTick()
+ // TODO("b/383591086") Implement replacement or delete
}
@Deprecated("Not supported by scenes")
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/chips/notification/domain/interactor/SingleNotificationChipInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/interactor/SingleNotificationChipInteractor.kt
index bbecde830a9f..dff6f567f6c6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/interactor/SingleNotificationChipInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/interactor/SingleNotificationChipInteractor.kt
@@ -137,7 +137,7 @@ constructor(
}
}
- return NotificationChipModel(key, statusBarChipIconView, whenTime, promotedContent)
+ return NotificationChipModel(key, statusBarChipIconView, promotedContent)
}
@AssistedFactory
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/model/NotificationChipModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/model/NotificationChipModel.kt
index 9f0638baec83..c6759da304bb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/model/NotificationChipModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/model/NotificationChipModel.kt
@@ -23,7 +23,5 @@ import com.android.systemui.statusbar.notification.promoted.shared.model.Promote
data class NotificationChipModel(
val key: String,
val statusBarChipIconView: StatusBarIconView?,
- // TODO(b/364653005): Use [PromotedNotificationContentModel.time] instead of a custom field.
- val whenTime: Long,
val promotedContent: PromotedNotificationContentModel,
)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt
index 2d16f3b51ed1..66af275bc702 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt
@@ -27,6 +27,7 @@ import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationInteractor
import com.android.systemui.statusbar.notification.headsup.PinnedStatus
+import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
@@ -82,21 +83,51 @@ constructor(
)
}
}
- return if (headsUpState == PinnedStatus.PinnedByUser) {
+
+ if (headsUpState == PinnedStatus.PinnedByUser) {
// If the user tapped the chip to show the HUN, we want to just show the icon because
// the HUN will show the rest of the information.
- OngoingActivityChipModel.Shown.IconOnly(icon, colors, onClickListener)
- } else {
- OngoingActivityChipModel.Shown.ShortTimeDelta(
+ return OngoingActivityChipModel.Shown.IconOnly(icon, colors, onClickListener)
+ }
+
+ if (this.promotedContent.shortCriticalText != null) {
+ return OngoingActivityChipModel.Shown.Text(
icon,
colors,
- time = this.whenTime,
+ this.promotedContent.shortCriticalText,
onClickListener,
)
}
- // TODO(b/364653005): Use Notification.showWhen to determine if we should show the time.
- // TODO(b/364653005): If Notification.shortCriticalText is set, use that instead of `when`.
- // TODO(b/364653005): If the app that posted the notification is in the foreground, don't
- // show that app's chip.
+
+ if (this.promotedContent.time == null) {
+ return OngoingActivityChipModel.Shown.IconOnly(icon, colors, onClickListener)
+ }
+ when (this.promotedContent.time.mode) {
+ PromotedNotificationContentModel.When.Mode.BasicTime -> {
+ return OngoingActivityChipModel.Shown.ShortTimeDelta(
+ icon,
+ colors,
+ time = this.promotedContent.time.time,
+ onClickListener,
+ )
+ }
+ PromotedNotificationContentModel.When.Mode.CountUp -> {
+ return OngoingActivityChipModel.Shown.Timer(
+ icon,
+ colors,
+ startTimeMs = this.promotedContent.time.time,
+ onClickListener,
+ )
+ }
+ PromotedNotificationContentModel.When.Mode.CountDown -> {
+ // TODO(b/364653005): Support CountDown.
+ return OngoingActivityChipModel.Shown.Timer(
+ icon,
+ colors,
+ startTimeMs = this.promotedContent.time.time,
+ onClickListener,
+ )
+ }
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/binder/OngoingActivityChipBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/binder/OngoingActivityChipBinder.kt
index cf69d401df60..0c4c1a71ccc7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/binder/OngoingActivityChipBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/binder/OngoingActivityChipBinder.kt
@@ -310,9 +310,13 @@ object OngoingActivityChipBinder {
private fun View.setBackgroundPaddingForEmbeddedPaddingIcon() {
val sidePadding =
- context.resources.getDimensionPixelSize(
- R.dimen.ongoing_activity_chip_side_padding_for_embedded_padding_icon
- )
+ if (StatusBarNotifChips.isEnabled) {
+ 0
+ } else {
+ context.resources.getDimensionPixelSize(
+ R.dimen.ongoing_activity_chip_side_padding_for_embedded_padding_icon
+ )
+ }
setPaddingRelative(sidePadding, paddingTop, sidePadding, paddingBottom)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt
index 2dce4e38b803..18217d786cd4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt
@@ -116,7 +116,8 @@ sealed class OngoingActivityChipModel {
override val colors: ColorsModel,
// TODO(b/361346412): Enforce a max length requirement?
val text: String,
- ) : Shown(icon, colors, onClickListener = null) {
+ override val onClickListener: View.OnClickListener? = null,
+ ) : Shown(icon, colors, onClickListener) {
override val logName = "Shown.Text"
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractor.kt
index e122ca888f45..863c665eb4f5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractor.kt
@@ -71,6 +71,7 @@ constructor(
contentBuilder.appName = notification.loadHeaderAppName(context)
contentBuilder.subText = notification.subText()
contentBuilder.time = notification.extractWhen()
+ contentBuilder.shortCriticalText = notification.shortCriticalText()
contentBuilder.lastAudiblyAlertedMs = entry.lastAudiblyAlertedMs
contentBuilder.profileBadgeResId = null // TODO
contentBuilder.title = notification.title()
@@ -97,6 +98,13 @@ private fun Notification.text(): CharSequence? = extras?.getCharSequence(EXTRA_T
private fun Notification.subText(): String? = extras?.getString(EXTRA_SUB_TEXT)
+private fun Notification.shortCriticalText(): String? {
+ if (!android.app.Flags.apiRichOngoing()) {
+ return null
+ }
+ return this.shortCriticalText
+}
+
private fun Notification.chronometerCountDown(): Boolean =
extras?.getBoolean(EXTRA_CHRONOMETER_COUNT_DOWN, /* defaultValue= */ false) ?: false
@@ -107,7 +115,7 @@ private fun Notification.extractWhen(): When? {
val countDown = chronometerCountDown()
return when {
- showsTime -> When(time, When.Mode.Absolute)
+ showsTime -> When(time, When.Mode.BasicTime)
showsChronometer -> When(time, if (countDown) When.Mode.CountDown else When.Mode.CountUp)
else -> null
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/shared/model/PromotedNotificationContentModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/shared/model/PromotedNotificationContentModel.kt
index 0af40437828e..fe2dabe1ba8a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/shared/model/PromotedNotificationContentModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/shared/model/PromotedNotificationContentModel.kt
@@ -34,6 +34,11 @@ data class PromotedNotificationContentModel(
val skeletonSmallIcon: Icon?, // TODO(b/377568176): Make into an IconModel.
val appName: CharSequence?,
val subText: CharSequence?,
+ val shortCriticalText: String?,
+ /**
+ * The timestamp associated with the notification. Null if the timestamp should not be
+ * displayed.
+ */
val time: When?,
val lastAudiblyAlertedMs: Long,
@DrawableRes val profileBadgeResId: Int?,
@@ -57,6 +62,7 @@ data class PromotedNotificationContentModel(
var appName: CharSequence? = null
var subText: CharSequence? = null
var time: When? = null
+ var shortCriticalText: String? = null
var lastAudiblyAlertedMs: Long = 0L
@DrawableRes var profileBadgeResId: Int? = null
var title: CharSequence? = null
@@ -80,6 +86,7 @@ data class PromotedNotificationContentModel(
skeletonSmallIcon = skeletonSmallIcon,
appName = appName,
subText = subText,
+ shortCriticalText = shortCriticalText,
time = time,
lastAudiblyAlertedMs = lastAudiblyAlertedMs,
profileBadgeResId = profileBadgeResId,
@@ -100,8 +107,11 @@ data class PromotedNotificationContentModel(
data class When(val time: Long, val mode: Mode) {
/** The mode used to display a notification's `when` value. */
enum class Mode {
- Absolute,
+ /** No custom mode requested by the notification. */
+ BasicTime,
+ /** Show the notification's time as a chronometer that counts down to [time]. */
CountDown,
+ /** Show the notification's time as a chronometer that counts up from [time]. */
CountUp,
}
}
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/phone/KeyguardBottomAreaView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.kt
index eadb7f5a1684..2368824311f8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.kt
@@ -22,14 +22,7 @@ import android.view.View
import android.view.ViewGroup
import android.widget.FrameLayout
import androidx.annotation.StringRes
-import com.android.keyguard.LockIconViewController
-import com.android.systemui.keyguard.ui.binder.KeyguardBottomAreaViewBinder
-import com.android.systemui.keyguard.ui.binder.KeyguardBottomAreaViewBinder.bind
-import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel
-import com.android.systemui.plugins.ActivityStarter
-import com.android.systemui.plugins.FalsingManager
import com.android.systemui.res.R
-import com.android.systemui.statusbar.VibratorHelper
/**
* Renders the bottom area of the lock-screen. Concerned primarily with the quick affordance UI
@@ -51,124 +44,7 @@ constructor(
defStyleAttr,
defStyleRes,
) {
-
- @Deprecated("Deprecated as part of b/278057014")
- interface MessageDisplayer {
- fun display(@StringRes stringResourceId: Int)
- }
-
- private var ambientIndicationArea: View? = null
- private var keyguardIndicationArea: View? = null
- private var binding: KeyguardBottomAreaViewBinder.Binding? = null
- private var lockIconViewController: LockIconViewController? = null
- private var isLockscreenLandscapeEnabled: Boolean = false
-
- /** Initializes the view. */
- @Deprecated("Deprecated as part of b/278057014")
- fun init(
- viewModel: KeyguardBottomAreaViewModel,
- falsingManager: FalsingManager? = null,
- lockIconViewController: LockIconViewController? = null,
- messageDisplayer: MessageDisplayer? = null,
- vibratorHelper: VibratorHelper? = null,
- activityStarter: ActivityStarter? = null,
- ) {
- binding?.destroy()
- binding =
- bind(
- this,
- viewModel,
- falsingManager,
- vibratorHelper,
- activityStarter,
- ) {
- messageDisplayer?.display(it)
- }
- this.lockIconViewController = lockIconViewController
- }
-
- /**
- * Initializes this instance of [KeyguardBottomAreaView] based on the given instance of another
- * [KeyguardBottomAreaView]
- */
- @Deprecated("Deprecated as part of b/278057014")
- fun initFrom(oldBottomArea: KeyguardBottomAreaView) {
- // if it exists, continue to use the original ambient indication container
- // instead of the newly inflated one
- ambientIndicationArea?.let { nonNullAmbientIndicationArea ->
- // remove old ambient indication from its parent
- val originalAmbientIndicationView =
- oldBottomArea.requireViewById<View>(R.id.ambient_indication_container)
- (originalAmbientIndicationView.parent as ViewGroup).removeView(
- originalAmbientIndicationView
- )
-
- // remove current ambient indication from its parent (discard)
- val ambientIndicationParent = nonNullAmbientIndicationArea.parent as ViewGroup
- val ambientIndicationIndex =
- ambientIndicationParent.indexOfChild(nonNullAmbientIndicationArea)
- ambientIndicationParent.removeView(nonNullAmbientIndicationArea)
-
- // add the old ambient indication to this view
- ambientIndicationParent.addView(originalAmbientIndicationView, ambientIndicationIndex)
- ambientIndicationArea = originalAmbientIndicationView
- }
- }
-
- fun setIsLockscreenLandscapeEnabled(isLockscreenLandscapeEnabled: Boolean) {
- this.isLockscreenLandscapeEnabled = isLockscreenLandscapeEnabled
- }
-
- override fun onFinishInflate() {
- super.onFinishInflate()
- ambientIndicationArea = findViewById(R.id.ambient_indication_container)
- keyguardIndicationArea = findViewById(R.id.keyguard_indication_area)
- }
-
- override fun onConfigurationChanged(newConfig: Configuration) {
- super.onConfigurationChanged(newConfig)
- binding?.onConfigurationChanged()
-
- if (isLockscreenLandscapeEnabled) {
- updateIndicationAreaBottomMargin()
- }
- }
-
- private fun updateIndicationAreaBottomMargin() {
- keyguardIndicationArea?.let {
- val params = it.layoutParams as FrameLayout.LayoutParams
- params.bottomMargin =
- resources.getDimensionPixelSize(R.dimen.keyguard_indication_margin_bottom)
- it.layoutParams = params
- }
- }
-
override fun hasOverlappingRendering(): Boolean {
return false
}
-
- override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
- super.onLayout(changed, left, top, right, bottom)
- findViewById<View>(R.id.ambient_indication_container)?.let {
- val (ambientLeft, ambientTop) = it.locationOnScreen
- if (binding?.shouldConstrainToTopOfLockIcon() == true) {
- // make top of ambient indication view the bottom of the lock icon
- it.layout(
- ambientLeft,
- lockIconViewController?.getBottom()?.toInt() ?: 0,
- right - ambientLeft,
- ambientTop + it.measuredHeight
- )
- } else {
- // make bottom of ambient indication view the top of the lock icon
- val lockLocationTop = lockIconViewController?.getTop() ?: 0
- it.layout(
- ambientLeft,
- lockLocationTop.toInt() - it.measuredHeight,
- right - ambientLeft,
- lockLocationTop.toInt()
- )
- }
- }
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaViewController.kt
deleted file mode 100644
index 4aece3d5cd6a..000000000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaViewController.kt
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.phone
-
-import com.android.systemui.flags.FeatureFlagsClassic
-import com.android.systemui.flags.Flags
-import com.android.systemui.Flags.smartspaceRelocateToBottom
-import android.view.View
-import android.view.ViewGroup
-import android.widget.LinearLayout
-import com.android.systemui.res.R
-import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController
-import com.android.systemui.util.ViewController
-import javax.inject.Inject
-
-class KeyguardBottomAreaViewController
- @Inject constructor(
- view: KeyguardBottomAreaView,
- private val smartspaceController: LockscreenSmartspaceController,
- featureFlags: FeatureFlagsClassic
-) : ViewController<KeyguardBottomAreaView> (view) {
-
- private var smartspaceView: View? = null
-
- init {
- view.setIsLockscreenLandscapeEnabled(
- featureFlags.isEnabled(Flags.LOCKSCREEN_ENABLE_LANDSCAPE))
- }
-
- override fun onViewAttached() {
- if (!smartspaceRelocateToBottom() || !smartspaceController.isEnabled) {
- return
- }
-
- val ambientIndicationArea = mView.findViewById<View>(R.id.ambient_indication_container)
- ambientIndicationArea?.visibility = View.GONE
-
- addSmartspaceView()
- }
-
- override fun onViewDetached() {
- }
-
- fun getView(): KeyguardBottomAreaView {
- // TODO: remove this method.
- return mView
- }
-
- private fun addSmartspaceView() {
- if (!smartspaceRelocateToBottom()) {
- return
- }
-
- val smartspaceContainer = mView.findViewById<View>(R.id.smartspace_container)
- smartspaceContainer!!.visibility = View.VISIBLE
-
- smartspaceView = smartspaceController.buildAndConnectView(smartspaceContainer as ViewGroup)
- val lp = LinearLayout.LayoutParams(
- ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
- (smartspaceContainer as ViewGroup).addView(smartspaceView, 0, lp)
- val startPadding = context.resources.getDimensionPixelSize(
- R.dimen.below_clock_padding_start)
- val endPadding = context.resources.getDimensionPixelSize(
- R.dimen.below_clock_padding_end)
- smartspaceView?.setPaddingRelative(startPadding, 0, endPadding, 0)
-// mKeyguardUnlockAnimationController.lockscreenSmartspace = smartspaceView
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImpl.kt
index a36ef56e57a8..f11ebc00e242 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImpl.kt
@@ -220,7 +220,7 @@ constructor(
if (satelliteManager != null) {
// Outer scope launch allows us to delay until MIN_UPTIME
- scope.launch {
+ scope.launch(context = bgDispatcher) {
// First, check that satellite is supported on this device
satelliteSupport.value = checkSatelliteSupportAfterMinUptime(satelliteManager)
logBuffer.i(
@@ -229,7 +229,9 @@ constructor(
)
// Second, register a listener to let us know if there are changes to support
- scope.launch { listenForChangesToSatelliteSupport(satelliteManager) }
+ scope.launch(context = bgDispatcher) {
+ listenForChangesToSatelliteSupport(satelliteManager)
+ }
}
} else {
logBuffer.i { "Satellite manager is null" }
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/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt
index cea51a89a378..222a7fe05778 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt
@@ -23,7 +23,6 @@ import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
-import com.android.systemui.Flags as AConfigFlags
import com.android.systemui.SysuiTestCase
import com.android.systemui.biometrics.AuthController
import com.android.systemui.flags.FakeFeatureFlags
@@ -66,8 +65,6 @@ class DefaultDeviceEntrySectionTest : SysuiTestCase() {
fun setup() {
MockitoAnnotations.initMocks(this)
- mSetFlagsRule.enableFlags(AConfigFlags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR)
-
featureFlags =
FakeFeatureFlagsClassic().apply { set(Flags.LOCKSCREEN_ENABLE_LANDSCAPE, false) }
underTest =
@@ -88,16 +85,7 @@ class DefaultDeviceEntrySectionTest : SysuiTestCase() {
}
@Test
- fun addViewsConditionally_migrateFlagOn() {
- mSetFlagsRule.enableFlags(AConfigFlags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR)
- val constraintLayout = ConstraintLayout(context, null)
- underTest.addViews(constraintLayout)
- assertThat(constraintLayout.childCount).isGreaterThan(0)
- }
-
- @Test
- fun addViewsConditionally_migrateAndRefactorFlagsOn() {
- mSetFlagsRule.enableFlags(AConfigFlags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR)
+ fun addViewsConditionally() {
val constraintLayout = ConstraintLayout(context, null)
underTest.addViews(constraintLayout)
assertThat(constraintLayout.childCount).isGreaterThan(0)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
deleted file mode 100644
index 7e85dd5d3236..000000000000
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
+++ /dev/null
@@ -1,771 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.keyguard.ui.viewmodel
-
-import android.app.admin.DevicePolicyManager
-import android.content.Intent
-import android.os.UserHandle
-import android.platform.test.flag.junit.FlagsParameterization
-import androidx.test.filters.SmallTest
-import com.android.internal.logging.testing.UiEventLoggerFake
-import com.android.internal.widget.LockPatternUtils
-import com.android.keyguard.logging.KeyguardQuickAffordancesLogger
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.animation.DialogTransitionAnimator
-import com.android.systemui.animation.Expandable
-import com.android.systemui.broadcast.BroadcastDispatcher
-import com.android.systemui.common.shared.model.Icon
-import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.deviceentry.domain.interactor.deviceEntryFaceAuthInteractor
-import com.android.systemui.dock.DockManagerFake
-import com.android.systemui.doze.util.BurnInHelperWrapper
-import com.android.systemui.flags.FakeFeatureFlags
-import com.android.systemui.flags.Flags
-import com.android.systemui.flags.andSceneContainer
-import com.android.systemui.keyguard.data.quickaffordance.BuiltInKeyguardQuickAffordanceKeys
-import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceConfig
-import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceProviderClientFactory
-import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
-import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceLegacySettingSyncer
-import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceLocalUserSelectionManager
-import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceRemoteUserSelectionManager
-import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
-import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
-import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepository
-import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor
-import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
-import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor
-import com.android.systemui.keyguard.domain.interactor.KeyguardTouchHandlingInteractor
-import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
-import com.android.systemui.keyguard.shared.quickaffordance.ActivationState
-import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition
-import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancesMetricsLogger
-import com.android.systemui.kosmos.testDispatcher
-import com.android.systemui.kosmos.testScope
-import com.android.systemui.plugins.ActivityStarter
-import com.android.systemui.res.R
-import com.android.systemui.scene.domain.interactor.sceneInteractor
-import com.android.systemui.settings.UserFileManager
-import com.android.systemui.settings.UserTracker
-import com.android.systemui.shade.domain.interactor.shadeInteractor
-import com.android.systemui.shade.pulsingGestureListener
-import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
-import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper
-import com.android.systemui.statusbar.policy.KeyguardStateController
-import com.android.systemui.testKosmos
-import com.android.systemui.util.FakeSharedPreferences
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.mock
-import com.android.systemui.util.mockito.whenever
-import com.android.systemui.util.settings.fakeSettings
-import com.google.common.truth.Truth.assertThat
-import kotlin.math.max
-import kotlin.math.min
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.test.runTest
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.ArgumentMatchers.anyInt
-import org.mockito.ArgumentMatchers.anyString
-import org.mockito.Mock
-import org.mockito.Mockito
-import org.mockito.Mockito.verifyNoMoreInteractions
-import org.mockito.MockitoAnnotations
-import platform.test.runner.parameterized.ParameterizedAndroidJunit4
-import platform.test.runner.parameterized.Parameters
-
-@SmallTest
-@RunWith(ParameterizedAndroidJunit4::class)
-class KeyguardBottomAreaViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
-
- private val kosmos = testKosmos()
- private val testDispatcher = kosmos.testDispatcher
- private val testScope = kosmos.testScope
- private val settings = kosmos.fakeSettings
-
- @Mock private lateinit var expandable: Expandable
- @Mock private lateinit var burnInHelperWrapper: BurnInHelperWrapper
- @Mock private lateinit var lockPatternUtils: LockPatternUtils
- @Mock private lateinit var keyguardStateController: KeyguardStateController
- @Mock private lateinit var userTracker: UserTracker
- @Mock private lateinit var activityStarter: ActivityStarter
- @Mock private lateinit var launchAnimator: DialogTransitionAnimator
- @Mock private lateinit var devicePolicyManager: DevicePolicyManager
- @Mock private lateinit var logger: KeyguardQuickAffordancesLogger
- @Mock private lateinit var metricsLogger: KeyguardQuickAffordancesMetricsLogger
- @Mock private lateinit var broadcastDispatcher: BroadcastDispatcher
- @Mock private lateinit var accessibilityManager: AccessibilityManagerWrapper
-
- private lateinit var underTest: KeyguardBottomAreaViewModel
-
- private lateinit var repository: FakeKeyguardRepository
- private lateinit var homeControlsQuickAffordanceConfig: FakeKeyguardQuickAffordanceConfig
- private lateinit var quickAccessWalletAffordanceConfig: FakeKeyguardQuickAffordanceConfig
- private lateinit var qrCodeScannerAffordanceConfig: FakeKeyguardQuickAffordanceConfig
- private lateinit var dockManager: DockManagerFake
- private lateinit var biometricSettingsRepository: FakeBiometricSettingsRepository
-
- init {
- mSetFlagsRule.setFlagsParameterization(flags)
- }
-
- @Before
- fun setUp() {
- MockitoAnnotations.initMocks(this)
-
- overrideResource(R.bool.custom_lockscreen_shortcuts_enabled, true)
- overrideResource(
- R.array.config_keyguardQuickAffordanceDefaults,
- arrayOf(
- KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START +
- ":" +
- BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS,
- KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END +
- ":" +
- BuiltInKeyguardQuickAffordanceKeys.QUICK_ACCESS_WALLET
- )
- )
-
- whenever(burnInHelperWrapper.burnInOffset(anyInt(), any()))
- .thenReturn(RETURNED_BURN_IN_OFFSET)
-
- homeControlsQuickAffordanceConfig =
- FakeKeyguardQuickAffordanceConfig(BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS)
- quickAccessWalletAffordanceConfig =
- FakeKeyguardQuickAffordanceConfig(
- BuiltInKeyguardQuickAffordanceKeys.QUICK_ACCESS_WALLET
- )
- qrCodeScannerAffordanceConfig =
- FakeKeyguardQuickAffordanceConfig(BuiltInKeyguardQuickAffordanceKeys.QR_CODE_SCANNER)
- dockManager = DockManagerFake()
- biometricSettingsRepository = FakeBiometricSettingsRepository()
- val featureFlags =
- FakeFeatureFlags().apply { set(Flags.LOCK_SCREEN_LONG_PRESS_ENABLED, false) }
-
- val withDeps = KeyguardInteractorFactory.create(featureFlags = featureFlags)
- val keyguardInteractor = withDeps.keyguardInteractor
- repository = withDeps.repository
-
- whenever(userTracker.userHandle).thenReturn(mock())
- whenever(lockPatternUtils.getStrongAuthForUser(anyInt()))
- .thenReturn(LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED)
- val localUserSelectionManager =
- KeyguardQuickAffordanceLocalUserSelectionManager(
- context = context,
- userFileManager =
- mock<UserFileManager>().apply {
- whenever(
- getSharedPreferences(
- anyString(),
- anyInt(),
- anyInt(),
- )
- )
- .thenReturn(FakeSharedPreferences())
- },
- userTracker = userTracker,
- broadcastDispatcher = fakeBroadcastDispatcher,
- )
- val remoteUserSelectionManager =
- KeyguardQuickAffordanceRemoteUserSelectionManager(
- scope = testScope.backgroundScope,
- userTracker = userTracker,
- clientFactory = FakeKeyguardQuickAffordanceProviderClientFactory(userTracker),
- userHandle = UserHandle.SYSTEM,
- )
- val quickAffordanceRepository =
- KeyguardQuickAffordanceRepository(
- appContext = context,
- scope = testScope.backgroundScope,
- localUserSelectionManager = localUserSelectionManager,
- remoteUserSelectionManager = remoteUserSelectionManager,
- userTracker = userTracker,
- legacySettingSyncer =
- KeyguardQuickAffordanceLegacySettingSyncer(
- scope = testScope.backgroundScope,
- backgroundDispatcher = testDispatcher,
- secureSettings = settings,
- selectionsManager = localUserSelectionManager,
- ),
- configs =
- setOf(
- homeControlsQuickAffordanceConfig,
- quickAccessWalletAffordanceConfig,
- qrCodeScannerAffordanceConfig,
- ),
- dumpManager = mock(),
- userHandle = UserHandle.SYSTEM,
- )
- val keyguardTouchHandlingInteractor =
- KeyguardTouchHandlingInteractor(
- context = mContext,
- scope = testScope.backgroundScope,
- transitionInteractor = kosmos.keyguardTransitionInteractor,
- repository = repository,
- logger = UiEventLoggerFake(),
- featureFlags = featureFlags,
- broadcastDispatcher = broadcastDispatcher,
- accessibilityManager = accessibilityManager,
- pulsingGestureListener = kosmos.pulsingGestureListener,
- faceAuthInteractor = kosmos.deviceEntryFaceAuthInteractor,
- )
- underTest =
- KeyguardBottomAreaViewModel(
- keyguardInteractor = keyguardInteractor,
- quickAffordanceInteractor =
- KeyguardQuickAffordanceInteractor(
- keyguardInteractor = keyguardInteractor,
- shadeInteractor = kosmos.shadeInteractor,
- lockPatternUtils = lockPatternUtils,
- keyguardStateController = keyguardStateController,
- userTracker = userTracker,
- activityStarter = activityStarter,
- featureFlags = featureFlags,
- repository = { quickAffordanceRepository },
- launchAnimator = launchAnimator,
- logger = logger,
- metricsLogger = metricsLogger,
- devicePolicyManager = devicePolicyManager,
- dockManager = dockManager,
- biometricSettingsRepository = biometricSettingsRepository,
- backgroundDispatcher = testDispatcher,
- appContext = mContext,
- sceneInteractor = { kosmos.sceneInteractor },
- ),
- bottomAreaInteractor = KeyguardBottomAreaInteractor(repository = repository),
- burnInHelperWrapper = burnInHelperWrapper,
- keyguardTouchHandlingViewModel =
- KeyguardTouchHandlingViewModel(
- interactor = keyguardTouchHandlingInteractor,
- ),
- settingsMenuViewModel =
- KeyguardSettingsMenuViewModel(
- interactor = keyguardTouchHandlingInteractor,
- ),
- )
- }
-
- @Test
- fun startButton_present_visibleModel_startsActivityOnClick() =
- testScope.runTest {
- repository.setKeyguardShowing(true)
- val latest = collectLastValue(underTest.startButton)
-
- val testConfig =
- TestConfig(
- isVisible = true,
- isClickable = true,
- isActivated = true,
- icon = mock(),
- canShowWhileLocked = false,
- intent = Intent("action"),
- slotId = KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId(),
- )
- val configKey =
- setUpQuickAffordanceModel(
- position = KeyguardQuickAffordancePosition.BOTTOM_START,
- testConfig = testConfig,
- )
-
- assertQuickAffordanceViewModel(
- viewModel = latest(),
- testConfig = testConfig,
- configKey = configKey,
- )
- }
-
- @Test
- fun startButton_hiddenWhenDevicePolicyDisablesAllKeyguardFeatures() =
- testScope.runTest {
- whenever(devicePolicyManager.getKeyguardDisabledFeatures(null, userTracker.userId))
- .thenReturn(DevicePolicyManager.KEYGUARD_DISABLE_FEATURES_ALL)
- repository.setKeyguardShowing(true)
- val latest by collectLastValue(underTest.startButton)
-
- val testConfig =
- TestConfig(
- isVisible = true,
- isClickable = true,
- isActivated = true,
- icon = mock(),
- canShowWhileLocked = false,
- intent = Intent("action"),
- slotId = KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId(),
- )
- val configKey =
- setUpQuickAffordanceModel(
- position = KeyguardQuickAffordancePosition.BOTTOM_START,
- testConfig = testConfig,
- )
-
- assertQuickAffordanceViewModel(
- viewModel = latest,
- testConfig =
- TestConfig(
- isVisible = false,
- slotId = KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId(),
- ),
- configKey = configKey,
- )
- }
-
- @Test
- fun startButton_inPreviewMode_visibleEvenWhenKeyguardNotShowing() =
- testScope.runTest {
- underTest.enablePreviewMode(
- initiallySelectedSlotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START,
- shouldHighlightSelectedAffordance = true,
- )
- repository.setKeyguardShowing(false)
- val latest = collectLastValue(underTest.startButton)
-
- val icon: Icon = mock()
- val configKey =
- setUpQuickAffordanceModel(
- position = KeyguardQuickAffordancePosition.BOTTOM_START,
- testConfig =
- TestConfig(
- isVisible = true,
- isClickable = true,
- isActivated = true,
- icon = icon,
- canShowWhileLocked = false,
- intent = Intent("action"),
- slotId = KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId(),
- ),
- )
-
- assertQuickAffordanceViewModel(
- viewModel = latest(),
- testConfig =
- TestConfig(
- isVisible = true,
- isClickable = false,
- isActivated = false,
- icon = icon,
- canShowWhileLocked = false,
- intent = Intent("action"),
- isSelected = true,
- slotId = KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId(),
- ),
- configKey = configKey,
- )
- assertThat(latest()?.isSelected).isTrue()
- }
-
- @Test
- fun endButton_inHiglightedPreviewMode_dimmedWhenOtherIsSelected() =
- testScope.runTest {
- underTest.enablePreviewMode(
- initiallySelectedSlotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START,
- shouldHighlightSelectedAffordance = true,
- )
- repository.setKeyguardShowing(false)
- val startButton = collectLastValue(underTest.startButton)
- val endButton = collectLastValue(underTest.endButton)
-
- val icon: Icon = mock()
- setUpQuickAffordanceModel(
- position = KeyguardQuickAffordancePosition.BOTTOM_START,
- testConfig =
- TestConfig(
- isVisible = true,
- isClickable = true,
- isActivated = true,
- icon = icon,
- canShowWhileLocked = false,
- intent = Intent("action"),
- slotId = KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId(),
- ),
- )
- val configKey =
- setUpQuickAffordanceModel(
- position = KeyguardQuickAffordancePosition.BOTTOM_END,
- testConfig =
- TestConfig(
- isVisible = true,
- isClickable = true,
- isActivated = true,
- icon = icon,
- canShowWhileLocked = false,
- intent = Intent("action"),
- slotId = KeyguardQuickAffordancePosition.BOTTOM_END.toSlotId(),
- ),
- )
-
- assertQuickAffordanceViewModel(
- viewModel = endButton(),
- testConfig =
- TestConfig(
- isVisible = true,
- isClickable = false,
- isActivated = false,
- icon = icon,
- canShowWhileLocked = false,
- intent = Intent("action"),
- isDimmed = true,
- slotId = KeyguardQuickAffordancePosition.BOTTOM_END.toSlotId(),
- ),
- configKey = configKey,
- )
- }
-
- @Test
- fun endButton_present_visibleModel_doNothingOnClick() =
- testScope.runTest {
- repository.setKeyguardShowing(true)
- val latest = collectLastValue(underTest.endButton)
-
- val config =
- TestConfig(
- isVisible = true,
- isClickable = true,
- icon = mock(),
- canShowWhileLocked = false,
- intent =
- null, // This will cause it to tell the system that the click was handled.
- slotId = KeyguardQuickAffordancePosition.BOTTOM_END.toSlotId(),
- )
- val configKey =
- setUpQuickAffordanceModel(
- position = KeyguardQuickAffordancePosition.BOTTOM_END,
- testConfig = config,
- )
-
- assertQuickAffordanceViewModel(
- viewModel = latest(),
- testConfig = config,
- configKey = configKey,
- )
- }
-
- @Test
- fun startButton_notPresent_modelIsHidden() =
- testScope.runTest {
- val latest = collectLastValue(underTest.startButton)
-
- val config =
- TestConfig(
- isVisible = false,
- slotId = KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId(),
- )
- val configKey =
- setUpQuickAffordanceModel(
- position = KeyguardQuickAffordancePosition.BOTTOM_START,
- testConfig = config,
- )
-
- assertQuickAffordanceViewModel(
- viewModel = latest(),
- testConfig = config,
- configKey = configKey,
- )
- }
-
- @Test
- fun animateButtonReveal() =
- testScope.runTest {
- repository.setKeyguardShowing(true)
- val testConfig =
- TestConfig(
- isVisible = true,
- isClickable = true,
- icon = mock(),
- canShowWhileLocked = false,
- intent = Intent("action"),
- slotId = KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId(),
- )
-
- setUpQuickAffordanceModel(
- position = KeyguardQuickAffordancePosition.BOTTOM_START,
- testConfig = testConfig,
- )
-
- val value = collectLastValue(underTest.startButton.map { it.animateReveal })
-
- assertThat(value()).isFalse()
- repository.setAnimateDozingTransitions(true)
- assertThat(value()).isTrue()
- repository.setAnimateDozingTransitions(false)
- assertThat(value()).isFalse()
- }
-
- @Test
- fun isOverlayContainerVisible() =
- testScope.runTest {
- val value = collectLastValue(underTest.isOverlayContainerVisible)
-
- assertThat(value()).isTrue()
- repository.setIsDozing(true)
- assertThat(value()).isFalse()
- repository.setIsDozing(false)
- assertThat(value()).isTrue()
- }
-
- @Test
- fun alpha() =
- testScope.runTest {
- val value = collectLastValue(underTest.alpha)
-
- assertThat(value()).isEqualTo(1f)
- repository.setBottomAreaAlpha(0.1f)
- assertThat(value()).isEqualTo(0.1f)
- repository.setBottomAreaAlpha(0.5f)
- assertThat(value()).isEqualTo(0.5f)
- repository.setBottomAreaAlpha(0.2f)
- assertThat(value()).isEqualTo(0.2f)
- repository.setBottomAreaAlpha(0f)
- assertThat(value()).isEqualTo(0f)
- }
-
- @Test
- fun alpha_inPreviewMode_doesNotChange() =
- testScope.runTest {
- underTest.enablePreviewMode(
- initiallySelectedSlotId = null,
- shouldHighlightSelectedAffordance = false,
- )
- val value = collectLastValue(underTest.alpha)
-
- assertThat(value()).isEqualTo(1f)
- repository.setBottomAreaAlpha(0.1f)
- assertThat(value()).isEqualTo(1f)
- repository.setBottomAreaAlpha(0.5f)
- assertThat(value()).isEqualTo(1f)
- repository.setBottomAreaAlpha(0.2f)
- assertThat(value()).isEqualTo(1f)
- repository.setBottomAreaAlpha(0f)
- assertThat(value()).isEqualTo(1f)
- }
-
- @Test
- fun isClickable_trueWhenAlphaAtThreshold() =
- testScope.runTest {
- repository.setKeyguardShowing(true)
- repository.setBottomAreaAlpha(
- KeyguardBottomAreaViewModel.AFFORDANCE_FULLY_OPAQUE_ALPHA_THRESHOLD
- )
-
- val testConfig =
- TestConfig(
- isVisible = true,
- isClickable = true,
- icon = mock(),
- canShowWhileLocked = false,
- intent = Intent("action"),
- slotId = KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId(),
- )
- val configKey =
- setUpQuickAffordanceModel(
- position = KeyguardQuickAffordancePosition.BOTTOM_START,
- testConfig = testConfig,
- )
-
- val latest = collectLastValue(underTest.startButton)
-
- assertQuickAffordanceViewModel(
- viewModel = latest(),
- testConfig = testConfig,
- configKey = configKey,
- )
- }
-
- @Test
- fun isClickable_trueWhenAlphaAboveThreshold() =
- testScope.runTest {
- repository.setKeyguardShowing(true)
- val latest = collectLastValue(underTest.startButton)
- repository.setBottomAreaAlpha(
- min(1f, KeyguardBottomAreaViewModel.AFFORDANCE_FULLY_OPAQUE_ALPHA_THRESHOLD + 0.1f),
- )
-
- val testConfig =
- TestConfig(
- isVisible = true,
- isClickable = true,
- icon = mock(),
- canShowWhileLocked = false,
- intent = Intent("action"),
- slotId = KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId(),
- )
- val configKey =
- setUpQuickAffordanceModel(
- position = KeyguardQuickAffordancePosition.BOTTOM_START,
- testConfig = testConfig,
- )
-
- assertQuickAffordanceViewModel(
- viewModel = latest(),
- testConfig = testConfig,
- configKey = configKey,
- )
- }
-
- @Test
- fun isClickable_falseWhenAlphaBelowThreshold() =
- testScope.runTest {
- repository.setKeyguardShowing(true)
- val latest = collectLastValue(underTest.startButton)
- repository.setBottomAreaAlpha(
- max(0f, KeyguardBottomAreaViewModel.AFFORDANCE_FULLY_OPAQUE_ALPHA_THRESHOLD - 0.1f),
- )
-
- val testConfig =
- TestConfig(
- isVisible = true,
- isClickable = false,
- icon = mock(),
- canShowWhileLocked = false,
- intent = Intent("action"),
- slotId = KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId(),
- )
- val configKey =
- setUpQuickAffordanceModel(
- position = KeyguardQuickAffordancePosition.BOTTOM_START,
- testConfig = testConfig,
- )
-
- assertQuickAffordanceViewModel(
- viewModel = latest(),
- testConfig = testConfig,
- configKey = configKey,
- )
- }
-
- @Test
- fun isClickable_falseWhenAlphaAtZero() =
- testScope.runTest {
- repository.setKeyguardShowing(true)
- val latest = collectLastValue(underTest.startButton)
- repository.setBottomAreaAlpha(0f)
-
- val testConfig =
- TestConfig(
- isVisible = true,
- isClickable = false,
- icon = mock(),
- canShowWhileLocked = false,
- intent = Intent("action"),
- slotId = KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId(),
- )
- val configKey =
- setUpQuickAffordanceModel(
- position = KeyguardQuickAffordancePosition.BOTTOM_START,
- testConfig = testConfig,
- )
-
- assertQuickAffordanceViewModel(
- viewModel = latest(),
- testConfig = testConfig,
- configKey = configKey,
- )
- }
-
- private suspend fun setUpQuickAffordanceModel(
- position: KeyguardQuickAffordancePosition,
- testConfig: TestConfig,
- ): String {
- val config =
- when (position) {
- KeyguardQuickAffordancePosition.BOTTOM_START -> homeControlsQuickAffordanceConfig
- KeyguardQuickAffordancePosition.BOTTOM_END -> quickAccessWalletAffordanceConfig
- }
-
- val lockScreenState =
- if (testConfig.isVisible) {
- if (testConfig.intent != null) {
- config.onTriggeredResult =
- KeyguardQuickAffordanceConfig.OnTriggeredResult.StartActivity(
- intent = testConfig.intent,
- canShowWhileLocked = testConfig.canShowWhileLocked,
- )
- }
- KeyguardQuickAffordanceConfig.LockScreenState.Visible(
- icon = testConfig.icon ?: error("Icon is unexpectedly null!"),
- activationState =
- when (testConfig.isActivated) {
- true -> ActivationState.Active
- false -> ActivationState.Inactive
- }
- )
- } else {
- KeyguardQuickAffordanceConfig.LockScreenState.Hidden
- }
- config.setState(lockScreenState)
-
- return "${position.toSlotId()}::${config.key}"
- }
-
- private fun assertQuickAffordanceViewModel(
- viewModel: KeyguardQuickAffordanceViewModel?,
- testConfig: TestConfig,
- configKey: String,
- ) {
- checkNotNull(viewModel)
- assertThat(viewModel.isVisible).isEqualTo(testConfig.isVisible)
- assertThat(viewModel.isClickable).isEqualTo(testConfig.isClickable)
- assertThat(viewModel.isActivated).isEqualTo(testConfig.isActivated)
- assertThat(viewModel.isSelected).isEqualTo(testConfig.isSelected)
- assertThat(viewModel.isDimmed).isEqualTo(testConfig.isDimmed)
- assertThat(viewModel.slotId).isEqualTo(testConfig.slotId)
- if (testConfig.isVisible) {
- assertThat(viewModel.icon).isEqualTo(testConfig.icon)
- viewModel.onClicked.invoke(
- KeyguardQuickAffordanceViewModel.OnClickedParameters(
- configKey = configKey,
- expandable = expandable,
- slotId = viewModel.slotId,
- )
- )
- if (testConfig.intent != null) {
- assertThat(Mockito.mockingDetails(activityStarter).invocations).hasSize(1)
- } else {
- verifyNoMoreInteractions(activityStarter)
- }
- } else {
- assertThat(viewModel.isVisible).isFalse()
- }
- }
-
- private data class TestConfig(
- val isVisible: Boolean,
- val isClickable: Boolean = false,
- val isActivated: Boolean = false,
- val icon: Icon? = null,
- val canShowWhileLocked: Boolean = false,
- val intent: Intent? = null,
- val isSelected: Boolean = false,
- val isDimmed: Boolean = false,
- val slotId: String = ""
- ) {
- init {
- check(!isVisible || icon != null) { "Must supply non-null icon if visible!" }
- }
- }
-
- companion object {
- private const val DEFAULT_BURN_IN_OFFSET = 5
- private const val RETURNED_BURN_IN_OFFSET = 3
-
- @JvmStatic
- @Parameters(name = "{0}")
- fun getParams(): List<FlagsParameterization> {
- return FlagsParameterization.allCombinationsOf().andSceneContainer()
- }
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt
index cb2c8fc2c418..3364528f0b54 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt
@@ -25,7 +25,6 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.internal.widget.LockPatternUtils
import com.android.keyguard.logging.KeyguardQuickAffordancesLogger
-import com.android.systemui.Flags as AConfigFlags
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.DialogTransitionAnimator
import com.android.systemui.animation.Expandable
@@ -191,8 +190,6 @@ class KeyguardQuickAffordancesCombinedViewModelTest : SysuiTestCase() {
dockManager = DockManagerFake()
biometricSettingsRepository = FakeBiometricSettingsRepository()
- mSetFlagsRule.enableFlags(AConfigFlags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR)
-
val featureFlags =
FakeFeatureFlags().apply { set(Flags.LOCK_SCREEN_LONG_PRESS_ENABLED, false) }
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/flags/EnableSceneContainer.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/EnableSceneContainer.kt
index 41402badf141..4513cc086513 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/EnableSceneContainer.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/EnableSceneContainer.kt
@@ -17,7 +17,6 @@
package com.android.systemui.flags
import android.platform.test.annotations.EnableFlags
-import com.android.systemui.Flags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR
import com.android.systemui.Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR
import com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT
import com.android.systemui.Flags.FLAG_NOTIFICATION_AVALANCHE_THROTTLE_HUN
@@ -29,7 +28,6 @@ import com.android.systemui.Flags.FLAG_SCENE_CONTAINER
* that feature. It is also picked up by [SceneContainerRule] to set non-aconfig prerequisites.
*/
@EnableFlags(
- FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR,
FLAG_KEYGUARD_WM_STATE_REFACTOR,
FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT,
FLAG_NOTIFICATION_AVALANCHE_THROTTLE_HUN,
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/keyguard/data/repository/FakeKeyguardRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
index 693ec7954d70..1288d3151051 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
@@ -56,9 +56,6 @@ class FakeKeyguardRepository @Inject constructor() : KeyguardRepository {
override val animateBottomAreaDozingTransitions: StateFlow<Boolean> =
_animateBottomAreaDozingTransitions
- private val _bottomAreaAlpha = MutableStateFlow(1f)
- override val bottomAreaAlpha: StateFlow<Float> = _bottomAreaAlpha
-
private val _isKeyguardShowing = MutableStateFlow(false)
override val isKeyguardShowing: StateFlow<Boolean> = _isKeyguardShowing
@@ -159,11 +156,6 @@ class FakeKeyguardRepository @Inject constructor() : KeyguardRepository {
_animateBottomAreaDozingTransitions.tryEmit(animate)
}
- @Deprecated("Deprecated as part of b/278057014")
- override fun setBottomAreaAlpha(alpha: Float) {
- _bottomAreaAlpha.value = alpha
- }
-
fun setKeyguardShowing(isShowing: Boolean) {
_isKeyguardShowing.value = isShowing
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModelKosmos.kt
index 2d1f836d455d..b03624bcc294 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModelKosmos.kt
@@ -26,5 +26,6 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi
val Kosmos.alternateBouncerToPrimaryBouncerTransitionViewModel by Fixture {
AlternateBouncerToPrimaryBouncerTransitionViewModel(
animationFlow = keyguardTransitionAnimationFlow,
+ shadeDependentFlows = shadeDependentFlows,
)
}
diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AodToPrimaryBouncerViewModelKosmos.kt
index c5012b01dd3e..5e6d605e9bfb 100644
--- a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AodToPrimaryBouncerViewModelKosmos.kt
@@ -14,22 +14,14 @@
* limitations under the License.
*/
-package com.android.keyguard
+package com.android.systemui.keyguard.ui.viewmodel
-import android.view.MotionEvent
-import android.view.View
+import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import kotlinx.coroutines.ExperimentalCoroutinesApi
-/** Controls the [LockIconView]. */
-interface LockIconViewController {
- fun setLockIconView(lockIconView: View)
-
- fun getTop(): Float
-
- fun getBottom(): Float
-
- fun dozeTimeTick()
-
- fun setAlpha(alpha: Float)
-
- fun willHandleTouchWhileDozing(event: MotionEvent): Boolean
+@OptIn(ExperimentalCoroutinesApi::class)
+val Kosmos.aodToPrimaryBouncerTransitionViewModel by Fixture {
+ AodToPrimaryBouncerTransitionViewModel(animationFlow = keyguardTransitionAnimationFlow)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardBottomAreaInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGlanceableHubTransitionViewModelKosmos.kt
index a3955f7634eb..09233af7bae6 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardBottomAreaInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGlanceableHubTransitionViewModelKosmos.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * 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.
@@ -14,14 +14,14 @@
* limitations under the License.
*/
-package com.android.systemui.keyguard.domain.interactor
+package com.android.systemui.keyguard.ui.viewmodel
-import com.android.systemui.keyguard.data.repository.keyguardRepository
+import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
-val Kosmos.keyguardBottomAreaInteractor by Fixture {
- KeyguardBottomAreaInteractor(
- repository = keyguardRepository,
+val Kosmos.primaryBouncerToGlanceableHubTransitionViewModel by Fixture {
+ PrimaryBouncerToGlanceableHubTransitionViewModel(
+ animationFlow = keyguardTransitionAnimationFlow
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModelKosmos.kt
index 370afc3b660b..76478cb43361 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModelKosmos.kt
@@ -26,5 +26,6 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi
val Kosmos.primaryBouncerToLockscreenTransitionViewModel by Fixture {
PrimaryBouncerToLockscreenTransitionViewModel(
animationFlow = keyguardTransitionAnimationFlow,
+ shadeDependentFlows = shadeDependentFlows,
)
}
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/shade/domain/interactor/ShadeLockscreenInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeLockscreenInteractorKosmos.kt
index 00b788fa4a41..c0d2621fae23 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeLockscreenInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeLockscreenInteractorKosmos.kt
@@ -30,7 +30,6 @@ val Kosmos.shadeLockscreenInteractor by
backgroundScope = applicationCoroutineScope,
shadeInteractor = shadeInteractorImpl,
sceneInteractor = sceneInteractor,
- lockIconViewController = mock(),
shadeRepository = shadeRepository,
)
}
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/autofill/bugfixes.aconfig b/services/autofill/bugfixes.aconfig
index 7c5cfa91ab8a..65c446ee6fa8 100644
--- a/services/autofill/bugfixes.aconfig
+++ b/services/autofill/bugfixes.aconfig
@@ -96,3 +96,13 @@ flag {
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "add_accessibility_title_for_augmented_autofill_dropdown"
+ namespace: "autofill"
+ description: "Add accessibility title for augmented autofill dropdown"
+ bug: "375284244"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index aea24d978bee..600aa1fdaa04 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -427,11 +427,14 @@ public final class BatteryStatsService extends IBatteryStats.Stub
com.android.internal.R.bool.config_batteryStatsResetOnUnplugHighBatteryLevel);
final boolean resetOnUnplugAfterSignificantCharge = context.getResources().getBoolean(
com.android.internal.R.bool.config_batteryStatsResetOnUnplugAfterSignificantCharge);
+ final int batteryHistoryStorageSize = context.getResources().getInteger(
+ com.android.internal.R.integer.config_batteryHistoryStorageSize);
BatteryStatsImpl.BatteryStatsConfig.Builder batteryStatsConfigBuilder =
new BatteryStatsImpl.BatteryStatsConfig.Builder()
.setResetOnUnplugHighBatteryLevel(resetOnUnplugHighBatteryLevel)
.setResetOnUnplugAfterSignificantCharge(
- resetOnUnplugAfterSignificantCharge);
+ resetOnUnplugAfterSignificantCharge)
+ .setMaxHistorySizeBytes(batteryHistoryStorageSize);
setPowerStatsThrottlePeriods(batteryStatsConfigBuilder, context.getResources().getString(
com.android.internal.R.string.config_powerStatsThrottlePeriods));
mBatteryStatsConfig = batteryStatsConfigBuilder.build();
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 1799b7715e5c..0fd47169122b 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -11845,7 +11845,7 @@ public class AudioService extends IAudioService.Stub
private AudioDeviceAttributes anonymizeAudioDeviceAttributesUnchecked(
AudioDeviceAttributes ada) {
- if (!AudioSystem.isBluetoothDevice(ada.getInternalType())) {
+ if (ada == null || !AudioSystem.isBluetoothDevice(ada.getInternalType())) {
return ada;
}
AudioDeviceAttributes res = new AudioDeviceAttributes(ada);
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index 4c5f65285a9e..ac0892b92646 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -1960,6 +1960,10 @@ public class Vpn {
public void onUserAdded(int userId) {
// If the user is restricted tie them to the parent user's VPN
UserInfo user = mUserManager.getUserInfo(userId);
+ if (user == null) {
+ Log.e(TAG, "Can not retrieve UserInfo for userId=" + userId);
+ return;
+ }
if (user.isRestricted() && user.restrictedProfileParentId == mUserId) {
synchronized(Vpn.this) {
final Set<Range<Integer>> existingRanges = mNetworkCapabilities.getUids();
@@ -1989,6 +1993,14 @@ public class Vpn {
public void onUserRemoved(int userId) {
// clean up if restricted
UserInfo user = mUserManager.getUserInfo(userId);
+ // TODO: Retrieving UserInfo upon receiving the USER_REMOVED intent is not guaranteed.
+ // This could prevent the removal of associated ranges. To ensure proper range removal,
+ // store the user info when adding ranges. This allows using the user ID in the
+ // USER_REMOVED intent to handle the removal process.
+ if (user == null) {
+ Log.e(TAG, "Can not retrieve UserInfo for userId=" + userId);
+ return;
+ }
if (user.isRestricted() && user.restrictedProfileParentId == mUserId) {
synchronized(Vpn.this) {
final Set<Range<Integer>> existingRanges = mNetworkCapabilities.getUids();
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/power/stats/BatteryStatsImpl.java b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
index fe14f6b172f1..95690cd63994 100644
--- a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
+++ b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
@@ -600,13 +600,7 @@ public class BatteryStatsImpl extends BatteryStats {
private final int mFlags;
private final Long mDefaultPowerStatsThrottlePeriod;
private final Map<String, Long> mPowerStatsThrottlePeriods;
-
- @VisibleForTesting
- public BatteryStatsConfig() {
- mFlags = 0;
- mDefaultPowerStatsThrottlePeriod = 0L;
- mPowerStatsThrottlePeriods = Map.of();
- }
+ private final int mMaxHistorySizeBytes;
private BatteryStatsConfig(Builder builder) {
int flags = 0;
@@ -619,6 +613,7 @@ public class BatteryStatsImpl extends BatteryStats {
mFlags = flags;
mDefaultPowerStatsThrottlePeriod = builder.mDefaultPowerStatsThrottlePeriod;
mPowerStatsThrottlePeriods = builder.mPowerStatsThrottlePeriods;
+ mMaxHistorySizeBytes = builder.mMaxHistorySizeBytes;
}
/**
@@ -648,18 +643,24 @@ public class BatteryStatsImpl extends BatteryStats {
mDefaultPowerStatsThrottlePeriod);
}
+ public int getMaxHistorySizeBytes() {
+ return mMaxHistorySizeBytes;
+ }
+
/**
* Builder for BatteryStatsConfig
*/
public static class Builder {
private boolean mResetOnUnplugHighBatteryLevel;
private boolean mResetOnUnplugAfterSignificantCharge;
- public static final long DEFAULT_POWER_STATS_THROTTLE_PERIOD =
+ private static final long DEFAULT_POWER_STATS_THROTTLE_PERIOD =
TimeUnit.HOURS.toMillis(1);
- public static final long DEFAULT_POWER_STATS_THROTTLE_PERIOD_CPU =
+ private static final long DEFAULT_POWER_STATS_THROTTLE_PERIOD_CPU =
TimeUnit.MINUTES.toMillis(1);
+ private static final int DEFAULT_MAX_HISTORY_SIZE = 4 * 1024 * 1024;
private long mDefaultPowerStatsThrottlePeriod = DEFAULT_POWER_STATS_THROTTLE_PERIOD;
private final Map<String, Long> mPowerStatsThrottlePeriods = new HashMap<>();
+ private int mMaxHistorySizeBytes = DEFAULT_MAX_HISTORY_SIZE;
public Builder() {
mResetOnUnplugHighBatteryLevel = true;
@@ -712,6 +713,15 @@ public class BatteryStatsImpl extends BatteryStats {
mDefaultPowerStatsThrottlePeriod = periodMs;
return this;
}
+
+ /**
+ * Sets the maximum amount of disk space, in bytes, that battery history can
+ * utilize. As this space fills up, the oldest history chunks must be expunged.
+ */
+ public Builder setMaxHistorySizeBytes(int maxHistorySizeBytes) {
+ mMaxHistorySizeBytes = maxHistorySizeBytes;
+ return this;
+ }
}
}
@@ -11425,7 +11435,7 @@ public class BatteryStatsImpl extends BatteryStats {
}
mHistory = new BatteryStatsHistory(null /* historyBuffer */, systemDir,
- mConstants.MAX_HISTORY_FILES, mConstants.MAX_HISTORY_BUFFER, mStepDetailsCalculator,
+ mConstants.MAX_HISTORY_SIZE, mConstants.MAX_HISTORY_BUFFER, mStepDetailsCalculator,
mClock, mMonotonicClock, traceDelegate, eventLogger);
mCpuPowerStatsCollector = new CpuPowerStatsCollector(mPowerStatsCollectorInjector);
@@ -11970,9 +11980,8 @@ public class BatteryStatsImpl extends BatteryStats {
return mNextMaxDailyDeadlineMs;
}
- @GuardedBy("this")
public int getHistoryTotalSize() {
- return mConstants.MAX_HISTORY_BUFFER * mConstants.MAX_HISTORY_FILES;
+ return mHistory.getMaxHistorySize();
}
public int getHistoryUsedSize() {
@@ -16101,7 +16110,7 @@ public class BatteryStatsImpl extends BatteryStats {
= "battery_level_collection_delay_ms";
public static final String KEY_PROC_STATE_CHANGE_COLLECTION_DELAY_MS =
"procstate_change_collection_delay_ms";
- public static final String KEY_MAX_HISTORY_FILES = "max_history_files";
+ public static final String KEY_MAX_HISTORY_SIZE = "max_history_size";
public static final String KEY_MAX_HISTORY_BUFFER_KB = "max_history_buffer_kb";
public static final String KEY_BATTERY_CHARGED_DELAY_MS =
"battery_charged_delay_ms";
@@ -16152,9 +16161,7 @@ public class BatteryStatsImpl extends BatteryStats {
private static final long DEFAULT_EXTERNAL_STATS_COLLECTION_RATE_LIMIT_MS = 600_000;
private static final long DEFAULT_BATTERY_LEVEL_COLLECTION_DELAY_MS = 300_000;
private static final long DEFAULT_PROC_STATE_CHANGE_COLLECTION_DELAY_MS = 60_000;
- private static final int DEFAULT_MAX_HISTORY_FILES = 32;
private static final int DEFAULT_MAX_HISTORY_BUFFER_KB = 128; /*Kilo Bytes*/
- private static final int DEFAULT_MAX_HISTORY_FILES_LOW_RAM_DEVICE = 64;
private static final int DEFAULT_MAX_HISTORY_BUFFER_LOW_RAM_DEVICE_KB = 64; /*Kilo Bytes*/
private static final int DEFAULT_BATTERY_CHARGED_DELAY_MS = 900000; /* 15 min */
private static final int DEFAULT_BATTERY_CHARGING_ENFORCE_LEVEL = 90;
@@ -16176,7 +16183,7 @@ public class BatteryStatsImpl extends BatteryStats {
= DEFAULT_BATTERY_LEVEL_COLLECTION_DELAY_MS;
public long PROC_STATE_CHANGE_COLLECTION_DELAY_MS =
DEFAULT_PROC_STATE_CHANGE_COLLECTION_DELAY_MS;
- public int MAX_HISTORY_FILES;
+ public int MAX_HISTORY_SIZE;
public int MAX_HISTORY_BUFFER; /*Bytes*/
public int BATTERY_CHARGED_DELAY_MS = DEFAULT_BATTERY_CHARGED_DELAY_MS;
public int BATTERY_CHARGING_ENFORCE_LEVEL = DEFAULT_BATTERY_CHARGING_ENFORCE_LEVEL;
@@ -16192,12 +16199,11 @@ public class BatteryStatsImpl extends BatteryStats {
public Constants(Handler handler) {
super(handler);
if (isLowRamDevice()) {
- MAX_HISTORY_FILES = DEFAULT_MAX_HISTORY_FILES_LOW_RAM_DEVICE;
MAX_HISTORY_BUFFER = DEFAULT_MAX_HISTORY_BUFFER_LOW_RAM_DEVICE_KB * 1024;
} else {
- MAX_HISTORY_FILES = DEFAULT_MAX_HISTORY_FILES;
MAX_HISTORY_BUFFER = DEFAULT_MAX_HISTORY_BUFFER_KB * 1024;
}
+ MAX_HISTORY_SIZE = mBatteryStatsConfig.getMaxHistorySizeBytes();
}
public void startObserving(ContentResolver resolver) {
@@ -16260,13 +16266,23 @@ public class BatteryStatsImpl extends BatteryStats {
PROC_STATE_CHANGE_COLLECTION_DELAY_MS = mParser.getLong(
KEY_PROC_STATE_CHANGE_COLLECTION_DELAY_MS,
DEFAULT_PROC_STATE_CHANGE_COLLECTION_DELAY_MS);
- MAX_HISTORY_FILES = mParser.getInt(KEY_MAX_HISTORY_FILES,
- isLowRamDevice() ? DEFAULT_MAX_HISTORY_FILES_LOW_RAM_DEVICE
- : DEFAULT_MAX_HISTORY_FILES);
MAX_HISTORY_BUFFER = mParser.getInt(KEY_MAX_HISTORY_BUFFER_KB,
isLowRamDevice() ? DEFAULT_MAX_HISTORY_BUFFER_LOW_RAM_DEVICE_KB
: DEFAULT_MAX_HISTORY_BUFFER_KB)
* 1024;
+ int maxHistorySize = mParser.getInt(KEY_MAX_HISTORY_SIZE, -1);
+ if (maxHistorySize == -1) {
+ // Process the deprecated max_history_files parameter for compatibility
+ int maxHistoryFiles = mParser.getInt("max_history_files", -1);
+ if (maxHistoryFiles != -1) {
+ maxHistorySize = maxHistoryFiles * MAX_HISTORY_BUFFER;
+ }
+ }
+ if (maxHistorySize == -1) {
+ maxHistorySize = mBatteryStatsConfig.getMaxHistorySizeBytes();
+ }
+ MAX_HISTORY_SIZE = maxHistorySize;
+
final String perUidModemModel = mParser.getString(KEY_PER_UID_MODEM_POWER_MODEL,
"");
PER_UID_MODEM_MODEL = getPerUidModemModel(perUidModemModel);
@@ -16291,7 +16307,7 @@ public class BatteryStatsImpl extends BatteryStats {
*/
@VisibleForTesting
public void onChange() {
- mHistory.setMaxHistoryFiles(MAX_HISTORY_FILES);
+ mHistory.setMaxHistorySize(MAX_HISTORY_SIZE);
mHistory.setMaxHistoryBufferSize(MAX_HISTORY_BUFFER);
}
@@ -16354,8 +16370,8 @@ public class BatteryStatsImpl extends BatteryStats {
pw.println(BATTERY_LEVEL_COLLECTION_DELAY_MS);
pw.print(KEY_PROC_STATE_CHANGE_COLLECTION_DELAY_MS); pw.print("=");
pw.println(PROC_STATE_CHANGE_COLLECTION_DELAY_MS);
- pw.print(KEY_MAX_HISTORY_FILES); pw.print("=");
- pw.println(MAX_HISTORY_FILES);
+ pw.print(KEY_MAX_HISTORY_SIZE); pw.print("=");
+ pw.println(MAX_HISTORY_SIZE);
pw.print(KEY_MAX_HISTORY_BUFFER_KB); pw.print("=");
pw.println(MAX_HISTORY_BUFFER/1024);
pw.print(KEY_BATTERY_CHARGED_DELAY_MS); pw.print("=");
diff --git a/services/core/java/com/android/server/wm/AppCompatUtils.java b/services/core/java/com/android/server/wm/AppCompatUtils.java
index a41832498880..0369a0ff4c76 100644
--- a/services/core/java/com/android/server/wm/AppCompatUtils.java
+++ b/services/core/java/com/android/server/wm/AppCompatUtils.java
@@ -285,7 +285,7 @@ final class AppCompatUtils {
info.topActivityLetterboxAppWidth = TaskInfo.PROPERTY_VALUE_UNSET;
info.topActivityLetterboxBounds = null;
info.cameraCompatTaskInfo.freeformCameraCompatMode =
- CameraCompatTaskInfo.CAMERA_COMPAT_FREEFORM_NONE;
+ CameraCompatTaskInfo.CAMERA_COMPAT_FREEFORM_UNSPECIFIED;
info.clearTopActivityFlags();
}
}
diff --git a/services/core/java/com/android/server/wm/CameraCompatFreeformPolicy.java b/services/core/java/com/android/server/wm/CameraCompatFreeformPolicy.java
index 1b9454ec2272..f5bc9f0f9a47 100644
--- a/services/core/java/com/android/server/wm/CameraCompatFreeformPolicy.java
+++ b/services/core/java/com/android/server/wm/CameraCompatFreeformPolicy.java
@@ -21,6 +21,7 @@ import static android.app.CameraCompatTaskInfo.CAMERA_COMPAT_FREEFORM_LANDSCAPE_
import static android.app.CameraCompatTaskInfo.CAMERA_COMPAT_FREEFORM_NONE;
import static android.app.CameraCompatTaskInfo.CAMERA_COMPAT_FREEFORM_PORTRAIT_DEVICE_IN_LANDSCAPE;
import static android.app.CameraCompatTaskInfo.CAMERA_COMPAT_FREEFORM_PORTRAIT_DEVICE_IN_PORTRAIT;
+import static android.app.CameraCompatTaskInfo.CAMERA_COMPAT_FREEFORM_UNSPECIFIED;
import static android.app.WindowConfiguration.WINDOW_CONFIG_APP_BOUNDS;
import static android.app.WindowConfiguration.WINDOW_CONFIG_DISPLAY_ROTATION;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LOCKED;
@@ -235,7 +236,8 @@ final class CameraCompatFreeformPolicy implements CameraStateMonitor.CameraCompa
}
boolean isInFreeformCameraCompatMode(@NonNull ActivityRecord activity) {
- return getCameraCompatMode(activity) != CAMERA_COMPAT_FREEFORM_NONE;
+ return getCameraCompatMode(activity) != CAMERA_COMPAT_FREEFORM_UNSPECIFIED
+ && getCameraCompatMode(activity) != CAMERA_COMPAT_FREEFORM_NONE;
}
float getCameraCompatAspectRatio(@NonNull ActivityRecord activityRecord) {
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/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 29e0487dad0a..0eac4f2c922a 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -109,6 +109,7 @@ import com.android.internal.notification.SystemNotificationChannels;
import com.android.internal.os.ApplicationSharedMemory;
import com.android.internal.os.BinderInternal;
import com.android.internal.os.RuntimeInit;
+import com.android.internal.os.logging.MetricsLoggerWrapper;
import com.android.internal.pm.RoSystemFeatures;
import com.android.internal.policy.AttributeCache;
import com.android.internal.protolog.ProtoLog;
@@ -1002,6 +1003,17 @@ public final class SystemServer implements Dumpable {
}
});
+ // Register callback to report native memory metrics post GC cleanup
+ // for system_server
+ if (android.app.Flags.reportPostgcMemoryMetrics() &&
+ com.android.libcore.readonly.Flags.postCleanupApis()) {
+ VMRuntime.addPostCleanupCallback(new Runnable() {
+ @Override public void run() {
+ MetricsLoggerWrapper.logPostGcMemorySnapshot();
+ }
+ });
+ }
+
// Loop forever.
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
diff --git a/services/tests/VpnTests/java/com/android/server/connectivity/VpnTest.java b/services/tests/VpnTests/java/com/android/server/connectivity/VpnTest.java
index 5db6a8f12e52..9117cc8e5ab8 100644
--- a/services/tests/VpnTests/java/com/android/server/connectivity/VpnTest.java
+++ b/services/tests/VpnTests/java/com/android/server/connectivity/VpnTest.java
@@ -918,6 +918,30 @@ public class VpnTest extends VpnTestBase {
}
@Test
+ public void testOnUserAddedAndRemoved_nullUserInfo() throws Exception {
+ final Vpn vpn = createVpn(PRIMARY_USER.id);
+ final Set<Range<Integer>> initialRange = rangeSet(PRIMARY_USER_RANGE);
+ // Note since mVpnProfile is a Ikev2VpnProfile, this starts an IkeV2VpnRunner.
+ startLegacyVpn(vpn, mVpnProfile);
+ // Set an initial Uid range and mock the network agent
+ vpn.mNetworkCapabilities.setUids(initialRange);
+ vpn.mNetworkAgent = mMockNetworkAgent;
+
+ // Add the restricted user and then remove it immediately. So the getUserInfo() will return
+ // null for the given restricted user id.
+ setMockedUsers(PRIMARY_USER, RESTRICTED_PROFILE_A);
+ doReturn(null).when(mUserManager).getUserInfo(RESTRICTED_PROFILE_A.id);
+ vpn.onUserAdded(RESTRICTED_PROFILE_A.id);
+ // Expect no range change to the NetworkCapabilities.
+ assertEquals(initialRange, vpn.mNetworkCapabilities.getUids());
+
+ // Remove the restricted user
+ vpn.onUserRemoved(RESTRICTED_PROFILE_A.id);
+ // Expect no range change to the NetworkCapabilities.
+ assertEquals(initialRange, vpn.mNetworkCapabilities.getUids());
+ }
+
+ @Test
public void testPrepare_throwSecurityExceptionWhenGivenPackageDoesNotBelongToTheCaller()
throws Exception {
mTestDeps.mIgnoreCallingUidChecks = false;
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());
+ }
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/JobParametersTest.java b/services/tests/mockingservicestests/src/com/android/server/job/JobParametersTest.java
index 3b6c86e3c94f..0c92c10e2523 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/JobParametersTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/JobParametersTest.java
@@ -29,15 +29,20 @@ import android.app.job.IJobCallback;
import android.app.job.JobParameters;
import android.net.Uri;
import android.os.Parcel;
-import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.CheckFlagsRule;
import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.platform.test.flag.junit.SetFlagsRule;
+import libcore.junit.util.compat.CoreCompatChangeRule;
+import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges;
+import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
+
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
+import org.junit.rules.TestRule;
import org.mockito.Mock;
import org.mockito.MockitoSession;
import org.mockito.quality.Strictness;
@@ -47,7 +52,10 @@ public class JobParametersTest {
private static final int TEST_JOB_ID_1 = 123;
private static final String TEST_NAMESPACE = "TEST_NAMESPACE";
private static final String TEST_DEBUG_STOP_REASON = "TEST_DEBUG_STOP_REASON";
- @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+ @Rule
+ public TestRule compatChangeRule = new CoreCompatChangeRule();
@Rule
public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
@@ -129,9 +137,10 @@ public class JobParametersTest {
}
/** Test to verify that the JobParameters Cleaner is disabled */
- @RequiresFlagsEnabled(FLAG_HANDLE_ABANDONED_JOBS)
@Test
- public void testCleanerWithLeakedJobCleanerDisabled_flagHandleAbandonedJobs() {
+ @EnableFlags(FLAG_HANDLE_ABANDONED_JOBS)
+ @DisableCompatChanges({JobParameters.OVERRIDE_HANDLE_ABANDONED_JOBS})
+ public void testCleanerWithLeakedNoJobCleaner_EnableFlagDisableCompatHandleAbandonedJobs() {
// Inject real JobCallbackCleanup
JobParameters jobParameters = JobParameters.CREATOR.createFromParcel(mMockParcel);
@@ -150,4 +159,31 @@ public class JobParametersTest {
assertThat(jobParameters.getCleanable()).isNull();
assertThat(jobParameters.getJobCleanupCallback()).isNull();
}
+
+ /**
+ * Test to verify that the JobParameters Cleaner is not enabled
+ * when the compat change is enabled and the flag is enabled
+ */
+ @Test
+ @EnableFlags(FLAG_HANDLE_ABANDONED_JOBS)
+ @EnableCompatChanges({JobParameters.OVERRIDE_HANDLE_ABANDONED_JOBS})
+ public void testCleanerWithLeakedNoJobCleaner_EnableFlagEnableCompatHandleAbandonedJobs() {
+ // Inject real JobCallbackCleanup
+ JobParameters jobParameters = JobParameters.CREATOR.createFromParcel(mMockParcel);
+
+ // Enable the cleaner
+ jobParameters.enableCleaner();
+
+ // Verify the cleaner is not enabled
+ assertThat(jobParameters.getCleanable()).isNull();
+ assertThat(jobParameters.getJobCleanupCallback()).isNull();
+
+ // Disable the cleaner
+ jobParameters.disableCleaner();
+
+ // Verify the cleaner is disabled
+ assertThat(jobParameters.getCleanable()).isNull();
+ assertThat(jobParameters.getJobCleanupCallback()).isNull();
+ }
+
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
index 1e7a4f6cf51b..8c09f26bb7fa 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
@@ -53,6 +53,7 @@ import static org.mockito.Mockito.when;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
+import android.app.AppGlobals;
import android.app.IActivityManager;
import android.app.UiModeManager;
import android.app.job.JobInfo;
@@ -60,6 +61,7 @@ import android.app.job.JobParameters;
import android.app.job.JobScheduler;
import android.app.job.JobWorkItem;
import android.app.usage.UsageStatsManagerInternal;
+import android.compat.testing.PlatformCompatChangeRule;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
@@ -79,7 +81,6 @@ import android.os.BatteryManagerInternal.ChargingPolicyChangeListener;
import android.os.Looper;
import android.os.Process;
import android.os.RemoteException;
-import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.WorkSource;
import android.os.WorkSource.WorkChain;
@@ -105,10 +106,14 @@ import com.android.server.job.restrictions.ThermalStatusRestriction;
import com.android.server.pm.UserManagerInternal;
import com.android.server.usage.AppStandbyInternal;
+import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges;
+import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
+
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
+import org.junit.rules.TestRule;
import org.mockito.ArgumentCaptor;
import org.mockito.ArgumentMatchers;
import org.mockito.Mock;
@@ -120,6 +125,7 @@ import java.time.Duration;
import java.time.ZoneOffset;
public class JobSchedulerServiceTest {
+ private static final String SOURCE_PACKAGE = "com.android.frameworks.mockingservicestests";
private static final String TAG = JobSchedulerServiceTest.class.getSimpleName();
private static final int TEST_UID = 10123;
@@ -141,8 +147,13 @@ public class JobSchedulerServiceTest {
@Rule
public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+ @Rule
+ public TestRule compatChangeRule = new PlatformCompatChangeRule();
+
private ChargingPolicyChangeListener mChargingPolicyChangeListener;
+ private int mSourceUid;
+
private class TestJobSchedulerService extends JobSchedulerService {
TestJobSchedulerService(Context context) {
super(context);
@@ -157,7 +168,6 @@ public class JobSchedulerServiceTest {
.strictness(Strictness.LENIENT)
.mockStatic(LocalServices.class)
.mockStatic(PermissionChecker.class)
- .mockStatic(ServiceManager.class)
.startMocking();
// Called in JobSchedulerService constructor.
@@ -226,6 +236,7 @@ public class JobSchedulerServiceTest {
verify(mBatteryManagerInternal).registerChargingPolicyChangeListener(
chargingPolicyChangeListenerCaptor.capture());
mChargingPolicyChangeListener = chargingPolicyChangeListenerCaptor.getValue();
+ mSourceUid = AppGlobals.getPackageManager().getPackageUid(SOURCE_PACKAGE, 0, 0);
}
@After
@@ -1063,6 +1074,7 @@ public class JobSchedulerServiceTest {
*/
@Test
@EnableFlags(FLAG_HANDLE_ABANDONED_JOBS)
+ @DisableCompatChanges({JobParameters.OVERRIDE_HANDLE_ABANDONED_JOBS})
public void testGetRescheduleJobForFailure_abandonedJob() {
final long nowElapsed = sElapsedRealtimeClock.millis();
final long initialBackoffMs = MINUTE_IN_MILLIS;
@@ -1074,6 +1086,9 @@ public class JobSchedulerServiceTest {
assertEquals(JobStatus.NO_EARLIEST_RUNTIME, originalJob.getEarliestRunTime());
assertEquals(JobStatus.NO_LATEST_RUNTIME, originalJob.getLatestRunTimeElapsed());
+ spyOn(originalJob);
+ doReturn(mSourceUid).when(originalJob).getSourceUid();
+
// failure = 1, systemStop = 0, abandoned = 1
JobStatus rescheduledJob = mService.getRescheduleJobForFailureLocked(originalJob,
JobParameters.STOP_REASON_DEVICE_STATE,
@@ -1081,6 +1096,8 @@ public class JobSchedulerServiceTest {
assertEquals(nowElapsed + initialBackoffMs, rescheduledJob.getEarliestRunTime());
assertEquals(JobStatus.NO_LATEST_RUNTIME, rescheduledJob.getLatestRunTimeElapsed());
+ spyOn(rescheduledJob);
+ doReturn(mSourceUid).when(rescheduledJob).getSourceUid();
// failure = 2, systemstop = 0, abandoned = 2
rescheduledJob = mService.getRescheduleJobForFailureLocked(rescheduledJob,
JobParameters.STOP_REASON_DEVICE_STATE,
@@ -1126,6 +1143,44 @@ public class JobSchedulerServiceTest {
}
/**
+ * Confirm that {@link JobSchedulerService#shouldUseAggressiveBackoff(int, int)} returns true
+ * when the number of abandoned jobs is greater than the threshold.
+ */
+ @Test
+ @EnableFlags(FLAG_HANDLE_ABANDONED_JOBS)
+ @DisableCompatChanges({JobParameters.OVERRIDE_HANDLE_ABANDONED_JOBS})
+ public void testGetRescheduleJobForFailure_EnableFlagDisableCompatCheckAggressiveBackoff() {
+ assertFalse(mService.shouldUseAggressiveBackoff(
+ mService.mConstants.ABANDONED_JOB_TIMEOUTS_BEFORE_AGGRESSIVE_BACKOFF - 1,
+ mSourceUid));
+ assertFalse(mService.shouldUseAggressiveBackoff(
+ mService.mConstants.ABANDONED_JOB_TIMEOUTS_BEFORE_AGGRESSIVE_BACKOFF,
+ mSourceUid));
+ assertTrue(mService.shouldUseAggressiveBackoff(
+ mService.mConstants.ABANDONED_JOB_TIMEOUTS_BEFORE_AGGRESSIVE_BACKOFF + 1,
+ mSourceUid));
+ }
+
+ /**
+ * Confirm that {@link JobSchedulerService#shouldUseAggressiveBackoff(int, int)} returns false
+ * always when the compat change is enabled and the flag is enabled.
+ */
+ @Test
+ @EnableFlags(FLAG_HANDLE_ABANDONED_JOBS)
+ @EnableCompatChanges({JobParameters.OVERRIDE_HANDLE_ABANDONED_JOBS})
+ public void testGetRescheduleJobForFailure_EnableFlagEnableCompatCheckAggressiveBackoff() {
+ assertFalse(mService.shouldUseAggressiveBackoff(
+ mService.mConstants.ABANDONED_JOB_TIMEOUTS_BEFORE_AGGRESSIVE_BACKOFF - 1,
+ mSourceUid));
+ assertFalse(mService.shouldUseAggressiveBackoff(
+ mService.mConstants.ABANDONED_JOB_TIMEOUTS_BEFORE_AGGRESSIVE_BACKOFF,
+ mSourceUid));
+ assertFalse(mService.shouldUseAggressiveBackoff(
+ mService.mConstants.ABANDONED_JOB_TIMEOUTS_BEFORE_AGGRESSIVE_BACKOFF + 1,
+ mSourceUid));
+ }
+
+ /**
* Confirm that
* {@link JobSchedulerService#getRescheduleJobForFailureLocked(JobStatus, int, int)}
* returns a job that is correctly marked as demoted by the user.
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/JobServiceContextTest.java b/services/tests/mockingservicestests/src/com/android/server/job/JobServiceContextTest.java
index 8c66fd0e684a..904545bd3cc3 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/JobServiceContextTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/JobServiceContextTest.java
@@ -31,6 +31,7 @@ import static org.mockito.Mockito.verify;
import android.app.AppGlobals;
import android.app.job.JobParameters;
+import android.compat.testing.PlatformCompatChangeRule;
import android.content.Context;
import android.os.Looper;
import android.os.PowerManager;
@@ -43,11 +44,15 @@ import com.android.internal.app.IBatteryStats;
import com.android.server.job.JobServiceContext.JobCallback;
import com.android.server.job.controllers.JobStatus;
+import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges;
+import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
+
import org.junit.After;
import org.junit.Before;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
+import org.junit.rules.TestRule;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoSession;
@@ -58,11 +63,14 @@ import java.time.Duration;
import java.time.ZoneOffset;
public class JobServiceContextTest {
+ private static final String SOURCE_PACKAGE = "com.android.frameworks.mockingservicestests";
private static final String TAG = JobServiceContextTest.class.getSimpleName();
@ClassRule
public static final SetFlagsRule.ClassRule mSetFlagsClassRule = new SetFlagsRule.ClassRule();
@Rule
public final SetFlagsRule mSetFlagsRule = mSetFlagsClassRule.createSetFlagsRule();
+ @Rule
+ public TestRule compatChangeRule = new PlatformCompatChangeRule();
@Mock
private JobSchedulerService mMockJobSchedulerService;
@Mock
@@ -86,13 +94,13 @@ public class JobServiceContextTest {
private MockitoSession mMockingSession;
private JobServiceContext mJobServiceContext;
private Object mLock;
+ private int mSourceUid;
@Before
public void setUp() throws Exception {
mMockingSession =
mockitoSession()
.initMocks(this)
- .mockStatic(AppGlobals.class)
.strictness(Strictness.LENIENT)
.startMocking();
JobSchedulerService.sElapsedRealtimeClock =
@@ -111,6 +119,7 @@ public class JobServiceContextTest {
mMockLooper);
spyOn(mJobServiceContext);
mJobServiceContext.setJobParamsLockedForTest(mMockJobParameters);
+ mSourceUid = AppGlobals.getPackageManager().getPackageUid(SOURCE_PACKAGE, 0, 0);
}
@After
@@ -130,11 +139,14 @@ public class JobServiceContextTest {
}
/**
- * Test that Abandoned jobs that are timed out are stopped with the correct stop reason
+ * Test that with the compat change disabled and the flag enabled, abandoned
+ * jobs that are timed out are stopped with the correct stop reason and the
+ * job is marked as abandoned.
*/
@Test
@EnableFlags(FLAG_HANDLE_ABANDONED_JOBS)
- public void testJobServiceContext_TimeoutAbandonedJob() {
+ @DisableCompatChanges({JobParameters.OVERRIDE_HANDLE_ABANDONED_JOBS})
+ public void testJobServiceContext_TimeoutAbandonedJob_EnableFlagDisableCompat() {
mJobServiceContext.mVerb = JobServiceContext.VERB_EXECUTING;
ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class);
doNothing().when(mJobServiceContext).sendStopMessageLocked(captor.capture());
@@ -143,6 +155,7 @@ public class JobServiceContextTest {
mJobServiceContext.setPendingStopReasonLockedForTest(JobParameters.STOP_REASON_UNDEFINED);
mJobServiceContext.setRunningJobLockedForTest(mMockJobStatus);
+ doReturn(mSourceUid).when(mMockJobStatus).getSourceUid();
doReturn(true).when(mMockJobStatus).isAbandoned();
mJobServiceContext.mVerb = JobServiceContext.VERB_EXECUTING;
@@ -158,11 +171,14 @@ public class JobServiceContextTest {
}
/**
- * Test that non-abandoned jobs that are timed out are stopped with the correct stop reason
+ * Test that with the compat change enabled and the flag enabled, abandoned
+ * jobs that are timed out are stopped with the correct stop reason and the
+ * job is not marked as abandoned.
*/
@Test
@EnableFlags(FLAG_HANDLE_ABANDONED_JOBS)
- public void testJobServiceContext_TimeoutNoAbandonedJob() {
+ @EnableCompatChanges({JobParameters.OVERRIDE_HANDLE_ABANDONED_JOBS})
+ public void testJobServiceContext_TimeoutAbandonedJob_EnableFlagEnableCompat() {
mJobServiceContext.mVerb = JobServiceContext.VERB_EXECUTING;
ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class);
doNothing().when(mJobServiceContext).sendStopMessageLocked(captor.capture());
@@ -171,7 +187,8 @@ public class JobServiceContextTest {
mJobServiceContext.setPendingStopReasonLockedForTest(JobParameters.STOP_REASON_UNDEFINED);
mJobServiceContext.setRunningJobLockedForTest(mMockJobStatus);
- doReturn(false).when(mMockJobStatus).isAbandoned();
+ doReturn(mSourceUid).when(mMockJobStatus).getSourceUid();
+ doReturn(true).when(mMockJobStatus).isAbandoned();
mJobServiceContext.mVerb = JobServiceContext.VERB_EXECUTING;
mJobServiceContext.handleOpTimeoutLocked();
@@ -186,12 +203,14 @@ public class JobServiceContextTest {
}
/**
- * Test that abandoned jobs that are timed out while the flag is disabled
- * are stopped with the correct stop reason
+ * Test that with the compat change disabled and the flag disabled, abandoned
+ * jobs that are timed out are stopped with the correct stop reason and the
+ * job is not marked as abandoned.
*/
@Test
@DisableFlags(FLAG_HANDLE_ABANDONED_JOBS)
- public void testJobServiceContext_TimeoutAbandonedJob_flagHandleAbandonedJobsDisabled() {
+ @DisableCompatChanges({JobParameters.OVERRIDE_HANDLE_ABANDONED_JOBS})
+ public void testJobServiceContext_TimeoutAbandonedJob_DisableFlagDisableCompat() {
mJobServiceContext.mVerb = JobServiceContext.VERB_EXECUTING;
ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class);
doNothing().when(mJobServiceContext).sendStopMessageLocked(captor.capture());
@@ -201,6 +220,39 @@ public class JobServiceContextTest {
mJobServiceContext.setRunningJobLockedForTest(mMockJobStatus);
doReturn(true).when(mMockJobStatus).isAbandoned();
+ doReturn(mSourceUid).when(mMockJobStatus).getSourceUid();
+ mJobServiceContext.mVerb = JobServiceContext.VERB_EXECUTING;
+
+ synchronized (mLock) {
+ mJobServiceContext.handleOpTimeoutLocked();
+ }
+
+ String stopMessage = captor.getValue();
+ assertEquals("timeout while executing", stopMessage);
+ verify(mMockJobParameters)
+ .setStopReason(
+ JobParameters.STOP_REASON_TIMEOUT,
+ JobParameters.INTERNAL_STOP_REASON_TIMEOUT,
+ "client timed out");
+ }
+
+ /**
+ * Test that non-abandoned jobs that are timed out are stopped with the correct stop reason
+ */
+ @Test
+ @EnableFlags(FLAG_HANDLE_ABANDONED_JOBS)
+ @DisableCompatChanges({JobParameters.OVERRIDE_HANDLE_ABANDONED_JOBS})
+ public void testJobServiceContext_TimeoutNoAbandonedJob() {
+ mJobServiceContext.mVerb = JobServiceContext.VERB_EXECUTING;
+ ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class);
+ synchronized (mLock) {
+ doNothing().when(mJobServiceContext).sendStopMessageLocked(captor.capture());
+ }
+ advanceElapsedClock(30 * MINUTE_IN_MILLIS); // 30 minutes
+ mJobServiceContext.setPendingStopReasonLockedForTest(JobParameters.STOP_REASON_UNDEFINED);
+
+ mJobServiceContext.setRunningJobLockedForTest(mMockJobStatus);
+ doReturn(false).when(mMockJobStatus).isAbandoned();
mJobServiceContext.mVerb = JobServiceContext.VERB_EXECUTING;
mJobServiceContext.handleOpTimeoutLocked();
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java
index b67ec8b2c828..bc81feb3f7c7 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java
@@ -68,6 +68,7 @@ import java.util.List;
@RunWith(AndroidJUnit4.class)
public class BatteryStatsHistoryTest {
private static final String TAG = "BatteryStatsHistoryTest";
+ private static final int MAX_HISTORY_BUFFER_SIZE = 1024;
private final Parcel mHistoryBuffer = Parcel.obtain();
private File mSystemDir;
private File mHistoryDir;
@@ -98,8 +99,9 @@ public class BatteryStatsHistoryTest {
mClock.realtime = 123;
- mHistory = new BatteryStatsHistory(mHistoryBuffer, mSystemDir, 32, 1024,
- mStepDetailsCalculator, mClock, mMonotonicClock, mTracer, mEventLogger);
+ mHistory = new BatteryStatsHistory(mHistoryBuffer, mSystemDir, 32768,
+ MAX_HISTORY_BUFFER_SIZE, mStepDetailsCalculator, mClock, mMonotonicClock, mTracer,
+ mEventLogger);
when(mStepDetailsCalculator.getHistoryStepDetails())
.thenReturn(new BatteryStats.HistoryStepDetails());
@@ -196,12 +198,15 @@ public class BatteryStatsHistoryTest {
}
@Test
- public void testStartNextFile() {
+ public void testStartNextFile() throws Exception {
+ mHistory.forceRecordAllHistory();
+
mClock.realtime = 123;
List<String> fileList = new ArrayList<>();
fileList.add("123.bh");
createActiveFile(mHistory);
+ fillActiveFile(mHistory);
// create file 1 to 31.
for (int i = 1; i < 32; i++) {
@@ -210,6 +215,8 @@ public class BatteryStatsHistoryTest {
mHistory.startNextFile(mClock.realtime);
createActiveFile(mHistory);
+ fillActiveFile(mHistory);
+
verifyFileNames(mHistory, fileList);
verifyActiveFile(mHistory, mClock.realtime + ".bh");
}
@@ -225,6 +232,8 @@ public class BatteryStatsHistoryTest {
verifyFileNames(mHistory, fileList);
verifyActiveFile(mHistory, "32000.bh");
+ fillActiveFile(mHistory);
+
// create file 33
mClock.realtime = 1000 * 33;
mHistory.startNextFile(mClock.realtime);
@@ -401,6 +410,14 @@ public class BatteryStatsHistoryTest {
}
}
+ private void fillActiveFile(BatteryStatsHistory history) {
+ // Create roughly 1K of history
+ int initialSize = history.getHistoryUsedSize();
+ while (history.getHistoryUsedSize() < initialSize + 1000) {
+ history.recordCurrentTimeChange(mClock.realtime, mClock.uptime, 0xFFFFFFFFL);
+ }
+ }
+
@Test
public void recordPowerStats() {
PowerStats.Descriptor descriptor = new PowerStats.Descriptor(42, "foo", 1, null, 0, 2,
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/MockBatteryStatsImpl.java b/services/tests/powerstatstests/src/com/android/server/power/stats/MockBatteryStatsImpl.java
index 9a38209a7d17..4b6fcc39dcef 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/MockBatteryStatsImpl.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/MockBatteryStatsImpl.java
@@ -92,7 +92,8 @@ public class MockBatteryStatsImpl extends BatteryStatsImpl {
powerStatsUidResolver, mock(FrameworkStatsLogger.class),
mock(BatteryStatsHistory.TraceDelegate.class),
mock(BatteryStatsHistory.EventLogger.class));
- setMaxHistoryBuffer(128 * 1024);
+ mConstants.MAX_HISTORY_BUFFER = 128 * 1024;
+ mConstants.onChange();
setExternalStatsSyncLocked(mExternalStatsSync);
informThatAllExternalStatsAreFlushed();
@@ -257,20 +258,6 @@ public class MockBatteryStatsImpl extends BatteryStatsImpl {
}
@GuardedBy("this")
- public MockBatteryStatsImpl setMaxHistoryFiles(int maxHistoryFiles) {
- mConstants.MAX_HISTORY_FILES = maxHistoryFiles;
- mConstants.onChange();
- return this;
- }
-
- @GuardedBy("this")
- public MockBatteryStatsImpl setMaxHistoryBuffer(int maxHistoryBuffer) {
- mConstants.MAX_HISTORY_BUFFER = maxHistoryBuffer;
- mConstants.onChange();
- return this;
- }
-
- @GuardedBy("this")
public MockBatteryStatsImpl setPerUidModemModel(int perUidModemModel) {
mConstants.PER_UID_MODEM_MODEL = perUidModemModel;
mConstants.onChange();
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/EventConditionProviderTest.java b/services/tests/uiservicestests/src/com/android/server/notification/EventConditionProviderTest.java
index fa1372d9f4ef..87b9154cfb4d 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/EventConditionProviderTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/EventConditionProviderTest.java
@@ -88,6 +88,8 @@ public class EventConditionProviderTest extends UiServiceTestCase {
mService.mContext = this.getContext();
mContext.addMockSystemService(UserManager.class, mUserManager);
+ when(mUserManager.getProfiles(eq(UserHandle.USER_SYSTEM))).thenReturn(
+ List.of(new UserInfo(UserHandle.USER_SYSTEM, "USER_SYSTEM", 0)));
when(mUserManager.getProfiles(eq(mUserId))).thenReturn(
List.of(new UserInfo(mUserId, "mUserId", 0)));
diff --git a/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/SurfaceControlPictureProfileTest.java b/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/SurfaceControlPictureProfileTest.java
index 135f7102b8a9..9ac08ed449fd 100644
--- a/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/SurfaceControlPictureProfileTest.java
+++ b/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/SurfaceControlPictureProfileTest.java
@@ -203,12 +203,10 @@ public class SurfaceControlPictureProfileTest {
transaction
.setBuffer(mSurfaceControls[i], buffer)
.setPictureProfileHandle(mSurfaceControls[i], handle)
- .setContentPriority(mSurfaceControls[i], 0);
+ .setContentPriority(mSurfaceControls[i], 1);
}
- // Make the first layer low priority (high value)
- transaction.setContentPriority(mSurfaceControls[0], 2);
- // Make the last layer higher priority (lower value)
- transaction.setContentPriority(mSurfaceControls[maxPictureProfiles], 1);
+ transaction.setContentPriority(mSurfaceControls[0], -1);
+ transaction.setContentPriority(mSurfaceControls[maxPictureProfiles], 0);
transaction.apply();
pictures = pollMs(picturesQueue, 200);
@@ -219,8 +217,8 @@ public class SurfaceControlPictureProfileTest {
assertThat(stream(pictures).map(picture -> picture.getPictureProfileHandle().getId()))
.containsExactlyElementsIn(toIterableRange(2, maxPictureProfiles + 1));
- // Change priority and ensure that the first layer gets access
- new SurfaceControl.Transaction().setContentPriority(mSurfaceControls[0], 0).apply();
+ // Elevate priority for the first layer and verify it gets to use a profile
+ new SurfaceControl.Transaction().setContentPriority(mSurfaceControls[0], 2).apply();
pictures = pollMs(picturesQueue, 200);
assertThat(pictures).isNotNull();
// Expect all but the last layer to be listed as an active picture