summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--AconfigFlags.bp7
-rw-r--r--core/api/current.txt1
-rw-r--r--core/java/android/app/INotificationManager.aidl1
-rw-r--r--core/java/android/app/contextualsearch/flags.aconfig7
-rw-r--r--core/java/android/content/pm/parsing/ApkLite.java23
-rw-r--r--core/java/android/content/pm/parsing/ApkLiteParseUtils.java8
-rw-r--r--core/java/android/content/pm/parsing/FrameworkParsingPackageUtils.java30
-rw-r--r--core/java/android/content/pm/parsing/PackageLite.java18
-rw-r--r--core/java/android/hardware/biometrics/BiometricConstants.java20
-rw-r--r--core/java/android/hardware/camera2/impl/CameraDeviceImpl.java8
-rw-r--r--core/java/android/os/ITradeInMode.aidl33
-rw-r--r--core/java/android/os/PowerManager.java11
-rw-r--r--core/java/android/os/TestLooperManager.java3
-rw-r--r--core/java/android/os/UserManager.java46
-rw-r--r--core/java/android/widget/RemoteViews.java10
-rw-r--r--core/java/android/window/DesktopModeFlags.java2
-rw-r--r--core/java/android/window/flags/lse_desktop_experience.aconfig10
-rw-r--r--core/java/android/window/flags/windowing_sdk.aconfig11
-rw-r--r--core/java/com/android/internal/content/NativeLibraryHelper.java25
-rw-r--r--core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java16
-rw-r--r--core/jni/com_android_internal_content_NativeLibraryHelper.cpp24
-rw-r--r--core/proto/android/widget/remoteviews.proto1
-rw-r--r--core/res/res/values/attrs_manifest.xml4
-rw-r--r--core/res/res/values/config.xml3
-rw-r--r--core/res/res/values/public-final.xml5
-rw-r--r--core/res/res/values/symbols.xml1
-rw-r--r--data/etc/privapp-permissions-platform.xml1
-rw-r--r--libs/WindowManager/Shell/res/values/dimen.xml2
-rw-r--r--libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java46
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarDragListener.kt39
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java22
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeShellCommandHandler.kt9
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt125
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt270
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt13
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/DeskTransition.kt12
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/DesksOrganizer.kt3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/DesksTransitionObserver.kt37
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/RootTaskDesksOrganizer.kt7
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java9
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java73
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitMultiDisplayProvider.java (renamed from libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIShellTestCase.java)18
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java13
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java2
-rw-r--r--libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/AndroidManifest.xml2
-rw-r--r--libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/DesktopModeFlickerScenarios.kt24
-rw-r--r--libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/OpenAppWithExternalDisplayConnected.kt49
-rw-r--r--libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/EnterDesktopWithDrag.kt2
-rw-r--r--libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/EnterDesktopWithDragExistingWindows.kt2
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java3
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUILayoutTest.java3
-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.java3
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/LetterboxEduDialogLayoutTest.java3
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/LetterboxEduWindowManagerTest.java3
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/ReachabilityEduLayoutTest.java3
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/ReachabilityEduWindowManagerTest.java3
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/RestartDialogLayoutTest.java3
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/RestartDialogWindowManagerTest.java3
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsLayoutTest.java3
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManagerTest.java3
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopActivityOrientationChangeHandlerTest.kt2
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandlerTest.kt2
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt68
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt580
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt56
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/multidesks/DesksTransitionObserverTest.kt60
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/multidesks/RootTaskDesksOrganizerTest.kt29
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropControllerTest.java8
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeCompatPolicyTest.kt4
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatusTest.kt66
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelAppHandleOnlyTest.kt2
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt30
-rw-r--r--libs/androidfw/Android.bp1
-rw-r--r--libs/androidfw/LocaleDataLookup.cpp7
-rw-r--r--libs/androidfw/tests/LocaleDataLookup_bench.cpp57
-rw-r--r--media/java/android/media/AudioManager.java17
-rw-r--r--media/java/android/media/IAudioService.aidl4
-rw-r--r--media/java/android/media/flags/media_better_together.aconfig10
-rw-r--r--nfc-non-updatable/flags/flags.aconfig10
-rw-r--r--nfc-non-updatable/java/android/nfc/cardemulation/ApduServiceInfo.java6
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java24
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java11
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/media/ManagerInfoMediaManager.java6
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java11
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/media/NoOpInfoMediaManager.java6
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/media/RouterInfoMediaManager.java38
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java25
-rw-r--r--packages/Shell/AndroidManifest.xml3
-rw-r--r--packages/SystemUI/Android.bp2
-rw-r--r--packages/SystemUI/aconfig/accessibility.aconfig10
-rw-r--r--packages/SystemUI/aconfig/systemui.aconfig7
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt118
-rw-r--r--packages/SystemUI/compose/core/src/com/android/compose/animation/ExpandableController.kt2
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt3
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt38
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeOverlay.kt26
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt66
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeOverlay.kt48
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/ribbon/ui/composable/Ribbon.kt26
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt1
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt51
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt213
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt47
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/VariableDayDate.kt20
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/ui/viewmodel/BrightnessSliderViewModelTest.kt32
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalSceneRepositoryImplTest.kt (renamed from packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalRepositoryImplTest.kt)60
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/complication/DreamClockTimeComplicationTest.java7
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt46
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java127
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayContentViewModelTest.kt12
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsContainerViewModelTest.kt82
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModelTest.kt18
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/recordissue/IssueRecordingServiceSessionTest.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt113
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndCastScreenToOtherDeviceDialogDelegateTest.kt39
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndGenericCastToOtherDeviceDialogDelegateTest.kt39
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/screenrecord/ui/view/EndScreenRecordingDialogDelegateTest.kt37
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/view/EndGenericShareToAppDialogDelegateTest.kt56
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/view/EndShareScreenToAppDialogDelegateTest.kt42
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModelTest.kt27
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImplTest.kt34
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractorImplTest.kt46
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt35
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImplTest.kt36
-rw-r--r--packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java7
-rw-r--r--packages/SystemUI/pods/com/android/systemui/util/settings/UserSettingsProxy.kt50
-rw-r--r--packages/SystemUI/res/values-sw600dp/config.xml6
-rw-r--r--packages/SystemUI/res/values/dimens.xml1
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationAnimationController.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java50
-rw-r--r--packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/AudioSharingDeviceItemActionInteractorImpl.kt23
-rw-r--r--packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothDetailsContentManager.kt442
-rw-r--r--packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegate.kt433
-rw-r--r--packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModel.kt143
-rw-r--r--packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemActionInteractor.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/brightness/ui/compose/BrightnessSlider.kt275
-rw-r--r--packages/SystemUI/src/com/android/systemui/brightness/ui/viewmodel/BrightnessSliderViewModel.kt61
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSceneRepository.kt44
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractor.kt23
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/complication/DreamClockTimeComplication.java15
-rw-r--r--packages/SystemUI/src/com/android/systemui/complication/dagger/DreamClockTimeComplicationComponent.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt24
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardJankBinder.kt96
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt35
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodPromotedNotificationSection.kt15
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardJankViewModel.kt56
-rw-r--r--packages/SystemUI/src/com/android/systemui/lowlightclock/AmbientLightModeMonitor.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/lowlightclock/dagger/LowLightModule.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt14
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java58
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dialog/MediaSwitchingController.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayContentViewModel.kt17
-rw-r--r--packages/SystemUI/src/com/android/systemui/power/domain/interactor/PowerInteractor.kt28
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/EditTile.kt59
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayout.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/selection/MutableSelectionState.kt13
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsContainerViewModel.kt19
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModel.kt35
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImpl.kt62
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModel.kt50
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java63
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndCastScreenToOtherDeviceDialogDelegate.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndGenericCastToOtherDeviceDialogDelegate.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/domain/model/MediaProjectionStopDialogModel.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/ui/view/EndScreenRecordingDialogDelegate.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/chips/sharetoapp/ui/view/EndGenericShareToAppDialogDelegate.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/chips/sharetoapp/ui/view/EndShareScreenToAppDialogDelegate.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/ChipContent.kt155
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/ChipText.kt114
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/OngoingActivityChip.kt45
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/AODPromotedNotification.kt107
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractor.kt22
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationLogger.kt42
-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/notification/promoted/ui/viewmodel/PromotedNotificationViewModel.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterInternal.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterInternalImpl.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImpl.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModel.kt30
-rw-r--r--packages/SystemUI/tests/goldens/brightnessSlider_iconAlphaChanges.json168
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java32
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java79
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/animation/ActivityTransitionAnimatorTest.kt269
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothDetailsContentManagerTest.kt461
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegateTest.kt348
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModelTest.kt86
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/brightness/ui/compose/BrightnessSliderMotionTest.kt136
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/lowlightclock/AmbientLightModeMonitorTest.kt7
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaSwitchingControllerTest.java7
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/DragAndDropTest.kt1
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/EditModeTest.kt199
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/ResizingTest.kt1
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt3
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/brightness/ui/viewmodel/BrightnessSliderViewModelKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalSceneRepository.kt7
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/plugins/statusbar/StatusBarStateControllerKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsContainerViewModelKosmos.kt4
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModelKosmos.kt3
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelKosmos.kt10
-rw-r--r--ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java35
-rw-r--r--services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerService.java38
-rw-r--r--services/core/java/com/android/server/BootReceiver.java7
-rw-r--r--services/core/java/com/android/server/TradeInModeService.java115
-rw-r--r--services/core/java/com/android/server/am/SettingsToPropertiesMapper.java3
-rw-r--r--services/core/java/com/android/server/audio/AudioManagerShellCommand.java18
-rw-r--r--services/core/java/com/android/server/audio/AudioPolicyFacade.java1
-rw-r--r--services/core/java/com/android/server/audio/AudioService.java32
-rw-r--r--services/core/java/com/android/server/audio/DefaultAudioPolicyFacade.java10
-rw-r--r--services/core/java/com/android/server/media/SystemMediaRoute2Provider2.java60
-rw-r--r--services/core/java/com/android/server/notification/NotificationManagerService.java84
-rw-r--r--services/core/java/com/android/server/pm/UserManagerInternal.java6
-rw-r--r--services/core/java/com/android/server/pm/UserManagerService.java54
-rw-r--r--services/core/java/com/android/server/pm/UserRestrictionsUtils.java3
-rw-r--r--services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java5
-rw-r--r--services/core/java/com/android/server/pm/permission/PermissionManagerService.java3
-rw-r--r--services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java3
-rw-r--r--services/core/java/com/android/server/pm/permission/PermissionManagerServiceInterface.java2
-rw-r--r--services/core/java/com/android/server/pm/permission/PermissionManagerServiceLoggingDecorator.java8
-rw-r--r--services/core/java/com/android/server/pm/permission/PermissionManagerServiceTestingShim.java6
-rw-r--r--services/core/java/com/android/server/pm/permission/PermissionManagerServiceTracingDecorator.java6
-rw-r--r--services/core/java/com/android/server/power/PowerManagerService.java17
-rw-r--r--services/core/java/com/android/server/power/feature/PowerManagerFlags.java12
-rw-r--r--services/core/java/com/android/server/power/feature/power_flags.aconfig11
-rw-r--r--services/core/java/com/android/server/power/stats/WakelockStatsFrameworkEvents.java12
-rw-r--r--services/core/java/com/android/server/wm/ActivityRecord.java1
-rw-r--r--services/core/java/com/android/server/wm/ActivityTaskSupervisor.java84
-rw-r--r--services/core/java/com/android/server/wm/AppTransitionController.java12
-rw-r--r--services/core/java/com/android/server/wm/DesktopModeHelper.java3
-rw-r--r--services/core/java/com/android/server/wm/DisplayContent.java17
-rw-r--r--services/core/java/com/android/server/wm/SurfaceAnimator.java28
-rw-r--r--services/core/java/com/android/server/wm/SurfaceFreezer.java303
-rw-r--r--services/core/java/com/android/server/wm/Task.java30
-rw-r--r--services/core/java/com/android/server/wm/TaskFragment.java28
-rw-r--r--services/core/java/com/android/server/wm/Transition.java2
-rw-r--r--services/core/java/com/android/server/wm/WindowContainer.java102
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerInternal.java13
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerService.java31
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java34
-rw-r--r--services/permission/java/com/android/server/permission/access/permission/PermissionService.kt16
-rw-r--r--services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java41
-rw-r--r--services/tests/powerstatstests/src/com/android/server/power/stats/WakelockStatsFrameworkEventsTest.java18
-rw-r--r--services/tests/servicestests/src/com/android/server/audio/VolumeHelperTest.java52
-rw-r--r--services/tests/servicestests/src/com/android/server/hdmi/ActiveSourceActionTest.java6
-rw-r--r--services/tests/servicestests/src/com/android/server/hdmi/ArcInitiationActionFromAvrTest.java6
-rw-r--r--services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java6
-rw-r--r--services/tests/servicestests/src/com/android/server/hdmi/DevicePowerStatusActionTest.java6
-rw-r--r--services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromPlaybackTest.java6
-rw-r--r--services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromTvTest.java6
-rw-r--r--services/tests/servicestests/src/com/android/server/hdmi/HdmiCecAtomLoggingTest.java6
-rw-r--r--services/tests/servicestests/src/com/android/server/hdmi/HdmiCecAtomLoggingTvTest.java6
-rw-r--r--services/tests/servicestests/src/com/android/server/hdmi/HdmiCecConfigTest.java6
-rw-r--r--services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java7
-rw-r--r--services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java7
-rw-r--r--services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java8
-rw-r--r--services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageValidatorTest.java7
-rw-r--r--services/tests/servicestests/src/com/android/server/hdmi/HdmiCecNetworkTest.java6
-rw-r--r--services/tests/servicestests/src/com/android/server/hdmi/HdmiCecPowerStatusControllerTest.java6
-rw-r--r--services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java5
-rw-r--r--services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTvTest.java6
-rw-r--r--services/tests/servicestests/src/com/android/server/hdmi/HdmiEarcLocalDeviceTxTest.java5
-rw-r--r--services/tests/servicestests/src/com/android/server/hdmi/PowerStatusMonitorActionTest.java6
-rw-r--r--services/tests/servicestests/src/com/android/server/hdmi/RequestSadActionTest.java7
-rw-r--r--services/tests/servicestests/src/com/android/server/hdmi/ResendCecCommandActionPlaybackTest.java7
-rw-r--r--services/tests/servicestests/src/com/android/server/hdmi/ResendCecCommandActionTvTest.java7
-rw-r--r--services/tests/servicestests/src/com/android/server/hdmi/RoutingControlActionTest.java7
-rw-r--r--services/tests/servicestests/src/com/android/server/hdmi/SetAudioVolumeLevelDiscoveryActionTest.java5
-rw-r--r--services/tests/servicestests/src/com/android/server/hdmi/SystemAudioAutoInitiationActionTest.java6
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java35
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java6
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java98
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/AppChangeTransitionTests.java175
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java2
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/CameraCompatFreeformPolicyTests.java68
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java2
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java40
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java166
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java17
-rw-r--r--services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java3
-rw-r--r--tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt6
-rw-r--r--tests/utils/testutils/java/android/os/test/TestLooper.java170
-rwxr-xr-xtools/localedata/extract_icu_data.py9
298 files changed, 7212 insertions, 3683 deletions
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index 834398e5c2c2..ac756ea1d624 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -1573,6 +1573,13 @@ java_aconfig_library {
defaults: ["framework-minus-apex-aconfig-java-defaults"],
}
+java_aconfig_library {
+ name: "power_flags_lib_host",
+ aconfig_declarations: "power_flags",
+ host_supported: true,
+ defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
// Content
aconfig_declarations {
name: "android.content.flags-aconfig",
diff --git a/core/api/current.txt b/core/api/current.txt
index 21929658cbb9..dd606774b770 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -1207,7 +1207,6 @@ package android {
field public static final int minResizeHeight = 16843670; // 0x1010396
field public static final int minResizeWidth = 16843669; // 0x1010395
field public static final int minSdkVersion = 16843276; // 0x101020c
- field @FlaggedApi("android.sdk.major_minor_versioning_scheme") public static final int minSdkVersionFull = 16844461; // 0x10106ad
field public static final int minWidth = 16843071; // 0x101013f
field public static final int minimumHorizontalAngle = 16843901; // 0x101047d
field public static final int minimumVerticalAngle = 16843902; // 0x101047e
diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index 3fb08224b9db..bc01f934e701 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -270,6 +270,7 @@ interface INotificationManager
int[] getAllowedAdjustmentKeyTypes();
void setAssistantAdjustmentKeyTypeState(int type, boolean enabled);
+ String[] getAdjustmentDeniedPackages(String key);
boolean isAdjustmentSupportedForPackage(String key, String pkg);
void setAdjustmentSupportedForPackage(String key, String pkg, boolean enabled);
diff --git a/core/java/android/app/contextualsearch/flags.aconfig b/core/java/android/app/contextualsearch/flags.aconfig
index c19921dcdc61..d81ec1e8b883 100644
--- a/core/java/android/app/contextualsearch/flags.aconfig
+++ b/core/java/android/app/contextualsearch/flags.aconfig
@@ -27,7 +27,10 @@ flag {
name: "contextual_search_window_layer"
namespace: "sysui_integrations"
description: "Identify live contextual search UI to exclude from contextual search screenshot."
- bug: "372510690"
+ bug: "390176823"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
}
flag {
@@ -35,4 +38,4 @@ flag {
namespace: "sysui_integrations"
description: "Add audio playing status to the contextual search invocation intent."
bug: "372935419"
-} \ No newline at end of file
+}
diff --git a/core/java/android/content/pm/parsing/ApkLite.java b/core/java/android/content/pm/parsing/ApkLite.java
index 1d8209da6559..b8cf70960ea3 100644
--- a/core/java/android/content/pm/parsing/ApkLite.java
+++ b/core/java/android/content/pm/parsing/ApkLite.java
@@ -18,6 +18,7 @@ package android.content.pm.parsing;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.content.pm.ApplicationInfo;
import android.content.pm.ArchivedPackageParcel;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
@@ -184,6 +185,11 @@ public class ApkLite {
*/
private final @Nullable ArchivedPackageParcel mArchivedPackage;
+ /**
+ * pageSizeCompat info from manifest file
+ */
+ private final int mPageSizeCompat;
+
public ApkLite(String path, String packageName, String splitName, boolean isFeatureSplit,
String configForSplit, String usesSplitName, boolean isSplitRequired, int versionCode,
int versionCodeMajor, int revisionCode, int installLocation,
@@ -200,7 +206,8 @@ public class ApkLite {
List<String> usesStaticLibraries, long[] usesStaticLibrariesVersionsMajor,
String[][] usesStaticLibrariesCertDigests,
boolean updatableSystem,
- String emergencyInstaller, List<SharedLibraryInfo> declaredLibraries) {
+ String emergencyInstaller, List<SharedLibraryInfo> declaredLibraries,
+ int pageSizeCompat) {
mPath = path;
mPackageName = packageName;
mSplitName = splitName;
@@ -245,6 +252,7 @@ public class ApkLite {
mEmergencyInstaller = emergencyInstaller;
mArchivedPackage = null;
mDeclaredLibraries = declaredLibraries;
+ mPageSizeCompat = pageSizeCompat;
}
public ApkLite(String path, ArchivedPackageParcel archivedPackage) {
@@ -292,6 +300,7 @@ public class ApkLite {
mEmergencyInstaller = null;
mArchivedPackage = archivedPackage;
mDeclaredLibraries = null;
+ mPageSizeCompat = ApplicationInfo.PAGE_SIZE_APP_COMPAT_FLAG_UNDEFINED;
}
/**
@@ -676,11 +685,19 @@ public class ApkLite {
return mArchivedPackage;
}
+ /**
+ * pageSizeCompat info from manifest file
+ */
+ @DataClass.Generated.Member
+ public int getPageSizeCompat() {
+ return mPageSizeCompat;
+ }
+
@DataClass.Generated(
- time = 1731589363302L,
+ time = 1738189581427L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/core/java/android/content/pm/parsing/ApkLite.java",
- inputSignatures = "private final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.NonNull java.lang.String mPath\nprivate final @android.annotation.Nullable java.lang.String mSplitName\nprivate final @android.annotation.Nullable java.lang.String mUsesSplitName\nprivate final @android.annotation.Nullable java.lang.String mConfigForSplit\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String> mRequiredSplitTypes\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String> mSplitTypes\nprivate final int mVersionCodeMajor\nprivate final int mVersionCode\nprivate final int mRevisionCode\nprivate final int mInstallLocation\nprivate final int mMinSdkVersion\nprivate final int mTargetSdkVersion\nprivate final @android.annotation.NonNull android.content.pm.VerifierInfo[] mVerifiers\nprivate final @android.annotation.NonNull android.content.pm.SigningDetails mSigningDetails\nprivate final boolean mFeatureSplit\nprivate final boolean mIsolatedSplits\nprivate final boolean mSplitRequired\nprivate final boolean mCoreApp\nprivate final boolean mDebuggable\nprivate final boolean mProfileableByShell\nprivate final boolean mMultiArch\nprivate final boolean mUse32bitAbi\nprivate final boolean mExtractNativeLibs\nprivate final boolean mUseEmbeddedDex\nprivate final @android.annotation.Nullable java.lang.String mTargetPackageName\nprivate final boolean mOverlayIsStatic\nprivate final int mOverlayPriority\nprivate final @android.annotation.Nullable java.lang.String mRequiredSystemPropertyName\nprivate final @android.annotation.Nullable java.lang.String mRequiredSystemPropertyValue\nprivate final int mRollbackDataPolicy\nprivate final boolean mHasDeviceAdminReceiver\nprivate final boolean mIsSdkLibrary\nprivate final boolean mIsStaticLibrary\nprivate final @android.annotation.NonNull java.util.List<java.lang.String> mUsesSdkLibraries\nprivate final @android.annotation.Nullable long[] mUsesSdkLibrariesVersionsMajor\nprivate final @android.annotation.Nullable java.lang.String[][] mUsesSdkLibrariesCertDigests\nprivate final @android.annotation.NonNull java.util.List<java.lang.String> mUsesStaticLibraries\nprivate final @android.annotation.Nullable long[] mUsesStaticLibrariesVersions\nprivate final @android.annotation.Nullable java.lang.String[][] mUsesStaticLibrariesCertDigests\nprivate final boolean mUpdatableSystem\nprivate final @android.annotation.Nullable java.lang.String mEmergencyInstaller\nprivate final @android.annotation.NonNull java.util.List<android.content.pm.SharedLibraryInfo> mDeclaredLibraries\nprivate final @android.annotation.Nullable android.content.pm.ArchivedPackageParcel mArchivedPackage\npublic long getLongVersionCode()\nprivate boolean hasAnyRequiredSplitTypes()\nclass ApkLite extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false, genConstDefs=false)")
+ inputSignatures = "private final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.NonNull java.lang.String mPath\nprivate final @android.annotation.Nullable java.lang.String mSplitName\nprivate final @android.annotation.Nullable java.lang.String mUsesSplitName\nprivate final @android.annotation.Nullable java.lang.String mConfigForSplit\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String> mRequiredSplitTypes\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String> mSplitTypes\nprivate final int mVersionCodeMajor\nprivate final int mVersionCode\nprivate final int mRevisionCode\nprivate final int mInstallLocation\nprivate final int mMinSdkVersion\nprivate final int mTargetSdkVersion\nprivate final @android.annotation.NonNull android.content.pm.VerifierInfo[] mVerifiers\nprivate final @android.annotation.NonNull android.content.pm.SigningDetails mSigningDetails\nprivate final boolean mFeatureSplit\nprivate final boolean mIsolatedSplits\nprivate final boolean mSplitRequired\nprivate final boolean mCoreApp\nprivate final boolean mDebuggable\nprivate final boolean mProfileableByShell\nprivate final boolean mMultiArch\nprivate final boolean mUse32bitAbi\nprivate final boolean mExtractNativeLibs\nprivate final boolean mUseEmbeddedDex\nprivate final @android.annotation.Nullable java.lang.String mTargetPackageName\nprivate final boolean mOverlayIsStatic\nprivate final int mOverlayPriority\nprivate final @android.annotation.Nullable java.lang.String mRequiredSystemPropertyName\nprivate final @android.annotation.Nullable java.lang.String mRequiredSystemPropertyValue\nprivate final int mRollbackDataPolicy\nprivate final boolean mHasDeviceAdminReceiver\nprivate final boolean mIsSdkLibrary\nprivate final boolean mIsStaticLibrary\nprivate final @android.annotation.NonNull java.util.List<java.lang.String> mUsesSdkLibraries\nprivate final @android.annotation.Nullable long[] mUsesSdkLibrariesVersionsMajor\nprivate final @android.annotation.Nullable java.lang.String[][] mUsesSdkLibrariesCertDigests\nprivate final @android.annotation.NonNull java.util.List<java.lang.String> mUsesStaticLibraries\nprivate final @android.annotation.Nullable long[] mUsesStaticLibrariesVersions\nprivate final @android.annotation.Nullable java.lang.String[][] mUsesStaticLibrariesCertDigests\nprivate final boolean mUpdatableSystem\nprivate final @android.annotation.Nullable java.lang.String mEmergencyInstaller\nprivate final @android.annotation.NonNull java.util.List<android.content.pm.SharedLibraryInfo> mDeclaredLibraries\nprivate final @android.annotation.Nullable android.content.pm.ArchivedPackageParcel mArchivedPackage\nprivate final int mPageSizeCompat\npublic long getLongVersionCode()\nprivate boolean hasAnyRequiredSplitTypes()\nclass ApkLite extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false, genConstDefs=false)")
@Deprecated
private void __metadata() {}
diff --git a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
index 71d0a04760ac..26252a990676 100644
--- a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
+++ b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
@@ -22,6 +22,7 @@ import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER;
import android.annotation.NonNull;
import android.app.admin.DeviceAdminReceiver;
+import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.SharedLibraryInfo;
@@ -459,6 +460,7 @@ public class ApkLiteParseUtils {
boolean overlayIsStatic = false;
int overlayPriority = 0;
int rollbackDataPolicy = 0;
+ int pageSizeCompat = ApplicationInfo.PAGE_SIZE_APP_COMPAT_FLAG_UNDEFINED;
String requiredSystemPropertyName = null;
String requiredSystemPropertyValue = null;
@@ -516,6 +518,10 @@ public class ApkLiteParseUtils {
boolean hasBindDeviceAdminPermission =
android.Manifest.permission.BIND_DEVICE_ADMIN.equals(permission);
+ pageSizeCompat = parser.getAttributeIntValue(ANDROID_RES_NAMESPACE,
+ "pageSizeCompat",
+ ApplicationInfo.PAGE_SIZE_APP_COMPAT_FLAG_UNDEFINED);
+
final int innerDepth = parser.getDepth();
int innerType;
while ((innerType = parser.next()) != XmlPullParser.END_DOCUMENT
@@ -817,7 +823,7 @@ public class ApkLiteParseUtils {
usesSdkLibrariesVersionsMajor, usesSdkLibrariesCertDigests, isStaticLibrary,
usesStaticLibraries, usesStaticLibrariesVersions,
usesStaticLibrariesCertDigests, updatableSystem, emergencyInstaller,
- declaredLibraries));
+ declaredLibraries, pageSizeCompat));
}
private static ParseResult<String[]> parseAdditionalCertificates(ParseInput input,
diff --git a/core/java/android/content/pm/parsing/FrameworkParsingPackageUtils.java b/core/java/android/content/pm/parsing/FrameworkParsingPackageUtils.java
index d2d3a6840acc..c7403c0ea98c 100644
--- a/core/java/android/content/pm/parsing/FrameworkParsingPackageUtils.java
+++ b/core/java/android/content/pm/parsing/FrameworkParsingPackageUtils.java
@@ -339,36 +339,6 @@ public class FrameworkParsingPackageUtils {
}
/**
- * Check if a package is compatible with this platform with regards to its
- * its minSdkVersionFull.
- *
- * @param minSdkVersionFullString A string representation of a major.minor version,
- * e.g. "12.34"
- * @param platformMinSdkVersionFull The major and minor version of the platform, i.e. the value
- * of Build.VERSION.SDK_INT_FULL
- * @param input A ParseInput object to report success or failure
- */
- public static ParseResult<Void> verifyMinSdkVersionFull(@NonNull String minSdkVersionFullString,
- int platformMinSdkVersionFull, @NonNull ParseInput input) {
- int minSdkVersionFull;
- try {
- minSdkVersionFull = Build.parseFullVersion(minSdkVersionFullString);
- } catch (IllegalStateException e) {
- return input.error(PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED,
- e.getMessage());
- }
- if (minSdkVersionFull <= platformMinSdkVersionFull) {
- return input.success(null);
- }
- return input.error(PackageManager.INSTALL_FAILED_OLDER_SDK,
- "Requires newer sdk version "
- + Build.fullVersionToString(minSdkVersionFull)
- + " (current version is "
- + Build.fullVersionToString(platformMinSdkVersionFull)
- + ")");
- }
-
- /**
* Computes the targetSdkVersion to use at runtime. If the package is not compatible with this
* platform, populates {@code outError[0]} with an error message.
* <p>
diff --git a/core/java/android/content/pm/parsing/PackageLite.java b/core/java/android/content/pm/parsing/PackageLite.java
index 0e11eecfc7ec..43a3645f34cd 100644
--- a/core/java/android/content/pm/parsing/PackageLite.java
+++ b/core/java/android/content/pm/parsing/PackageLite.java
@@ -138,6 +138,11 @@ public class PackageLite {
*/
private final @Nullable ArchivedPackageParcel mArchivedPackage;
+ /**
+ * pageSizeCompat info from manifest file
+ */
+ private final int mPageSizeCompat;
+
public PackageLite(String path, String baseApkPath, ApkLite baseApk,
String[] splitNames, boolean[] isFeatureSplits, String[] usesSplitNames,
String[] configForSplit, String[] splitApkPaths, int[] splitRevisionCodes,
@@ -182,6 +187,7 @@ public class PackageLite {
mTargetSdk = targetSdk;
mDeclaredLibraries = baseApk.getDeclaredLibraries();
mArchivedPackage = baseApk.getArchivedPackage();
+ mPageSizeCompat = baseApk.getPageSizeCompat();
}
/**
@@ -511,11 +517,19 @@ public class PackageLite {
return mArchivedPackage;
}
+ /**
+ * pageSizeCompat info from manifest file
+ */
+ @DataClass.Generated.Member
+ public int getPageSizeCompat() {
+ return mPageSizeCompat;
+ }
+
@DataClass.Generated(
- time = 1731591578587L,
+ time = 1738193799106L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/core/java/android/content/pm/parsing/PackageLite.java",
- inputSignatures = "private final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.NonNull java.lang.String mPath\nprivate final @android.annotation.NonNull java.lang.String mBaseApkPath\nprivate final @android.annotation.Nullable java.lang.String[] mSplitApkPaths\nprivate final @android.annotation.Nullable java.lang.String[] mSplitNames\nprivate final @android.annotation.Nullable java.lang.String[] mUsesSplitNames\nprivate final @android.annotation.Nullable java.lang.String[] mConfigForSplit\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String> mBaseRequiredSplitTypes\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String>[] mRequiredSplitTypes\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String>[] mSplitTypes\nprivate final int mVersionCodeMajor\nprivate final int mVersionCode\nprivate final int mTargetSdk\nprivate final int mBaseRevisionCode\nprivate final @android.annotation.Nullable int[] mSplitRevisionCodes\nprivate final int mInstallLocation\nprivate final @android.annotation.NonNull android.content.pm.VerifierInfo[] mVerifiers\nprivate final @android.annotation.NonNull android.content.pm.SigningDetails mSigningDetails\nprivate final @android.annotation.Nullable boolean[] mIsFeatureSplits\nprivate final boolean mIsolatedSplits\nprivate final boolean mSplitRequired\nprivate final boolean mCoreApp\nprivate final boolean mDebuggable\nprivate final boolean mMultiArch\nprivate final boolean mUse32bitAbi\nprivate final boolean mExtractNativeLibs\nprivate final boolean mProfileableByShell\nprivate final boolean mUseEmbeddedDex\nprivate final boolean mIsSdkLibrary\nprivate final boolean mIsStaticLibrary\nprivate final @android.annotation.NonNull java.util.List<java.lang.String> mUsesSdkLibraries\nprivate final @android.annotation.Nullable long[] mUsesSdkLibrariesVersionsMajor\nprivate final @android.annotation.Nullable java.lang.String[][] mUsesSdkLibrariesCertDigests\nprivate final @android.annotation.NonNull java.util.List<java.lang.String> mUsesStaticLibraries\nprivate final @android.annotation.Nullable long[] mUsesStaticLibrariesVersions\nprivate final @android.annotation.Nullable java.lang.String[][] mUsesStaticLibrariesCertDigests\nprivate final @android.annotation.NonNull java.util.List<android.content.pm.SharedLibraryInfo> mDeclaredLibraries\nprivate final @android.annotation.Nullable android.content.pm.ArchivedPackageParcel mArchivedPackage\npublic java.util.List<java.lang.String> getAllApkPaths()\npublic long getLongVersionCode()\nprivate boolean hasAnyRequiredSplitTypes()\nclass PackageLite extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false, genConstDefs=false)")
+ inputSignatures = "private final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.NonNull java.lang.String mPath\nprivate final @android.annotation.NonNull java.lang.String mBaseApkPath\nprivate final @android.annotation.Nullable java.lang.String[] mSplitApkPaths\nprivate final @android.annotation.Nullable java.lang.String[] mSplitNames\nprivate final @android.annotation.Nullable java.lang.String[] mUsesSplitNames\nprivate final @android.annotation.Nullable java.lang.String[] mConfigForSplit\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String> mBaseRequiredSplitTypes\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String>[] mRequiredSplitTypes\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String>[] mSplitTypes\nprivate final int mVersionCodeMajor\nprivate final int mVersionCode\nprivate final int mTargetSdk\nprivate final int mBaseRevisionCode\nprivate final @android.annotation.Nullable int[] mSplitRevisionCodes\nprivate final int mInstallLocation\nprivate final @android.annotation.NonNull android.content.pm.VerifierInfo[] mVerifiers\nprivate final @android.annotation.NonNull android.content.pm.SigningDetails mSigningDetails\nprivate final @android.annotation.Nullable boolean[] mIsFeatureSplits\nprivate final boolean mIsolatedSplits\nprivate final boolean mSplitRequired\nprivate final boolean mCoreApp\nprivate final boolean mDebuggable\nprivate final boolean mMultiArch\nprivate final boolean mUse32bitAbi\nprivate final boolean mExtractNativeLibs\nprivate final boolean mProfileableByShell\nprivate final boolean mUseEmbeddedDex\nprivate final boolean mIsSdkLibrary\nprivate final boolean mIsStaticLibrary\nprivate final @android.annotation.NonNull java.util.List<java.lang.String> mUsesSdkLibraries\nprivate final @android.annotation.Nullable long[] mUsesSdkLibrariesVersionsMajor\nprivate final @android.annotation.Nullable java.lang.String[][] mUsesSdkLibrariesCertDigests\nprivate final @android.annotation.NonNull java.util.List<java.lang.String> mUsesStaticLibraries\nprivate final @android.annotation.Nullable long[] mUsesStaticLibrariesVersions\nprivate final @android.annotation.Nullable java.lang.String[][] mUsesStaticLibrariesCertDigests\nprivate final @android.annotation.NonNull java.util.List<android.content.pm.SharedLibraryInfo> mDeclaredLibraries\nprivate final @android.annotation.Nullable android.content.pm.ArchivedPackageParcel mArchivedPackage\nprivate final int mPageSizeCompat\npublic java.util.List<java.lang.String> getAllApkPaths()\npublic long getLongVersionCode()\nprivate boolean hasAnyRequiredSplitTypes()\nclass PackageLite extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false, genConstDefs=false)")
@Deprecated
private void __metadata() {}
diff --git a/core/java/android/hardware/biometrics/BiometricConstants.java b/core/java/android/hardware/biometrics/BiometricConstants.java
index 7dc6afba3f1c..a7fbce51e9df 100644
--- a/core/java/android/hardware/biometrics/BiometricConstants.java
+++ b/core/java/android/hardware/biometrics/BiometricConstants.java
@@ -188,6 +188,24 @@ public interface BiometricConstants {
int BIOMETRIC_ERROR_CONTENT_VIEW_MORE_OPTIONS_BUTTON = 22;
/**
+ * The error code returned after lock out error happens, the error dialog shows, and the users
+ * dismisses the dialog. This is a placeholder that is currently only used by the support
+ * library.
+ *
+ * @hide
+ */
+ int BIOMETRIC_ERROR_LOCKOUT_ERROR_DIALOG_DISMISSED = 23;
+
+ /**
+ * The error code returned after biometric hardware error happens, the error dialog shows, and
+ * the users dismisses the dialog.This is a placeholder that is currently only used by the
+ * support library.
+ *
+ * @hide
+ */
+ int BIOMETRIC_ERROR_BIOMETRIC_HARDWARE_ERROR_DIALOG_DISMISSED = 24;
+
+ /**
* This constant is only used by SystemUI. It notifies SystemUI that authentication was paused
* because the authentication attempt was unsuccessful.
* @hide
@@ -219,6 +237,8 @@ public interface BiometricConstants {
BIOMETRIC_ERROR_IDENTITY_CHECK_NOT_ACTIVE,
BIOMETRIC_ERROR_NOT_ENABLED_FOR_APPS,
BIOMETRIC_ERROR_CONTENT_VIEW_MORE_OPTIONS_BUTTON,
+ BIOMETRIC_ERROR_LOCKOUT_ERROR_DIALOG_DISMISSED,
+ BIOMETRIC_ERROR_BIOMETRIC_HARDWARE_ERROR_DIALOG_DISMISSED,
BIOMETRIC_PAUSED_REJECTED})
@Retention(RetentionPolicy.SOURCE)
@interface Errors {}
diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
index 89a6b02b56c4..56d272768a66 100644
--- a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
@@ -1616,7 +1616,13 @@ public class CameraDeviceImpl extends CameraDevice
// request if no repeating request is active. A default capture request is created here
// for initial use. The capture callback will provide capture results that include the
// actual capture parameters used for the streaming.
- CaptureRequest.Builder builder = createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
+ CameraMetadataNative templatedRequest = mRemoteDevice.createDefaultRequest(
+ CameraDevice.TEMPLATE_PREVIEW);
+
+ CaptureRequest.Builder builder = new CaptureRequest.Builder(
+ templatedRequest, /*reprocess*/false, CameraCaptureSession.SESSION_ID_NONE,
+ getId(), /*physicalCameraIdSet*/ null);
+
for (Surface surface : surfaces) {
builder.addTarget(surface);
}
diff --git a/core/java/android/os/ITradeInMode.aidl b/core/java/android/os/ITradeInMode.aidl
index f15954d14d0e..d05f52cf6a90 100644
--- a/core/java/android/os/ITradeInMode.aidl
+++ b/core/java/android/os/ITradeInMode.aidl
@@ -59,4 +59,37 @@ interface ITradeInMode {
* ENTER_TRADE_IN_MODE permission is required.
*/
boolean enterEvaluationMode();
+
+ /**
+ * Schedules a wipe to trigger SUW for trade-in mode testing. A reboot is
+ * required. After this, startTesting() can be called.
+ *
+ * ENTER_TRADE_IN_MODE permission is required and ro.debuggable must be 1.
+ */
+ void scheduleWipeForTesting();
+
+ /**
+ * Enables testing. This only takes effect after the next reboot, and is
+ * only allowed in ro.debuggable builds. On the following boot, normal
+ * adbd will be disabled and trade-in mode adbd will be enabled instead.
+ *
+ * ENTER_TRADE_IN_MODE permission is required and ro.debuggable must be 1.
+ */
+ void startTesting();
+
+ /**
+ * Disables testing. This disables trade-in mode and removes any scheduled
+ * trade-in mode wipe.
+ *
+ * ENTER_TRADE_IN_MODE permission is required, ro.debuggable must be 1, and
+ * startTesting() must have been called.
+ */
+ void stopTesting();
+
+ /**
+ * Returns whether the device is testing trade-in mode.
+ *
+ * ENTER_TRADE_IN_MODE permission is required and ro.debuggable must be 1.
+ */
+ boolean isTesting();
}
diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java
index e769abec7dd9..5129af6be442 100644
--- a/core/java/android/os/PowerManager.java
+++ b/core/java/android/os/PowerManager.java
@@ -4213,6 +4213,17 @@ public final class PowerManager {
else mFlags &= ~UNIMPORTANT_FOR_LOGGING;
}
+ /** @hide */
+ public void updateUids(int[] uids) {
+ synchronized (mToken) {
+ try {
+ mService.updateWakeLockUids(mToken, uids);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+
@Override
public String toString() {
synchronized (mToken) {
diff --git a/core/java/android/os/TestLooperManager.java b/core/java/android/os/TestLooperManager.java
index 82bdb2280c35..2d9d025b8d80 100644
--- a/core/java/android/os/TestLooperManager.java
+++ b/core/java/android/os/TestLooperManager.java
@@ -174,6 +174,7 @@ public class TestLooperManager {
try {
execution.wait();
} catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
}
if (execution.response != null) {
throw new RuntimeException(execution.response);
@@ -231,6 +232,7 @@ public class TestLooperManager {
try {
mLooperHolderLatch.await();
} catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
}
}
@@ -245,6 +247,7 @@ public class TestLooperManager {
processMessage(take);
}
} catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
}
}
}
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index aaf6489d18ca..ce93c71ac776 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -5326,7 +5326,9 @@ public class UserManager {
}
/**
- * Returns list of the profiles of userId including userId itself.
+ * Returns a list of the users that are associated with userId, including userId itself. This
+ * includes the user, its profiles, its parent, and its parent's other profiles, as applicable.
+ *
* Note that this returns both enabled and not enabled profiles. See
* {@link #getEnabledProfiles(int)} if you need only the enabled ones.
* <p>Note that this includes all profile types (not including Restricted profiles).
@@ -5334,7 +5336,7 @@ public class UserManager {
* <p>Requires {@link android.Manifest.permission#MANAGE_USERS} or
* {@link android.Manifest.permission#CREATE_USERS} or
* {@link android.Manifest.permission#QUERY_USERS} if userId is not the calling user.
- * @param userId profiles of this user will be returned.
+ * @param userId profiles associated with this user (including itself) will be returned.
* @return the list of profiles.
* @hide
*/
@@ -5358,12 +5360,13 @@ public class UserManager {
}
/**
- * Returns list of the profiles of the given user, including userId itself, as well as the
- * communal profile, if there is one.
+ * Returns a list of the users that are associated with userId, including userId itself,
+ * as well as the communal profile, if there is one.
*
* <p>Note that this returns both enabled and not enabled profiles.
* <p>Note that this includes all profile types (not including Restricted profiles).
*
+ * @see #getProfiles(int)
* @hide
*/
@FlaggedApi(android.multiuser.Flags.FLAG_SUPPORT_COMMUNAL_PROFILE)
@@ -5419,7 +5422,10 @@ public class UserManager {
}
/**
- * Returns list of the profiles of userId including userId itself.
+ * Returns a list of the enabled users that are associated with userId, including userId itself.
+ * This includes the user, its profiles, its parent, and its parent's other profiles, as
+ * applicable.
+ *
* Note that this returns only {@link UserInfo#isEnabled() enabled} profiles.
* <p>Note that this includes all profile types (not including Restricted profiles).
*
@@ -5447,8 +5453,10 @@ public class UserManager {
}
/**
- * Returns a list of UserHandles for profiles associated with the context user, including the
- * user itself.
+ * Returns a list of the users that are associated with the context user, including the user
+ * itself. This includes the user, its profiles, its parent, and its parent's other profiles,
+ * as applicable.
+ *
* <p>Note that this includes all profile types (not including Restricted profiles).
*
* @return A non-empty list of UserHandles associated with the context user.
@@ -5465,8 +5473,10 @@ public class UserManager {
}
/**
- * Returns a list of ids for enabled profiles associated with the context user including the
- * user itself.
+ * Returns a list of the enabled users that are associated with the context user, including the
+ * user itself. This includes the user, its profiles, its parent, and its parent's other
+ * profiles, as applicable.
+ *
* <p>Note that this includes all profile types (not including Restricted profiles).
*
* @return A non-empty list of UserHandles associated with the context user.
@@ -5483,8 +5493,10 @@ public class UserManager {
}
/**
- * Returns a list of ids for all profiles associated with the context user including the user
- * itself.
+ * Returns a list of all users that are associated with the context user, including the user
+ * itself. This includes the user, its profiles, its parent, and its parent's other profiles,
+ * as applicable.
+ *
* <p>Note that this includes all profile types (not including Restricted profiles).
*
* @return A non-empty list of UserHandles associated with the context user.
@@ -5501,8 +5513,10 @@ public class UserManager {
}
/**
- * Returns a list of ids for profiles associated with the context user including the user
- * itself.
+ * Returns a list of the users that are associated with the context user, including the user
+ * itself. This includes the user, its profiles, its parent, and its parent's other profiles, as
+ * applicable.
+ *
* <p>Note that this includes all profile types (not including Restricted profiles).
*
* @param enabledOnly whether to return only {@link UserInfo#isEnabled() enabled} profiles
@@ -5528,8 +5542,10 @@ public class UserManager {
}
/**
- * Returns a list of ids for profiles associated with the specified user including the user
- * itself.
+ * Returns a list of the users that are associated with the specified user, including the user
+ * itself. This includes the user, its profiles, its parent, and its parent's other profiles,
+ * as applicable.
+ *
* <p>Note that this includes all profile types (not including Restricted profiles).
*
* @param userId id of the user to return profiles for
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index ec0d9152468e..0f5476f58f74 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -10082,6 +10082,7 @@ public class RemoteViews implements Parcelable, Filter {
if (mApplication != null) {
// mApplication may be null if this was created with DrawInstructions constructor.
out.write(RemoteViewsProto.PACKAGE_NAME, mApplication.packageName);
+ out.write(RemoteViewsProto.UID, mApplication.uid);
}
Resources appResources = getContextForResourcesEnsuringCorrectCachedApkPaths(
context).getResources();
@@ -10163,6 +10164,7 @@ public class RemoteViews implements Parcelable, Filter {
int mApplyFlags = 0;
long mProviderInstanceId = -1;
String mPackageName = null;
+ Integer mUid = null;
SizeF mIdealSize = null;
String mLayoutResName = null;
String mLightBackgroundResName = null;
@@ -10185,6 +10187,9 @@ public class RemoteViews implements Parcelable, Filter {
case (int) RemoteViewsProto.PACKAGE_NAME:
ref.mPackageName = in.readString(RemoteViewsProto.PACKAGE_NAME);
break;
+ case (int) RemoteViewsProto.UID:
+ ref.mUid = in.readInt(RemoteViewsProto.UID);
+ break;
case (int) RemoteViewsProto.IDEAL_SIZE:
final long idealSizeToken = in.start(RemoteViewsProto.IDEAL_SIZE);
ref.mIdealSize = createSizeFFromProto(in);
@@ -10286,8 +10291,9 @@ public class RemoteViews implements Parcelable, Filter {
Resources appResources = null;
if (!ref.mHasDrawInstructions) {
checkProtoResultNotNull(ref.mPackageName, "No application info");
- rv.mApplication = context.getPackageManager().getApplicationInfo(ref.mPackageName,
- /* flags= */ 0);
+ checkProtoResultNotNull(ref.mUid, "No uid");
+ rv.mApplication = context.getPackageManager().getApplicationInfoAsUser(
+ ref.mPackageName, /* flags= */ 0, UserHandle.getUserId(ref.mUid));
appContext = rv.getContextForResourcesEnsuringCorrectCachedApkPaths(context);
appResources = appContext.getResources();
diff --git a/core/java/android/window/DesktopModeFlags.java b/core/java/android/window/DesktopModeFlags.java
index f86a4249c0e1..082bf5dc5a1c 100644
--- a/core/java/android/window/DesktopModeFlags.java
+++ b/core/java/android/window/DesktopModeFlags.java
@@ -77,7 +77,7 @@ public enum DesktopModeFlags {
ENABLE_DESKTOP_WINDOWING_MODE(Flags::enableDesktopWindowingMode, true),
ENABLE_DESKTOP_WINDOWING_MULTI_INSTANCE_FEATURES(
Flags::enableDesktopWindowingMultiInstanceFeatures, true),
- ENABLE_DESKTOP_WINDOWING_PERSISTENCE(Flags::enableDesktopWindowingPersistence, false),
+ ENABLE_DESKTOP_WINDOWING_PERSISTENCE(Flags::enableDesktopWindowingPersistence, true),
ENABLE_DESKTOP_WINDOWING_QUICK_SWITCH(Flags::enableDesktopWindowingQuickSwitch, true),
ENABLE_DESKTOP_WINDOWING_SIZE_CONSTRAINTS(Flags::enableDesktopWindowingSizeConstraints, true),
ENABLE_DESKTOP_WINDOWING_TASKBAR_RUNNING_APPS(Flags::enableDesktopWindowingTaskbarRunningApps,
diff --git a/core/java/android/window/flags/lse_desktop_experience.aconfig b/core/java/android/window/flags/lse_desktop_experience.aconfig
index ed22ec73aac8..6634ee0e1020 100644
--- a/core/java/android/window/flags/lse_desktop_experience.aconfig
+++ b/core/java/android/window/flags/lse_desktop_experience.aconfig
@@ -631,3 +631,13 @@ flag {
description: "Enables full support of presentation API for connected displays."
bug: "378503083"
}
+
+flag {
+ name: "enable_full_screen_window_on_removing_split_screen_stage_bugfix"
+ namespace: "lse_desktop_experience"
+ description: "Enables clearing the windowing mode of a freeform window when removing the task from the split screen stage."
+ bug: "372791604"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/core/java/android/window/flags/windowing_sdk.aconfig b/core/java/android/window/flags/windowing_sdk.aconfig
index 1b946afd506c..6f8852daae5f 100644
--- a/core/java/android/window/flags/windowing_sdk.aconfig
+++ b/core/java/android/window/flags/windowing_sdk.aconfig
@@ -185,3 +185,14 @@ flag {
description: "Enables letterboxing for a safe region"
bug: "380132497"
}
+
+flag {
+ namespace: "windowing_sdk"
+ name: "fix_layout_existing_task"
+ description: "Layout the existing task to ensure the bounds are updated."
+ bug: "390291971"
+ is_fixed_read_only: true
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+} \ No newline at end of file
diff --git a/core/java/com/android/internal/content/NativeLibraryHelper.java b/core/java/com/android/internal/content/NativeLibraryHelper.java
index e170d6652863..8c64750b66e4 100644
--- a/core/java/com/android/internal/content/NativeLibraryHelper.java
+++ b/core/java/com/android/internal/content/NativeLibraryHelper.java
@@ -85,6 +85,8 @@ public class NativeLibraryHelper {
final boolean extractNativeLibs;
final boolean debuggable;
+ final boolean pageSizeCompatDisabled;
+
public static Handle create(File packageFile) throws IOException {
final ParseTypeImpl input = ParseTypeImpl.forDefaultParsing();
final ParseResult<PackageLite> ret = ApkLiteParseUtils.parsePackageLite(input.reset(),
@@ -97,12 +99,15 @@ public class NativeLibraryHelper {
}
public static Handle create(PackageLite lite) throws IOException {
+ boolean isPageSizeCompatDisabled = lite.getPageSizeCompat()
+ == ApplicationInfo.PAGE_SIZE_APP_COMPAT_FLAG_MANIFEST_OVERRIDE_DISABLED;
return create(lite.getAllApkPaths(), lite.isMultiArch(), lite.isExtractNativeLibs(),
- lite.isDebuggable());
+ lite.isDebuggable(), isPageSizeCompatDisabled);
}
public static Handle create(List<String> codePaths, boolean multiArch,
- boolean extractNativeLibs, boolean debuggable) throws IOException {
+ boolean extractNativeLibs, boolean debuggable, boolean isPageSizeCompatDisabled)
+ throws IOException {
final int size = codePaths.size();
final String[] apkPaths = new String[size];
final long[] apkHandles = new long[size];
@@ -119,7 +124,8 @@ public class NativeLibraryHelper {
}
}
- return new Handle(apkPaths, apkHandles, multiArch, extractNativeLibs, debuggable);
+ return new Handle(apkPaths, apkHandles, multiArch, extractNativeLibs, debuggable,
+ isPageSizeCompatDisabled);
}
public static Handle createFd(PackageLite lite, FileDescriptor fd) throws IOException {
@@ -130,17 +136,21 @@ public class NativeLibraryHelper {
throw new IOException("Unable to open APK " + path + " from fd " + fd);
}
+ boolean isPageSizeCompatDisabled = lite.getPageSizeCompat()
+ == ApplicationInfo.PAGE_SIZE_APP_COMPAT_FLAG_MANIFEST_OVERRIDE_DISABLED;
+
return new Handle(new String[]{path}, apkHandles, lite.isMultiArch(),
- lite.isExtractNativeLibs(), lite.isDebuggable());
+ lite.isExtractNativeLibs(), lite.isDebuggable(), isPageSizeCompatDisabled);
}
Handle(String[] apkPaths, long[] apkHandles, boolean multiArch,
- boolean extractNativeLibs, boolean debuggable) {
+ boolean extractNativeLibs, boolean debuggable, boolean isPageSizeCompatDisabled) {
this.apkPaths = apkPaths;
this.apkHandles = apkHandles;
this.multiArch = multiArch;
this.extractNativeLibs = extractNativeLibs;
this.debuggable = debuggable;
+ this.pageSizeCompatDisabled = isPageSizeCompatDisabled;
mGuard.open("close");
}
@@ -175,7 +185,8 @@ public class NativeLibraryHelper {
private static native long nativeSumNativeBinaries(long handle, String cpuAbi);
private native static int nativeCopyNativeBinaries(long handle, String sharedLibraryPath,
- String abiToCopy, boolean extractNativeLibs, boolean debuggable);
+ String abiToCopy, boolean extractNativeLibs, boolean debuggable,
+ boolean pageSizeCompatDisabled);
private static native int nativeCheckAlignment(
long handle,
@@ -203,7 +214,7 @@ public class NativeLibraryHelper {
public static int copyNativeBinaries(Handle handle, File sharedLibraryDir, String abi) {
for (long apkHandle : handle.apkHandles) {
int res = nativeCopyNativeBinaries(apkHandle, sharedLibraryDir.getPath(), abi,
- handle.extractNativeLibs, handle.debuggable);
+ handle.extractNativeLibs, handle.debuggable, handle.pageSizeCompatDisabled);
if (res != INSTALL_SUCCEEDED) {
return res;
}
diff --git a/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java b/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java
index 5c08dc6be1a0..db60e12e50b1 100644
--- a/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java
+++ b/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java
@@ -29,7 +29,6 @@ import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_UNEXPECTED_
import static android.os.Build.VERSION_CODES.DONUT;
import static android.os.Build.VERSION_CODES.O;
import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER;
-import static android.sdk.Flags.majorMinorVersioningScheme;
import static com.android.internal.pm.pkg.parsing.ParsingUtils.parseKnownActivityEmbeddingCerts;
@@ -1690,21 +1689,6 @@ public class ParsingPackageUtils {
targetCode = minCode;
}
- if (majorMinorVersioningScheme()) {
- val = sa.peekValue(R.styleable.AndroidManifestUsesSdk_minSdkVersionFull);
- if (val != null) {
- if (val.type == TypedValue.TYPE_STRING && val.string != null) {
- String minSdkVersionFullString = val.string.toString();
- ParseResult<Void> minSdkVersionFullResult =
- FrameworkParsingPackageUtils.verifyMinSdkVersionFull(
- minSdkVersionFullString, Build.VERSION.SDK_INT_FULL, input);
- if (minSdkVersionFullResult.isError()) {
- return input.error(minSdkVersionFullResult);
- }
- }
- }
- }
-
if (isApkInApex) {
val = sa.peekValue(R.styleable.AndroidManifestUsesSdk_maxSdkVersion);
if (val != null) {
diff --git a/core/jni/com_android_internal_content_NativeLibraryHelper.cpp b/core/jni/com_android_internal_content_NativeLibraryHelper.cpp
index e78c5247d8a7..06fd80e37669 100644
--- a/core/jni/com_android_internal_content_NativeLibraryHelper.cpp
+++ b/core/jni/com_android_internal_content_NativeLibraryHelper.cpp
@@ -273,6 +273,7 @@ static install_status_t copyFileIfChanged(JNIEnv* env, void* arg, ZipFileRO* zip
jboolean extractNativeLibs = *(jboolean*)args[1];
jboolean debuggable = *(jboolean*)args[2];
jboolean app_compat_16kb = *(jboolean*)args[3];
+ jboolean pageSizeCompatDisabled = *(jboolean*)args[4];
install_status_t ret = INSTALL_SUCCEEDED;
ScopedUtfChars nativeLibPath(env, *javaNativeLibPath);
@@ -304,6 +305,16 @@ static install_status_t copyFileIfChanged(JNIEnv* env, void* arg, ZipFileRO* zip
}
if (offset % kPageSize != 0) {
+ // If page size app compat was disabled explicitly in manifest, don't extract libs on
+ // 16 KB page size device.
+ if (kPageSize == 0x4000 && pageSizeCompatDisabled) {
+ ALOGE("pageSizeCompat=disabled library '%s' is not PAGE(%zu)-"
+ "aligned within apk (APK alignment, not ELF alignment) -"
+ "and will not be extracted.\n",
+ fileName, kPageSize);
+ return INSTALL_FAILED_INVALID_APK;
+ }
+
// If the library is zip-aligned correctly for 4kb devices and app compat is
// enabled, on 16kb devices fallback to extraction
if (offset % 0x1000 == 0 && app_compat_16kb) {
@@ -537,13 +548,12 @@ static inline bool app_compat_16kb_enabled() {
return !android::base::GetBoolProperty("pm.16kb.app_compat.disabled", false);
}
-static jint
-com_android_internal_content_NativeLibraryHelper_copyNativeBinaries(JNIEnv *env, jclass clazz,
- jlong apkHandle, jstring javaNativeLibPath, jstring javaCpuAbi,
- jboolean extractNativeLibs, jboolean debuggable)
-{
+static jint com_android_internal_content_NativeLibraryHelper_copyNativeBinaries(
+ JNIEnv* env, jclass clazz, jlong apkHandle, jstring javaNativeLibPath, jstring javaCpuAbi,
+ jboolean extractNativeLibs, jboolean debuggable, jboolean pageSizeCompatDisabled) {
jboolean app_compat_16kb = app_compat_16kb_enabled();
- void* args[] = { &javaNativeLibPath, &extractNativeLibs, &debuggable, &app_compat_16kb };
+ void* args[] = {&javaNativeLibPath, &extractNativeLibs, &debuggable, &app_compat_16kb,
+ &pageSizeCompatDisabled};
return (jint) iterateOverNativeFiles(env, apkHandle, javaCpuAbi,
copyFileIfChanged, reinterpret_cast<void*>(args));
}
@@ -804,7 +814,7 @@ static const JNINativeMethod gMethods[] = {
{"nativeOpenApkFd", "(Ljava/io/FileDescriptor;Ljava/lang/String;)J",
(void*)com_android_internal_content_NativeLibraryHelper_openApkFd},
{"nativeClose", "(J)V", (void*)com_android_internal_content_NativeLibraryHelper_close},
- {"nativeCopyNativeBinaries", "(JLjava/lang/String;Ljava/lang/String;ZZ)I",
+ {"nativeCopyNativeBinaries", "(JLjava/lang/String;Ljava/lang/String;ZZZ)I",
(void*)com_android_internal_content_NativeLibraryHelper_copyNativeBinaries},
{"nativeSumNativeBinaries", "(JLjava/lang/String;)J",
(void*)com_android_internal_content_NativeLibraryHelper_sumNativeBinaries},
diff --git a/core/proto/android/widget/remoteviews.proto b/core/proto/android/widget/remoteviews.proto
index 6a987a475711..91dbf7b54534 100644
--- a/core/proto/android/widget/remoteviews.proto
+++ b/core/proto/android/widget/remoteviews.proto
@@ -57,6 +57,7 @@ message RemoteViewsProto {
repeated bytes bitmap_cache = 14;
optional RemoteCollectionCache remote_collection_cache = 15;
repeated Action actions = 16;
+ optional int32 uid = 17;
message RemoteCollectionCache {
message Entry {
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index 8c6fd1dfc47e..3edc5c108083 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -2572,10 +2572,6 @@
against a development branch, in which case it will only work against
the development builds. -->
<attr name="minSdkVersion" format="integer|string" />
- <!-- This is the minimum SDK major and minor version (e.g. "36.1") that
- the application requires. Verified independently of minSdkVersion.
- @FlaggedApi(android.sdk.Flags.FLAG_MAJOR_MINOR_VERSIONING_SCHEME) -->
- <attr name="minSdkVersionFull" format="string" />
<!-- This is the SDK version number that the application is targeting.
It is able to run on older versions (down to minSdkVersion), but
was explicitly tested to work with the version specified here.
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index a49e03484192..9f731fe04472 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -5958,6 +5958,9 @@
<!-- <item>com.google</item> -->
</string-array>
+ <!-- Whether to restrict the accounts that raw contacts can be created in. -->
+ <bool name = "config_rawContactsAccountRestrictionEnabled">true</bool>
+
<!-- Whether or not to use assistant stream volume separately from music volume -->
<bool name="config_useAssistantVolume">false</bool>
diff --git a/core/res/res/values/public-final.xml b/core/res/res/values/public-final.xml
index d8e89318a134..af1e5123096d 100644
--- a/core/res/res/values/public-final.xml
+++ b/core/res/res/values/public-final.xml
@@ -3953,8 +3953,7 @@
<public name="pageSizeCompat" />
<!-- @FlaggedApi(android.nfc.Flags.FLAG_NFC_ASSOCIATED_ROLE_SERVICES) -->
<public name="wantsRoleHolderPriority"/>
- <!-- @FlaggedApi(android.sdk.Flags.FLAG_MAJOR_MINOR_VERSIONING_SCHEME) -->
- <public name="minSdkVersionFull"/>
+ <public name="removed_"/>
<public name="removed_" />
<public name="removed_" />
<public name="removed_" />
@@ -3980,8 +3979,6 @@
<public type="attr" name="pageSizeCompat" id="0x010106ab" />
<!-- @FlaggedApi(android.nfc.Flags.FLAG_NFC_ASSOCIATED_ROLE_SERVICES) -->
<public type="attr" name="wantsRoleHolderPriority" id="0x010106ac" />
- <!-- @FlaggedApi(android.sdk.Flags.FLAG_MAJOR_MINOR_VERSIONING_SCHEME) -->
- <public type="attr" name="minSdkVersionFull" id="0x010106ad" />
<staging-public-group-final type="string" first-id="0x01b40000">
<!-- @FlaggedApi(android.content.pm.Flags.FLAG_SDK_DEPENDENCY_INSTALLER)
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 653996afbc01..f4004fa70623 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -4617,6 +4617,7 @@
<java-symbol type="string" name="config_rawContactsLocalAccountName" />
<java-symbol type="string" name="config_rawContactsLocalAccountType" />
<java-symbol type="array" name="config_rawContactsEligibleDefaultAccountTypes" />
+ <java-symbol type="bool" name="config_rawContactsAccountRestrictionEnabled" />
<!-- For App Standby -->
<java-symbol type="string" name="as_app_forced_to_restricted_bucket" />
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 1edbffa9d572..1de8664231d7 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -259,6 +259,7 @@ applications that come with the platform
<permission name="android.permission.MODIFY_DAY_NIGHT_MODE"/>
<permission name="android.permission.ACCESS_LOWPAN_STATE"/>
<permission name="android.permission.BACKUP"/>
+ <permission name="android.permission.ENTER_TRADE_IN_MODE"/>
<!-- Needed for GMSCore Location API test only -->
<permission name="android.permission.LOCATION_BYPASS"/>
<!-- Needed for test only -->
diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml
index 9e2d23b41556..404bbd1d0a33 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -270,6 +270,8 @@
<dimen name="bubble_bar_expanded_view_switch_offset">48dp</dimen>
<!-- Minimum width of the bubble bar manage menu. -->
<dimen name="bubble_bar_manage_menu_min_width">200dp</dimen>
+ <!-- The Bubble Bar drop zone square size. -->
+ <dimen name="bubble_bar_drop_zone_side_size">200dp</dimen>
<!-- Size of the dismiss icon in the bubble bar manage menu. -->
<dimen name="bubble_bar_manage_menu_dismiss_icon_size">16dp</dimen>
<!-- Padding of the bubble bar manage menu, provides space for menu shadows -->
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java
index ea0894bf1eea..deec52d1c19e 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java
@@ -208,8 +208,7 @@ public class DesktopModeStatus {
/**
* Return {@code true} if the current device supports desktop mode.
*/
- @VisibleForTesting
- public static boolean isDesktopModeSupported(@NonNull Context context) {
+ private static boolean isDesktopModeSupported(@NonNull Context context) {
return context.getResources().getBoolean(R.bool.config_isDesktopModeSupported);
}
@@ -232,7 +231,7 @@ public class DesktopModeStatus {
* Return {@code true} if desktop mode dev option should be shown on current device
*/
public static boolean canShowDesktopExperienceDevOption(@NonNull Context context) {
- return Flags.showDesktopExperienceDevOption();
+ return Flags.showDesktopExperienceDevOption() && isDeviceEligibleForDesktopMode(context);
}
/** Returns if desktop mode dev option should be enabled if there is no user override. */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index 5aed9e910dd3..9120e0894ccf 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -93,6 +93,7 @@ import com.android.launcher3.icons.BubbleIconFactory;
import com.android.wm.shell.Flags;
import com.android.wm.shell.R;
import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.bubbles.bar.BubbleBarDragListener;
import com.android.wm.shell.bubbles.bar.BubbleBarLayerView;
import com.android.wm.shell.bubbles.shortcut.BubbleShortcutHelper;
import com.android.wm.shell.common.DisplayController;
@@ -148,7 +149,8 @@ import java.util.function.IntConsumer;
* The controller manages addition, removal, and visible state of bubbles on screen.
*/
public class BubbleController implements ConfigurationChangeListener,
- RemoteCallable<BubbleController>, Bubbles.SysuiProxy.Provider {
+ RemoteCallable<BubbleController>, Bubbles.SysuiProxy.Provider,
+ BubbleBarDragListener {
private static final String TAG = TAG_WITH_CLASS_NAME ? "BubbleController" : TAG_BUBBLES;
@@ -416,7 +418,6 @@ public class BubbleController implements ConfigurationChangeListener,
mBubbleData.setListener(mBubbleDataListener);
mBubbleData.setSuppressionChangedListener(this::onBubbleMetadataFlagChanged);
mDataRepository.setSuppressionChangedListener(this::onBubbleMetadataFlagChanged);
-
mBubbleData.setPendingIntentCancelledListener(bubble -> {
if (bubble.getBubbleIntent() == null) {
return;
@@ -844,6 +845,47 @@ public class BubbleController implements ConfigurationChangeListener,
}
}
+ @Override
+ public void onDragItemOverBubbleBarDragZone(@Nullable BubbleBarLocation bubbleBarLocation) {
+ if (bubbleBarLocation == null) return;
+ if (isShowingAsBubbleBar() && BubbleAnythingFlagHelper.enableCreateAnyBubble()) {
+ //TODO(b/388894910) show expanded view drop
+ mBubbleStateListener.onDragItemOverBubbleBarDragZone(bubbleBarLocation);
+ }
+ }
+
+ @Override
+ public void onItemDraggedOutsideBubbleBarDropZone() {
+ if (isShowingAsBubbleBar() && BubbleAnythingFlagHelper.enableCreateAnyBubble()) {
+ //TODO(b/388894910) hide expanded view drop
+ mBubbleStateListener.onItemDraggedOutsideBubbleBarDropZone();
+ }
+ }
+
+ @Override
+ public void onItemDroppedOverBubbleBarDragZone(@Nullable BubbleBarLocation bubbleBarLocation) {
+ if (bubbleBarLocation == null) return;
+ if (isShowingAsBubbleBar() && BubbleAnythingFlagHelper.enableCreateAnyBubble()) {
+ //TODO(b/388894910) handle item drop with expandStackAndSelectBubble()
+ }
+ }
+
+ @Override
+ public Map<BubbleBarLocation, Rect> getBubbleBarDropZones(int l, int t, int r, int b) {
+ Map<BubbleBarLocation, Rect> result = new HashMap<>();
+ if (isShowingAsBubbleBar() && BubbleAnythingFlagHelper.enableCreateAnyBubble()) {
+ // TODO(b/393172431) : Utilise DragZoneFactory once it is ready
+ final int bubbleBarDropZoneSideSize = getContext().getResources().getDimensionPixelSize(
+ R.dimen.bubble_bar_drop_zone_side_size);
+ int top = t - bubbleBarDropZoneSideSize;
+ result.put(BubbleBarLocation.LEFT,
+ new Rect(l, top, l + bubbleBarDropZoneSideSize, b));
+ result.put(BubbleBarLocation.RIGHT,
+ new Rect(r - bubbleBarDropZoneSideSize, top, r, b));
+ }
+ return result;
+ }
+
/** Whether this userId belongs to the current user. */
private boolean isCurrentProfile(int userId) {
return userId == UserHandle.USER_ALL
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarDragListener.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarDragListener.kt
new file mode 100644
index 000000000000..00eaad675350
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarDragListener.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.bubbles.bar
+
+import android.graphics.Rect
+import com.android.wm.shell.shared.bubbles.BubbleBarLocation
+
+/** Controller that takes care of the bubble bar drag events. */
+interface BubbleBarDragListener {
+
+ /** Called when the drag event is over the bubble bar drop zone. */
+ fun onDragItemOverBubbleBarDragZone(location: BubbleBarLocation)
+
+ /** Called when the drag event leaves the bubble bar drop zone. */
+ fun onItemDraggedOutsideBubbleBarDropZone()
+
+ /** Called when the drop event happens over the bubble bar drop zone. */
+ fun onItemDroppedOverBubbleBarDragZone(location: BubbleBarLocation?)
+
+ /**
+ * Returns mapping of the bubble bar locations to the corresponding
+ * [rect][android.graphics.Rect] zone.
+ */
+ fun getBubbleBarDropZones(l: Int, t: Int, r: Int, b: Int): Map<BubbleBarLocation, Rect>
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index 6f16e047a968..6ab103e3bd89 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -53,6 +53,7 @@ import com.android.wm.shell.apptoweb.AppToWebGenericLinksParser;
import com.android.wm.shell.apptoweb.AssistContentRequester;
import com.android.wm.shell.appzoomout.AppZoomOutController;
import com.android.wm.shell.back.BackAnimationController;
+import com.android.wm.shell.bubbles.bar.BubbleBarDragListener;
import com.android.wm.shell.bubbles.BubbleController;
import com.android.wm.shell.bubbles.BubbleData;
import com.android.wm.shell.bubbles.BubbleDataRepository;
@@ -169,19 +170,18 @@ import com.android.wm.shell.windowdecor.education.DesktopWindowingEducationPromo
import com.android.wm.shell.windowdecor.education.DesktopWindowingEducationTooltipController;
import com.android.wm.shell.windowdecor.tiling.DesktopTilingDecorViewModel;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+
import dagger.Binds;
import dagger.Lazy;
import dagger.Module;
import dagger.Provides;
-
import kotlinx.coroutines.CoroutineScope;
import kotlinx.coroutines.ExperimentalCoroutinesApi;
import kotlinx.coroutines.MainCoroutineDispatcher;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Optional;
-
/**
* Provides dependencies from {@link com.android.wm.shell}, these dependencies are only accessible
* from components within the WM subcomponent (can be explicitly exposed to the SysUIComponent, see
@@ -1157,9 +1157,10 @@ public abstract class WMShellModule {
@WMSingleton
@Provides
static DesksTransitionObserver provideDesksTransitionObserver(
- @NonNull @DynamicOverride DesktopUserRepositories desktopUserRepositories
+ @NonNull @DynamicOverride DesktopUserRepositories desktopUserRepositories,
+ @NonNull DesksOrganizer desksOrganizer
) {
- return new DesksTransitionObserver(desktopUserRepositories);
+ return new DesksTransitionObserver(desktopUserRepositories, desksOrganizer);
}
@WMSingleton
@@ -1411,6 +1412,7 @@ public abstract class WMShellModule {
IconProvider iconProvider,
GlobalDragListener globalDragListener,
Transitions transitions,
+ Lazy<BubbleController> bubbleControllerLazy,
@ShellMainThread ShellExecutor mainExecutor) {
return new DragAndDropController(
context,
@@ -1423,6 +1425,12 @@ public abstract class WMShellModule {
iconProvider,
globalDragListener,
transitions,
+ new Lazy<>() {
+ @Override
+ public BubbleBarDragListener get() {
+ return bubbleControllerLazy.get();
+ }
+ },
mainExecutor);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeShellCommandHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeShellCommandHandler.kt
index b93d2e396402..03bc42f08d59 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeShellCommandHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeShellCommandHandler.kt
@@ -57,7 +57,7 @@ class DesktopModeShellCommandHandler(private val controller: DesktopTasksControl
return false
}
if (!Flags.enableMultipleDesktopsBackend()) {
- return controller.moveTaskToDesktop(taskId, transitionSource = UNKNOWN)
+ return controller.moveTaskToDefaultDeskAndActivate(taskId, transitionSource = UNKNOWN)
}
if (args.size < 3) {
pw.println("Error: desk id should be provided as arguments")
@@ -70,8 +70,9 @@ class DesktopModeShellCommandHandler(private val controller: DesktopTasksControl
pw.println("Error: desk id should be an integer")
return false
}
+ controller.moveTaskToDesk(taskId = taskId, deskId = deskId, transitionSource = UNKNOWN)
pw.println("Not implemented.")
- return false
+ return true
}
private fun runMoveToNextDisplay(args: Array<String>, pw: PrintWriter): Boolean {
@@ -131,8 +132,8 @@ class DesktopModeShellCommandHandler(private val controller: DesktopTasksControl
pw.println("Error: desk id should be an integer")
return false
}
- pw.println("Not implemented.")
- return false
+ controller.activateDesk(deskId)
+ return true
}
private fun runRemoveDesk(args: Array<String>, pw: PrintWriter): Boolean {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt
index 043b353ba380..4777e7f93bc9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt
@@ -226,31 +226,42 @@ class DesktopRepository(
desktopData.setActiveDesk(displayId = displayId, deskId = deskId)
}
+ /** Returns the id of the active desk in the given display, if any. */
+ @VisibleForTesting
+ fun getActiveDeskId(displayId: Int): Int? = desktopData.getActiveDesk(displayId)?.deskId
+
/**
* Adds task with [taskId] to the list of freeform tasks on [displayId]'s active desk.
*
* TODO: b/389960283 - add explicit [deskId] argument.
*/
fun addTask(displayId: Int, taskId: Int, isVisible: Boolean) {
- addOrMoveFreeformTaskToTop(displayId, taskId)
- addActiveTask(displayId, taskId)
- updateTask(displayId, taskId, isVisible)
+ val activeDesk =
+ checkNotNull(desktopData.getDefaultDesk(displayId)) {
+ "Expected desk in display: $displayId"
+ }
+ addTaskToDesk(displayId = displayId, deskId = activeDesk.deskId, taskId = taskId, isVisible)
}
- /**
- * Adds task with [taskId] to the list of active tasks on [displayId]'s active desk.
- *
- * TODO: b/389960283 - add explicit [deskId] argument.
- */
- private fun addActiveTask(displayId: Int, taskId: Int) {
- val activeDesk = desktopData.getDefaultDesk(displayId)
- checkNotNull(activeDesk) { "Expected desk in display: $displayId" }
+ fun addTaskToDesk(displayId: Int, deskId: Int, taskId: Int, isVisible: Boolean) {
+ addOrMoveTaskToTopOfDesk(displayId = displayId, deskId = deskId, taskId = taskId)
+ addActiveTaskToDesk(displayId = displayId, deskId = deskId, taskId = taskId)
+ updateTaskInDesk(
+ displayId = displayId,
+ deskId = deskId,
+ taskId = taskId,
+ isVisible = isVisible,
+ )
+ }
+
+ private fun addActiveTaskToDesk(displayId: Int, deskId: Int, taskId: Int) {
+ val desk = checkNotNull(desktopData.getDesk(deskId)) { "Did not find desk: $deskId" }
- // Removes task if it is active on another desk excluding [activeDesk].
- removeActiveTask(taskId, excludedDeskId = activeDesk.deskId)
+ // Removes task if it is active on another desk excluding this desk.
+ removeActiveTask(taskId, excludedDeskId = deskId)
- if (activeDesk.activeTasks.add(taskId)) {
- logD("Adds active task=%d displayId=%d deskId=%d", taskId, displayId, activeDesk.deskId)
+ if (desk.activeTasks.add(taskId)) {
+ logD("Adds active task=%d displayId=%d deskId=%d", taskId, displayId, deskId)
updateActiveTasksListeners(displayId)
}
}
@@ -401,10 +412,10 @@ class DesktopRepository(
emptySet()
}
- /** Removes task from visible tasks of all displays except [excludedDisplayId]. */
- private fun removeVisibleTask(taskId: Int, excludedDisplayId: Int? = null) {
+ /** Removes task from visible tasks of all desks except [excludedDeskId]. */
+ private fun removeVisibleTask(taskId: Int, excludedDeskId: Int? = null) {
desktopData.forAllDesks { displayId, desk ->
- if (displayId != excludedDisplayId && desk.visibleTasks.remove(taskId)) {
+ if (desk.deskId != excludedDeskId && desk.visibleTasks.remove(taskId)) {
notifyVisibleTaskListeners(displayId, desk.visibleTasks.size)
}
}
@@ -419,30 +430,58 @@ class DesktopRepository(
* TODO: b/389960283 - add explicit [deskId] argument.
*/
fun updateTask(displayId: Int, taskId: Int, isVisible: Boolean) {
- logD("updateTask taskId=%d, displayId=%d, isVisible=%b", taskId, displayId, isVisible)
+ val validDisplayId =
+ if (displayId == INVALID_DISPLAY) {
+ // When a task vanishes it doesn't have a displayId. Find the display of the task.
+ getDisplayIdForTask(taskId)
+ } else {
+ displayId
+ }
+ if (validDisplayId == null) {
+ logW("No display id found for task: taskId=%d", taskId)
+ return
+ }
+ val desk =
+ checkNotNull(desktopData.getDefaultDesk(validDisplayId)) {
+ "Expected a desk in display: $validDisplayId"
+ }
+ updateTaskInDesk(
+ displayId = validDisplayId,
+ deskId = desk.deskId,
+ taskId = taskId,
+ isVisible,
+ )
+ }
+
+ private fun updateTaskInDesk(displayId: Int, deskId: Int, taskId: Int, isVisible: Boolean) {
+ check(displayId != INVALID_DISPLAY) { "Display must be valid" }
+ logD(
+ "updateTaskInDesk taskId=%d, deskId=%d, displayId=%d, isVisible=%b",
+ taskId,
+ deskId,
+ displayId,
+ isVisible,
+ )
if (isVisible) {
- // If task is visible, remove it from any other display besides [displayId].
- removeVisibleTask(taskId, excludedDisplayId = displayId)
- } else if (displayId == INVALID_DISPLAY) {
- // Task has vanished. Check which display to remove the task from.
- removeVisibleTask(taskId)
- return
+ // If task is visible, remove it from any other desk besides [deskId].
+ removeVisibleTask(taskId, excludedDeskId = deskId)
}
- val prevCount = getVisibleTaskCount(displayId)
+ val desk = checkNotNull(desktopData.getDesk(deskId)) { "Did not find desk: $deskId" }
+ val prevCount = getVisibleTaskCountInDesk(deskId)
if (isVisible) {
- desktopData.getDefaultDesk(displayId)?.visibleTasks?.add(taskId)
- ?: error("Expected non-null desk in display $displayId")
+ desk.visibleTasks.add(taskId)
unminimizeTask(displayId, taskId)
} else {
- desktopData.getActiveDesk(displayId)?.visibleTasks?.remove(taskId)
+ desk.visibleTasks.remove(taskId)
}
- val newCount = getVisibleTaskCount(displayId)
+ val newCount = getVisibleTaskCount(deskId)
if (prevCount != newCount) {
logD(
- "Update task visibility taskId=%d visible=%b displayId=%d",
+ "Update task visibility taskId=%d visible=%b deskId=%d displayId=%d",
taskId,
isVisible,
+ deskId,
displayId,
)
logD("VisibleTaskCount has changed from %d to %d", prevCount, newCount)
@@ -602,33 +641,32 @@ class DesktopRepository(
/**
* Gets number of visible freeform tasks on given [displayId]'s active desk.
*
- * TODO: b/389960283 - add explicit [deskId] argument.
+ * TODO: b/389960283 - migrate callers to [getVisibleTaskCountInDesk].
*/
fun getVisibleTaskCount(displayId: Int): Int =
(desktopData.getActiveDesk(displayId)?.visibleTasks?.size ?: 0).also {
logD("getVisibleTaskCount=$it")
}
+ /** Gets the number of visible tasks on the given desk. */
+ fun getVisibleTaskCountInDesk(deskId: Int): Int =
+ desktopData.getDesk(deskId)?.visibleTasks?.size ?: 0
+
/**
* Adds task (or moves if it already exists) to the top of the ordered list.
*
* Unminimizes the task if it is minimized.
- *
- * TODO: b/389960283 - add explicit [deskId] argument.
*/
- private fun addOrMoveFreeformTaskToTop(displayId: Int, taskId: Int) {
- val desk = getDefaultDesk(displayId) ?: error("Expected a desk in display: $displayId")
- logD(
- "Add or move task to top: display=%d taskId=%d deskId=%d",
- taskId,
- displayId,
- desk.deskId,
- )
+ private fun addOrMoveTaskToTopOfDesk(displayId: Int, deskId: Int, taskId: Int) {
+ val desk = desktopData.getDesk(deskId) ?: error("Could not find desk: $deskId")
+ logD("addOrMoveTaskToTopOfDesk: display=%d deskId=%d taskId=%d", displayId, deskId, taskId)
desktopData.forAllDesks { _, desk1 -> desk1.freeformTasksInZOrder.remove(taskId) }
desk.freeformTasksInZOrder.add(0, taskId)
+ // TODO: double check minimization logic.
// Unminimize the task if it is minimized.
unminimizeTask(displayId, taskId)
if (DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_PERSISTENCE.isTrue()) {
+ // TODO: can probably just update the desk.
updatePersistentRepository(displayId)
}
}
@@ -644,6 +682,7 @@ class DesktopRepository(
// mark it as minimized.
getDisplayIdForTask(taskId)?.let { minimizeTask(it, taskId) }
?: logW("Minimize task: No display id found for task: taskId=%d", taskId)
+ return
} else {
logD("Minimize Task: display=%d, task=%d", displayId, taskId)
desktopData.getActiveDesk(displayId)?.minimizedTasks?.add(taskId)
@@ -676,7 +715,7 @@ class DesktopRepository(
private fun getDisplayIdForTask(taskId: Int): Int? {
var displayForTask: Int? = null
desktopData.forAllDesks { displayId, desk ->
- if (taskId in desk.freeformTasksInZOrder) {
+ if (taskId in desk.activeTasks) {
displayForTask = displayId
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index 475515053bfe..7b0fb1d89557 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -315,24 +315,10 @@ class DesktopTasksController(
}
/** Show all tasks, that are part of the desktop, on top of launcher */
+ @Deprecated("Use activateDesk() instead.", ReplaceWith("activateDesk()"))
fun showDesktopApps(displayId: Int, remoteTransition: RemoteTransition? = null) {
logV("showDesktopApps")
- val wct = WindowContainerTransaction()
- bringDesktopAppsToFront(displayId, wct)
-
- val transitionType = transitionType(remoteTransition)
- val handler =
- remoteTransition?.let {
- OneShotRemoteHandler(transitions.mainExecutor, remoteTransition)
- }
- transitions.startTransition(transitionType, wct, handler).also { t ->
- handler?.setTransition(t)
- }
-
- // launch from recent DesktopTaskView
- desktopModeEnterExitTransitionListener?.onEnterDesktopModeTransitionStarted(
- FREEFORM_ANIMATION_DURATION
- )
+ activateDefaultDeskInDisplay(displayId, remoteTransition)
}
/** Gets number of visible freeform tasks in [displayId]. */
@@ -371,15 +357,15 @@ class DesktopTasksController(
0 -> return
// Full screen case
1 ->
- moveRunningTaskToDesktop(
- allFocusedTasks.single(),
+ moveTaskToDefaultDeskAndActivate(
+ allFocusedTasks.single().taskId,
transitionSource = transitionSource,
)
// Split-screen case where there are two focused tasks, then we find the child
// task to move to desktop.
2 ->
- moveRunningTaskToDesktop(
- getSplitFocusedTask(allFocusedTasks[0], allFocusedTasks[1]),
+ moveTaskToDefaultDeskAndActivate(
+ getSplitFocusedTask(allFocusedTasks[0], allFocusedTasks[1]).taskId,
transitionSource = transitionSource,
)
else ->
@@ -442,15 +428,57 @@ class DesktopTasksController(
/** Moves task to desktop mode if task is running, else launches it in desktop mode. */
@JvmOverloads
- fun moveTaskToDesktop(
+ fun moveTaskToDefaultDeskAndActivate(
+ taskId: Int,
+ wct: WindowContainerTransaction = WindowContainerTransaction(),
+ transitionSource: DesktopModeTransitionSource,
+ remoteTransition: RemoteTransition? = null,
+ callback: IMoveToDesktopCallback? = null,
+ ): Boolean {
+ val runningTask = shellTaskOrganizer.getRunningTaskInfo(taskId)
+ val backgroundTask = recentTasksController?.findTaskInBackground(taskId)
+ if (runningTask == null && backgroundTask == null) {
+ logW("moveTaskToDefaultDeskAndActivate taskId=%d not found", taskId)
+ return false
+ }
+ // TODO(342378842): Instead of using default display, support multiple displays
+ val displayId = runningTask?.displayId ?: DEFAULT_DISPLAY
+ val deskId =
+ checkNotNull(taskRepository.getDefaultDeskId(displayId)) {
+ "Expected a default desk to exist"
+ }
+ return moveTaskToDesk(
+ taskId = taskId,
+ deskId = deskId,
+ wct = wct,
+ transitionSource = transitionSource,
+ remoteTransition = remoteTransition,
+ )
+ }
+
+ /** Moves task to desktop mode if task is running, else launches it in desktop mode. */
+ fun moveTaskToDesk(
taskId: Int,
+ deskId: Int,
wct: WindowContainerTransaction = WindowContainerTransaction(),
transitionSource: DesktopModeTransitionSource,
remoteTransition: RemoteTransition? = null,
callback: IMoveToDesktopCallback? = null,
): Boolean {
val runningTask = shellTaskOrganizer.getRunningTaskInfo(taskId)
- if (runningTask == null) {
+ if (runningTask != null) {
+ moveRunningTaskToDesk(
+ task = runningTask,
+ deskId = deskId,
+ wct = wct,
+ transitionSource = transitionSource,
+ remoteTransition = remoteTransition,
+ callback = callback,
+ )
+ }
+ val backgroundTask = recentTasksController?.findTaskInBackground(taskId)
+ if (backgroundTask != null) {
+ // TODO: b/391484662 - add support for |deskId|.
return moveBackgroundTaskToDesktop(
taskId,
wct,
@@ -459,8 +487,8 @@ class DesktopTasksController(
callback,
)
}
- moveRunningTaskToDesktop(runningTask, wct, transitionSource, remoteTransition, callback)
- return true
+ logW("moveTaskToDesk taskId=%d not found", taskId)
+ return false
}
private fun moveBackgroundTaskToDesktop(
@@ -514,8 +542,9 @@ class DesktopTasksController(
}
/** Moves a running task to desktop. */
- fun moveRunningTaskToDesktop(
+ private fun moveRunningTaskToDesk(
task: RunningTaskInfo,
+ deskId: Int,
wct: WindowContainerTransaction = WindowContainerTransaction(),
transitionSource: DesktopModeTransitionSource,
remoteTransition: RemoteTransition? = null,
@@ -525,20 +554,49 @@ class DesktopTasksController(
logW("Cannot enter desktop for taskId %d, ineligible top activity found", task.taskId)
return
}
- logV("moveRunningTaskToDesktop taskId=%d", task.taskId)
+ val displayId = taskRepository.getDisplayForDesk(deskId)
+ logV(
+ "moveRunningTaskToDesk taskId=%d deskId=%d displayId=%d",
+ task.taskId,
+ deskId,
+ displayId,
+ )
exitSplitIfApplicable(wct, task)
val exitResult =
desktopImmersiveController.exitImmersiveIfApplicable(
wct = wct,
- displayId = task.displayId,
+ displayId = displayId,
excludeTaskId = task.taskId,
reason = DesktopImmersiveController.ExitReason.TASK_LAUNCH,
)
- // Bring other apps to front first
val taskIdToMinimize =
- bringDesktopAppsToFrontBeforeShowingNewTask(task.displayId, wct, task.taskId)
- addMoveToDesktopChanges(wct, task)
+ if (Flags.enableMultipleDesktopsBackend()) {
+ // Activate the desk first.
+ prepareForDeskActivation(displayId, wct)
+ desksOrganizer.activateDesk(wct, deskId)
+ if (DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_PERSISTENCE.isTrue()) {
+ // TODO: 362720497 - do non-running tasks need to be restarted with
+ // |wct#startTask|?
+ }
+ taskbarDesktopTaskListener?.onTaskbarCornerRoundingUpdate(
+ doesAnyTaskRequireTaskbarRounding(displayId)
+ )
+ // TODO: 362720497 - activating a desk with the intention to move a new task to it
+ // means we may need to minimize something in the activating desk. Do so here
+ // similar
+ // to how it's done in #bringDesktopAppsToFrontBeforeShowingNewTask instead of
+ // returning null.
+ null
+ } else {
+ // Bring other apps to front first.
+ bringDesktopAppsToFrontBeforeShowingNewTask(task.displayId, wct, task.taskId)
+ }
+ if (Flags.enableMultipleDesktopsBackend()) {
+ prepareMoveTaskToDesk(wct, task, deskId)
+ } else {
+ addMoveToDesktopChanges(wct, task)
+ }
val transition: IBinder
if (remoteTransition != null) {
@@ -557,6 +615,18 @@ class DesktopTasksController(
addPendingMinimizeTransition(transition, it, MinimizeReason.TASK_LIMIT)
}
exitResult.asExit()?.runOnTransitionStart?.invoke(transition)
+ if (Flags.enableMultipleDesktopsBackend()) {
+ desksTransitionObserver.addPendingTransition(
+ DeskTransition.ActiveDeskWithTask(
+ token = transition,
+ displayId = displayId,
+ deskId = deskId,
+ enterTaskId = task.taskId,
+ )
+ )
+ } else {
+ taskRepository.setActiveDesk(displayId = displayId, deskId = deskId)
+ }
}
private fun invokeCallbackToOverview(transition: IBinder, callback: IMoveToDesktopCallback?) {
@@ -606,9 +676,9 @@ class DesktopTasksController(
val wct = WindowContainerTransaction()
exitSplitIfApplicable(wct, taskInfo)
if (Flags.enablePerDisplayDesktopWallpaperActivity()) {
- moveHomeTask(wct, toTop = true, taskInfo.displayId)
+ moveHomeTask(taskInfo.displayId, wct)
} else {
- moveHomeTask(wct, toTop = true)
+ moveHomeTask(context.displayId, wct)
}
val taskIdToMinimize =
bringDesktopAppsToFrontBeforeShowingNewTask(taskInfo.displayId, wct, taskInfo.taskId)
@@ -780,7 +850,7 @@ class DesktopTasksController(
// We are moving a freeform task to fullscreen, put the home task under the fullscreen task.
if (!forceEnterDesktop(task.displayId)) {
- moveHomeTask(wct, toTop = true, task.displayId)
+ moveHomeTask(task.displayId, wct)
wct.reorder(task.token, /* onTop= */ true)
}
@@ -1018,6 +1088,23 @@ class DesktopTasksController(
}
val wct = WindowContainerTransaction()
+
+ // check if the task is part of splitscreen
+ if (
+ Flags.enableNonDefaultDisplaySplit() &&
+ Flags.enableMoveToNextDisplayShortcut() &&
+ splitScreenController.isTaskInSplitScreen(task.taskId)
+ ) {
+ val stageCoordinatorRootTaskToken =
+ splitScreenController.multiDisplayProvider.getDisplayRootForDisplayId(
+ DEFAULT_DISPLAY
+ )
+
+ wct.reparent(stageCoordinatorRootTaskToken, displayAreaInfo.token, true /* onTop */)
+ transitions.startTransition(TRANSIT_CHANGE, wct, /* handler= */ null)
+ return
+ }
+
if (!task.isFreeform) {
addMoveToDesktopChanges(wct, task, displayId)
} else if (Flags.enableMoveToNextDisplayShortcut()) {
@@ -1037,6 +1124,10 @@ class DesktopTasksController(
task.displayId,
wct,
forceToFullscreen = false,
+ // TODO: b/371096166 - Temporary turing home relaunch off to prevent home stealing
+ // display focus. Remove shouldEndUpAtHome = false when home focus handling
+ // with connected display is implemented in wm core.
+ shouldEndUpAtHome = false,
)
}
@@ -1416,33 +1507,36 @@ class DesktopTasksController(
?: WINDOWING_MODE_UNDEFINED
}
+ private fun prepareForDeskActivation(displayId: Int, wct: WindowContainerTransaction) {
+ // Move home to front, ensures that we go back home when all desktop windows are closed
+ val useParamDisplayId =
+ Flags.enableMultipleDesktopsBackend() ||
+ Flags.enablePerDisplayDesktopWallpaperActivity()
+ moveHomeTask(displayId = if (useParamDisplayId) displayId else context.displayId, wct = wct)
+ // Currently, we only handle the desktop on the default display really.
+ if (
+ (displayId == DEFAULT_DISPLAY || Flags.enablePerDisplayDesktopWallpaperActivity()) &&
+ ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY.isTrue()
+ ) {
+ // Add translucent wallpaper activity to show the wallpaper underneath.
+ addWallpaperActivity(displayId, wct)
+ }
+ }
+
private fun bringDesktopAppsToFrontBeforeShowingNewTask(
displayId: Int,
wct: WindowContainerTransaction,
newTaskIdInFront: Int,
): Int? = bringDesktopAppsToFront(displayId, wct, newTaskIdInFront)
+ @Deprecated("Use activeDesk() instead.", ReplaceWith("activateDesk()"))
private fun bringDesktopAppsToFront(
displayId: Int,
wct: WindowContainerTransaction,
newTaskIdInFront: Int? = null,
): Int? {
logV("bringDesktopAppsToFront, newTaskId=%d", newTaskIdInFront)
- // Move home to front, ensures that we go back home when all desktop windows are closed
- if (Flags.enablePerDisplayDesktopWallpaperActivity()) {
- moveHomeTask(wct, toTop = true, displayId)
- } else {
- moveHomeTask(wct, toTop = true)
- }
-
- // Currently, we only handle the desktop on the default display really.
- if (
- (displayId == DEFAULT_DISPLAY || Flags.enablePerDisplayDesktopWallpaperActivity()) &&
- ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY.isTrue()
- ) {
- // Add translucent wallpaper activity to show the wallpaper underneath
- addWallpaperActivity(displayId, wct)
- }
+ prepareForDeskActivation(displayId, wct)
val expandedTasksOrderedFrontToBack = taskRepository.getExpandedTasksOrdered(displayId)
// If we're adding a new Task we might need to minimize an old one
@@ -1486,15 +1580,11 @@ class DesktopTasksController(
return taskIdToMinimize
}
- private fun moveHomeTask(
- wct: WindowContainerTransaction,
- toTop: Boolean,
- displayId: Int = DEFAULT_DISPLAY,
- ) {
+ private fun moveHomeTask(displayId: Int, wct: WindowContainerTransaction) {
shellTaskOrganizer
.getRunningTasks(displayId)
.firstOrNull { task -> task.activityType == ACTIVITY_TYPE_HOME }
- ?.let { homeTask -> wct.reorder(homeTask.getToken(), /* onTop= */ toTop) }
+ ?.let { homeTask -> wct.reorder(homeTask.getToken(), /* onTop= */ true) }
}
private fun addLaunchHomePendingIntent(wct: WindowContainerTransaction, displayId: Int) {
@@ -2150,6 +2240,7 @@ class DesktopTasksController(
* different [displayId] if the task should be moved to a different display.
*/
@VisibleForTesting
+ @Deprecated("Deprecated with multiple desks", ReplaceWith("prepareMoveTaskToDesk()"))
fun addMoveToDesktopChanges(
wct: WindowContainerTransaction,
taskInfo: RunningTaskInfo,
@@ -2177,6 +2268,24 @@ class DesktopTasksController(
}
}
+ private fun prepareMoveTaskToDesk(
+ wct: WindowContainerTransaction,
+ taskInfo: RunningTaskInfo,
+ deskId: Int,
+ ) {
+ if (!Flags.enableMultipleDesktopsBackend()) return
+ val displayId = taskRepository.getDisplayForDesk(deskId)
+ val displayLayout = displayController.getDisplayLayout(displayId) ?: return
+ val initialBounds = getInitialBounds(displayLayout, taskInfo, displayId)
+ if (canChangeTaskPosition(taskInfo)) {
+ wct.setBounds(taskInfo.token, initialBounds)
+ }
+ desksOrganizer.moveTaskToDesk(wct, deskId = deskId, task = taskInfo)
+ if (useDesktopOverrideDensity()) {
+ wct.setDensityDpi(taskInfo.token, DESKTOP_DENSITY_OVERRIDE)
+ }
+ }
+
/**
* Apply changes to move a freeform task from one display to another, which includes handling
* density changes between displays.
@@ -2372,6 +2481,57 @@ class DesktopTasksController(
)
}
+ private fun activateDefaultDeskInDisplay(
+ displayId: Int,
+ remoteTransition: RemoteTransition? = null,
+ ) {
+ val deskId =
+ checkNotNull(taskRepository.getDefaultDeskId(displayId)) {
+ "Expected a default desk to exist"
+ }
+ activateDesk(deskId, remoteTransition)
+ }
+
+ /** Activates the given desk. */
+ fun activateDesk(deskId: Int, remoteTransition: RemoteTransition? = null) {
+ val displayId = taskRepository.getDisplayForDesk(deskId)
+ val wct = WindowContainerTransaction()
+ if (Flags.enableMultipleDesktopsBackend()) {
+ prepareForDeskActivation(displayId, wct)
+ desksOrganizer.activateDesk(wct, deskId)
+ if (DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_PERSISTENCE.isTrue()) {
+ // TODO: 362720497 - do non-running tasks need to be restarted with |wct#startTask|?
+ }
+ taskbarDesktopTaskListener?.onTaskbarCornerRoundingUpdate(
+ doesAnyTaskRequireTaskbarRounding(displayId)
+ )
+ } else {
+ bringDesktopAppsToFront(displayId, wct)
+ }
+
+ val transitionType = transitionType(remoteTransition)
+ val handler =
+ remoteTransition?.let {
+ OneShotRemoteHandler(transitions.mainExecutor, remoteTransition)
+ }
+
+ val transition = transitions.startTransition(transitionType, wct, handler)
+ handler?.setTransition(transition)
+ if (Flags.enableMultipleDesktopsBackend()) {
+ desksTransitionObserver.addPendingTransition(
+ DeskTransition.ActivateDesk(
+ token = transition,
+ displayId = displayId,
+ deskId = deskId,
+ )
+ )
+ }
+
+ desktopModeEnterExitTransitionListener?.onEnterDesktopModeTransitionStarted(
+ FREEFORM_ANIMATION_DURATION
+ )
+ }
+
/** Removes the default desk in the given display. */
@Deprecated("Deprecated with multi-desks.", ReplaceWith("removeDesk()"))
fun removeDefaultDeskInDisplay(displayId: Int) {
@@ -3120,7 +3280,7 @@ class DesktopTasksController(
callback: IMoveToDesktopCallback?,
) {
executeRemoteCallWithTaskPermission(controller, "moveTaskToDesktop") { c ->
- c.moveTaskToDesktop(
+ c.moveTaskToDefaultDeskAndActivate(
taskId,
transitionSource = transitionSource,
remoteTransition = remoteTransition,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt
index cc3d86c0c056..2ac76f319d32 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt
@@ -959,9 +959,16 @@ constructor(
super.setupEndDragToDesktop(info, startTransaction, finishTransaction)
val state = requireTransitionState()
- val homeLeash = state.homeChange?.leash ?: error("Expects home leash to be non-null")
- // Hide home on finish to prevent flickering when wallpaper activity flag is enabled
- finishTransaction.hide(homeLeash)
+ val homeLeash = state.homeChange?.leash
+ if (homeLeash == null) {
+ ProtoLog.e(
+ ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
+ "DragToDesktop: home leash is null",
+ )
+ } else {
+ // Hide home on finish to prevent flickering when wallpaper activity flag is enabled
+ finishTransaction.hide(homeLeash)
+ }
// Setup freeform tasks before animation
state.freeformTaskChanges.forEach { change ->
val startScale = FREEFORM_TASKS_INITIAL_SCALE
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/DeskTransition.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/DeskTransition.kt
index 47088c0b545a..8c4fd9db050f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/DeskTransition.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/DeskTransition.kt
@@ -30,4 +30,16 @@ sealed class DeskTransition {
val tasks: Set<Int>,
val onDeskRemovedListener: OnDeskRemovedListener?,
) : DeskTransition()
+
+ /** A transition to activate a desk in its display. */
+ data class ActivateDesk(override val token: IBinder, val displayId: Int, val deskId: Int) :
+ DeskTransition()
+
+ /** A transition to activate a desk by moving an outside task to it. */
+ data class ActiveDeskWithTask(
+ override val token: IBinder,
+ val displayId: Int,
+ val deskId: Int,
+ val enterTaskId: Int,
+ ) : DeskTransition()
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/DesksOrganizer.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/DesksOrganizer.kt
index 5cbb59fbf323..547890a6200a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/DesksOrganizer.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/DesksOrganizer.kt
@@ -43,6 +43,9 @@ interface DesksOrganizer {
*/
fun getDeskAtEnd(change: TransitionInfo.Change): Int?
+ /** Whether the desk is activate according to the given change at the end of a transition. */
+ fun isDeskActiveAtEnd(change: TransitionInfo.Change, deskId: Int): Boolean
+
/** A callback that is invoked when the desk container is created. */
fun interface OnCreateCallback {
/** Calls back when the [deskId] has been created. */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/DesksTransitionObserver.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/DesksTransitionObserver.kt
index 3e49b8a4538b..6d88c3310a63 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/DesksTransitionObserver.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/DesksTransitionObserver.kt
@@ -25,7 +25,10 @@ import com.android.wm.shell.desktopmode.DesktopUserRepositories
* Observer of desk-related transitions, such as adding, removing or activating a whole desk. It
* tracks pending transitions and updates repository state once they finish.
*/
-class DesksTransitionObserver(private val desktopUserRepositories: DesktopUserRepositories) {
+class DesksTransitionObserver(
+ private val desktopUserRepositories: DesktopUserRepositories,
+ private val desksOrganizer: DesksOrganizer,
+) {
private val deskTransitions = mutableMapOf<IBinder, DeskTransition>()
/** Adds a pending desk transition to be tracked. */
@@ -53,6 +56,38 @@ class DesksTransitionObserver(private val desktopUserRepositories: DesktopUserRe
desktopRepository.removeDesk(deskTransition.deskId)
deskTransition.onDeskRemovedListener?.onDeskRemoved(displayId, deskId)
}
+ is DeskTransition.ActivateDesk -> {
+ val activeDeskChange =
+ info.changes.find { change ->
+ desksOrganizer.isDeskActiveAtEnd(change, deskTransition.deskId)
+ }
+ activeDeskChange?.let {
+ desktopRepository.setActiveDesk(
+ displayId = deskTransition.displayId,
+ deskId = deskTransition.deskId,
+ )
+ }
+ }
+ is DeskTransition.ActiveDeskWithTask -> {
+ val withTask =
+ info.changes.find { change ->
+ change.taskInfo?.taskId == deskTransition.enterTaskId &&
+ change.taskInfo?.isVisibleRequested == true &&
+ desksOrganizer.getDeskAtEnd(change) == deskTransition.deskId
+ }
+ withTask?.let {
+ desktopRepository.setActiveDesk(
+ displayId = deskTransition.displayId,
+ deskId = deskTransition.deskId,
+ )
+ desktopRepository.addTaskToDesk(
+ displayId = deskTransition.displayId,
+ deskId = deskTransition.deskId,
+ taskId = deskTransition.enterTaskId,
+ isVisible = true,
+ )
+ }
+ }
}
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/RootTaskDesksOrganizer.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/RootTaskDesksOrganizer.kt
index 79c48c5e9594..5cda76e2f3e0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/RootTaskDesksOrganizer.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/RootTaskDesksOrganizer.kt
@@ -22,6 +22,7 @@ import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
import android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED
import android.util.SparseArray
import android.view.SurfaceControl
+import android.view.WindowManager.TRANSIT_TO_FRONT
import android.window.TransitionInfo
import android.window.WindowContainerTransaction
import androidx.core.util.forEach
@@ -88,12 +89,18 @@ class RootTaskDesksOrganizer(
task: RunningTaskInfo,
) {
val root = roots[deskId] ?: error("Root not found for desk: $deskId")
+ wct.setWindowingMode(task.token, WINDOWING_MODE_UNDEFINED)
wct.reparent(task.token, root.taskInfo.token, /* onTop= */ true)
}
override fun getDeskAtEnd(change: TransitionInfo.Change): Int? =
change.taskInfo?.parentTaskId?.takeIf { it in roots }
+ override fun isDeskActiveAtEnd(change: TransitionInfo.Change, deskId: Int): Boolean =
+ change.taskInfo?.taskId == deskId &&
+ change.taskInfo?.isVisibleRequested == true &&
+ change.mode == TRANSIT_TO_FRONT
+
override fun onTaskAppeared(taskInfo: RunningTaskInfo, leash: SurfaceControl) {
if (taskInfo.parentTaskId in roots) {
val deskId = taskInfo.parentTaskId
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java
index e8996bc03eeb..a67557bd7bd0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java
@@ -62,6 +62,7 @@ import com.android.internal.protolog.ProtoLog;
import com.android.launcher3.icons.IconProvider;
import com.android.wm.shell.R;
import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.bubbles.bar.BubbleBarDragListener;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.ExternalInterfaceBinder;
import com.android.wm.shell.common.RemoteCallable;
@@ -80,6 +81,8 @@ import java.util.ArrayList;
import java.util.function.Consumer;
import java.util.function.Function;
+import dagger.Lazy;
+
/**
* Handles the global drag and drop handling for the Shell.
*/
@@ -101,6 +104,7 @@ public class DragAndDropController implements RemoteCallable<DragAndDropControll
private final GlobalDragListener mGlobalDragListener;
private final Transitions mTransitions;
private SplitScreenController mSplitScreen;
+ private Lazy<BubbleBarDragListener> mBubbleBarDragController;
private ShellExecutor mMainExecutor;
private ArrayList<DragAndDropListener> mListeners = new ArrayList<>();
@@ -143,6 +147,7 @@ public class DragAndDropController implements RemoteCallable<DragAndDropControll
IconProvider iconProvider,
GlobalDragListener globalDragListener,
Transitions transitions,
+ Lazy<BubbleBarDragListener> bubbleBarDragController,
ShellExecutor mainExecutor) {
mContext = context;
mShellController = shellController;
@@ -153,6 +158,7 @@ public class DragAndDropController implements RemoteCallable<DragAndDropControll
mIconProvider = iconProvider;
mGlobalDragListener = globalDragListener;
mTransitions = transitions;
+ mBubbleBarDragController = bubbleBarDragController;
mMainExecutor = mainExecutor;
shellInit.addInitCallback(this::onInit, this);
}
@@ -246,7 +252,8 @@ public class DragAndDropController implements RemoteCallable<DragAndDropControll
R.layout.global_drop_target, null);
rootView.setOnDragListener(this);
rootView.setVisibility(View.INVISIBLE);
- DragLayoutProvider dragLayout = new DragLayout(context, mSplitScreen, mIconProvider);
+ DragLayoutProvider dragLayout = new DragLayout(context, mSplitScreen,
+ mBubbleBarDragController.get(), mIconProvider);
dragLayout.addDraggingView(rootView);
try {
wm.addView(rootView, layoutParams);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java
index 5c72cb7f71a6..f0e0295336a7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java
@@ -44,10 +44,8 @@ import android.graphics.Color;
import android.graphics.Insets;
import android.graphics.Point;
import android.graphics.Rect;
-import android.graphics.RectF;
import android.graphics.Region;
import android.graphics.drawable.Drawable;
-import android.util.Log;
import android.view.DragEvent;
import android.view.SurfaceControl;
import android.view.View;
@@ -66,9 +64,11 @@ import com.android.internal.logging.InstanceId;
import com.android.internal.protolog.ProtoLog;
import com.android.launcher3.icons.IconProvider;
import com.android.wm.shell.R;
+import com.android.wm.shell.bubbles.bar.BubbleBarDragListener;
import com.android.wm.shell.common.split.SplitScreenUtils;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.shared.animation.Interpolators;
+import com.android.wm.shell.shared.bubbles.BubbleBarLocation;
import com.android.wm.shell.splitscreen.SplitScreenController;
import java.io.PrintWriter;
@@ -106,9 +106,11 @@ public class DragLayout extends LinearLayout
private boolean mIsLeftRightSplit;
private SplitDragPolicy.Target mCurrentTarget = null;
+ private final BubbleBarDragListener mBubbleBarDragListener;
+ private final Map<BubbleBarLocation, Rect> mBubbleBarLocations = new HashMap<>();
+ private BubbleBarLocation mCurrentBubbleBarTarget = null;
private DropZoneView mDropZoneView1;
private DropZoneView mDropZoneView2;
-
private int mDisplayMargin;
private int mDividerSize;
private int mLaunchIntentEdgeMargin;
@@ -128,11 +130,14 @@ public class DragLayout extends LinearLayout
// Used with enableFlexibleSplit() flag
@SuppressLint("WrongConstant")
- public DragLayout(Context context, SplitScreenController splitScreenController,
+ public DragLayout(Context context,
+ SplitScreenController splitScreenController,
+ BubbleBarDragListener bubbleBarDragListener,
IconProvider iconProvider) {
super(context);
mSplitScreenController = splitScreenController;
mIconProvider = iconProvider;
+ mBubbleBarDragListener = bubbleBarDragListener;
mPolicy = new SplitDragPolicy(context, splitScreenController, this);
mStatusBarManager = context.getSystemService(StatusBarManager.class);
mLastConfiguration.setTo(context.getResources().getConfiguration());
@@ -188,6 +193,12 @@ public class DragLayout extends LinearLayout
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
updateTouchableRegion();
+ updateBubbleBarRegions(l, t, r, b);
+ }
+
+ private void updateBubbleBarRegions(int l, int t, int r, int b) {
+ mBubbleBarLocations.clear();
+ mBubbleBarLocations.putAll(mBubbleBarDragListener.getBubbleBarDropZones(l, t, r, b));
}
/**
@@ -514,17 +525,18 @@ public class DragLayout extends LinearLayout
if (mHasDropped) {
return;
}
+ // if event is over the bubble don't let split handle it
+ if (interceptBubbleBarEvent(x, y)) {
+ mLastPosition.set(x, y);
+ return;
+ }
// Find containing region, if the same as mCurrentRegion, then skip, otherwise, animate the
// visibility of the current region
SplitDragPolicy.Target target = mPolicy.getTargetAtLocation(x, y);
if (mCurrentTarget != target) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP, "Current target: %s", target);
if (target == null) {
- // Animating to no target
- animateSplitContainers(false, null /* animCompleteCallback */);
- if (enableFlexibleSplit()) {
- animateHighlight(target);
- }
+ animateToNoTarget();
} else if (mCurrentTarget == null) {
if (mPolicy.getNumTargets() == 1) {
animateFullscreenContainer(true);
@@ -565,6 +577,45 @@ public class DragLayout extends LinearLayout
mLastPosition.set(x, y);
}
+ private boolean interceptBubbleBarEvent(int x, int y) {
+ BubbleBarLocation bubbleBarLocation = getBubbleBarLocation(x, y);
+ boolean isOverTheBubbleBar = bubbleBarLocation != null;
+ if (mCurrentBubbleBarTarget != bubbleBarLocation) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP, "Current bubble bar location: %s",
+ isOverTheBubbleBar);
+ mCurrentBubbleBarTarget = bubbleBarLocation;
+ if (isOverTheBubbleBar) {
+ mBubbleBarDragListener.onDragItemOverBubbleBarDragZone(bubbleBarLocation);
+ if (mCurrentTarget != null) {
+ animateToNoTarget();
+ mCurrentTarget = null;
+ }
+ } else {
+ mBubbleBarDragListener.onItemDraggedOutsideBubbleBarDropZone();
+ }
+ //TODO(b/388894910): handle accessibility
+ }
+ return isOverTheBubbleBar;
+ }
+
+ @Nullable
+ private BubbleBarLocation getBubbleBarLocation(int x, int y) {
+ for (BubbleBarLocation location : mBubbleBarLocations.keySet()) {
+ if (mBubbleBarLocations.get(location).contains(x, y)) {
+ return location;
+ }
+ }
+ return null;
+ }
+
+ private void animateToNoTarget() {
+ // Animating to no target
+ animateSplitContainers(false, null /* animCompleteCallback */);
+ if (enableFlexibleSplit()) {
+ animateHighlight(null);
+ }
+ }
+
/**
* Hides the drag layout and animates out the visible drop targets.
*/
@@ -596,11 +647,13 @@ public class DragLayout extends LinearLayout
*/
public boolean drop(DragEvent event, @NonNull SurfaceControl dragSurface,
@Nullable WindowContainerToken hideTaskToken, Runnable dropCompleteCallback) {
- final boolean handledDrop = mCurrentTarget != null;
+ final boolean handledDrop = mCurrentTarget != null || mCurrentBubbleBarTarget != null;
mHasDropped = true;
// Process the drop
mPolicy.onDropped(mCurrentTarget, hideTaskToken);
+ //TODO(b/388894910) add info about the application
+ mBubbleBarDragListener.onItemDroppedOverBubbleBarDragZone(mCurrentBubbleBarTarget);
// Start animating the drop UI out with the drag surface
hide(event, dropCompleteCallback);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIShellTestCase.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitMultiDisplayProvider.java
index 979cee9d63c2..d2e57e51762b 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIShellTestCase.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitMultiDisplayProvider.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2024 The Android Open Source Project
+ * Copyright (C) 2025 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,12 +14,16 @@
* limitations under the License.
*/
-package com.android.wm.shell.compatui;
+package com.android.wm.shell.splitscreen;
-import com.android.wm.shell.ShellTestCase;
+import android.window.WindowContainerToken;
-/**
- * Base class for CompatUI tests.
- */
-public class CompatUIShellTestCase extends ShellTestCase {
+public interface SplitMultiDisplayProvider {
+ /**
+ * Returns the WindowContainerToken for the root of the given display ID.
+ *
+ * @param displayId The ID of the display.
+ * @return The {@link WindowContainerToken} associated with the display's root task.
+ */
+ WindowContainerToken getDisplayRootForDisplayId(int displayId);
}
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 ae0159263364..e9f8a4a86d27 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
@@ -321,6 +321,10 @@ public class SplitScreenController implements SplitDragPolicy.Starter,
return mStageCoordinator;
}
+ public SplitMultiDisplayProvider getMultiDisplayProvider() {
+ return mStageCoordinator;
+ }
+
@Nullable
public ActivityManager.RunningTaskInfo getTaskInfo(@SplitPosition int splitPosition) {
if (!isSplitScreenVisible() || splitPosition == SPLIT_POSITION_UNDEFINED) {
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 6783df8f8324..13940b1da257 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
@@ -189,7 +189,8 @@ import java.util.function.Predicate;
*/
public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
DisplayController.OnDisplaysChangedListener, Transitions.TransitionHandler,
- ShellTaskOrganizer.TaskListener, StageTaskListener.StageListenerCallbacks {
+ ShellTaskOrganizer.TaskListener, StageTaskListener.StageListenerCallbacks,
+ SplitMultiDisplayProvider {
private static final String TAG = StageCoordinator.class.getSimpleName();
@@ -287,6 +288,16 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mSplitTransitions.registerSplitAnimListener(listener, executor);
}
+ @Override
+ public WindowContainerToken getDisplayRootForDisplayId(int displayId) {
+ if (displayId == DEFAULT_DISPLAY) {
+ return mRootTaskInfo != null ? mRootTaskInfo.token : null;
+ }
+
+ // TODO(b/393217881): support different root task on external displays.
+ return null; // Return null for unknown display IDs
+ }
+
class SplitRequest {
@SplitPosition
int mActivatePosition;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index fb4ce13c441f..17d619c9bee8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
@@ -755,7 +755,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
// App sometimes draws before the insets from WindowDecoration#relayout have
// been added, so they must be added here
decoration.addCaptionInset(wct);
- mDesktopTasksController.moveTaskToDesktop(taskId, wct, source,
+ mDesktopTasksController.moveTaskToDefaultDeskAndActivate(taskId, wct, source,
/* remoteTransition= */ null, /* moveToDesktopCallback */ null);
decoration.closeHandleMenu();
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/AndroidManifest.xml b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/AndroidManifest.xml
index 1bbbefadaa03..8fc974d4381e 100644
--- a/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/AndroidManifest.xml
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/AndroidManifest.xml
@@ -47,6 +47,8 @@
<uses-permission android:name="android.permission.STATUS_BAR_SERVICE" />
<!-- Allow the test to connect to perfetto trace processor -->
<uses-permission android:name="android.permission.INTERNET"/>
+ <!-- Use trusted virtual displays to emulate an external display -->
+ <uses-permission android:name="android.permission.ADD_TRUSTED_DISPLAY"/>
<!-- Allow the test to write directly to /sdcard/ and connect to trace processor -->
<application android:requestLegacyExternalStorage="true"
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/DesktopModeFlickerScenarios.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/DesktopModeFlickerScenarios.kt
index f767861addf9..9b402734a4c4 100644
--- a/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/DesktopModeFlickerScenarios.kt
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/DesktopModeFlickerScenarios.kt
@@ -546,5 +546,29 @@ class DesktopModeFlickerScenarios {
AppWindowBecomesPinned(DESKTOP_MODE_APP),
).associateBy({ it }, { AssertionInvocationGroup.BLOCKING })
)
+
+ val OPEN_APP_WHEN_EXTERNAL_DISPLAY_CONNECTED =
+ FlickerConfigEntry(
+ scenarioId = ScenarioId("OPEN_APP_WHEN_EXTERNAL_DISPLAY_CONNECTED"),
+ extractor =
+ ShellTransitionScenarioExtractor(
+ transitionMatcher =
+ object : ITransitionMatcher {
+ override fun findAll(
+ transitions: Collection<Transition>
+ ): Collection<Transition> {
+ return listOf(transitions
+ .filter { it.type == TransitionType.OPEN }
+ .maxByOrNull { it.id }!!)
+ }
+ }
+ ),
+ assertions =
+ listOf(
+ AppWindowBecomesVisible(DESKTOP_MODE_APP),
+ AppWindowOnTopAtEnd(DESKTOP_MODE_APP),
+ AppWindowBecomesVisible(DESKTOP_WALLPAPER),
+ ).associateBy({ it }, { AssertionInvocationGroup.BLOCKING }),
+ )
}
}
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/OpenAppWithExternalDisplayConnected.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/OpenAppWithExternalDisplayConnected.kt
new file mode 100644
index 000000000000..66d2ea95c67f
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/OpenAppWithExternalDisplayConnected.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker
+
+import android.tools.flicker.FlickerConfig
+import android.tools.flicker.annotation.ExpectedScenarios
+import android.tools.flicker.annotation.FlickerConfigProvider
+import android.tools.flicker.config.FlickerConfig
+import android.tools.flicker.config.FlickerServiceConfig
+import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.DesktopModeFlickerScenarios.Companion.OPEN_APP_WHEN_EXTERNAL_DISPLAY_CONNECTED
+import com.android.wm.shell.scenarios.OpenAppWithExternalDisplayConnected
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Open an app on the default display when an external display is connected.
+ *
+ * Assert that the app launches in desktop mode.
+ */
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class OpenAppWithExternalDisplayConnected : OpenAppWithExternalDisplayConnected() {
+ @ExpectedScenarios(["OPEN_APP_WHEN_EXTERNAL_DISPLAY_CONNECTED"])
+ @Test
+ override fun openAppWithExternalDisplayConnected() = super.openAppWithExternalDisplayConnected()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig()
+ .use(FlickerServiceConfig.DEFAULT)
+ .use(OPEN_APP_WHEN_EXTERNAL_DISPLAY_CONNECTED)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/EnterDesktopWithDrag.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/EnterDesktopWithDrag.kt
index 8d04749d76a5..2115f70faad0 100644
--- a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/EnterDesktopWithDrag.kt
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/EnterDesktopWithDrag.kt
@@ -50,7 +50,7 @@ constructor(
@Test
open fun enterDesktopWithDrag() {
// By default this method uses drag to desktop
- testApp.enterDesktopMode(wmHelper, device)
+ testApp.enterDesktopMode(wmHelper, device, shouldUseDragToDesktop = true)
}
@After
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/EnterDesktopWithDragExistingWindows.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/EnterDesktopWithDragExistingWindows.kt
index 814478af67c1..9a1919304675 100644
--- a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/EnterDesktopWithDragExistingWindows.kt
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/EnterDesktopWithDragExistingWindows.kt
@@ -62,7 +62,7 @@ constructor(
@Test
open fun reenterDesktopWithDrag() {
// By default this method uses drag to desktop
- testApp.enterDesktopMode(wmHelper, device)
+ testApp.enterDesktopMode(wmHelper, device, shouldUseDragToDesktop = true)
}
@After
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 b5c9fa151dac..2264adec9a19 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
@@ -49,6 +49,7 @@ 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;
@@ -85,7 +86,7 @@ import org.mockito.MockitoAnnotations;
*/
@RunWith(AndroidTestingRunner.class)
@SmallTest
-public class CompatUIControllerTest extends CompatUIShellTestCase {
+public class CompatUIControllerTest extends ShellTestCase {
private static final int DISPLAY_ID = 0;
private static final int TASK_ID = 12;
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 2117b062bf57..c567b5fbbb70 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
@@ -38,6 +38,7 @@ 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;
@@ -62,7 +63,7 @@ import java.util.function.Consumer;
*/
@RunWith(AndroidTestingRunner.class)
@SmallTest
-public class CompatUILayoutTest extends CompatUIShellTestCase {
+public class CompatUILayoutTest extends ShellTestCase {
private static final int TASK_ID = 1;
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 0b37648faeec..8fd7c0ec3099 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,6 +27,8 @@ 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;
@@ -42,7 +44,7 @@ import java.util.function.IntSupplier;
*/
@RunWith(AndroidTestingRunner.class)
@SmallTest
-public class CompatUIStatusManagerTest extends CompatUIShellTestCase {
+public class CompatUIStatusManagerTest extends ShellTestCase {
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 010474e42195..0562bb835671 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
@@ -53,6 +53,7 @@ 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;
@@ -77,7 +78,7 @@ import java.util.function.Consumer;
*/
@RunWith(AndroidTestingRunner.class)
@SmallTest
-public class CompatUIWindowManagerTest extends CompatUIShellTestCase {
+public class CompatUIWindowManagerTest extends ShellTestCase {
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 e786fef1855c..c6884ea17302 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
@@ -32,6 +32,7 @@ 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.Test;
@@ -47,7 +48,7 @@ import org.mockito.MockitoAnnotations;
*/
@RunWith(AndroidTestingRunner.class)
@SmallTest
-public class LetterboxEduDialogLayoutTest extends CompatUIShellTestCase {
+public class LetterboxEduDialogLayoutTest extends ShellTestCase {
@Mock
private Runnable mDismissCallback;
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 09fc082a63e3..cbf5d1bb65dd 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
@@ -62,6 +62,7 @@ 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;
@@ -90,7 +91,7 @@ import java.util.function.Consumer;
*/
@RunWith(AndroidTestingRunner.class)
@SmallTest
-public class LetterboxEduWindowManagerTest extends CompatUIShellTestCase {
+public class LetterboxEduWindowManagerTest extends ShellTestCase {
private static final int USER_ID_1 = 1;
private static final int USER_ID_2 = 2;
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 02c099b3cfb2..31ea8f76359f 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
@@ -34,6 +34,7 @@ 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.Test;
@@ -50,7 +51,7 @@ import org.mockito.MockitoAnnotations;
@RunWith(AndroidTestingRunner.class)
@SmallTest
@TestableLooper.RunWithLooper(setAsMainLooper = true)
-public class ReachabilityEduLayoutTest extends CompatUIShellTestCase {
+public class ReachabilityEduLayoutTest extends ShellTestCase {
private ReachabilityEduLayout mLayout;
private View mMoveUpButton;
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 fa04e070250e..1b2c0944777e 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
@@ -30,6 +30,7 @@ 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;
@@ -52,7 +53,7 @@ import java.util.function.BiConsumer;
*/
@RunWith(AndroidTestingRunner.class)
@SmallTest
-public class ReachabilityEduWindowManagerTest extends CompatUIShellTestCase {
+public class ReachabilityEduWindowManagerTest extends ShellTestCase {
@Mock
private SyncTransactionQueue mSyncTransactionQueue;
@Mock
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 2cded9d9776c..5075453d8c73 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
@@ -34,6 +34,7 @@ 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.Test;
@@ -51,7 +52,7 @@ import java.util.function.Consumer;
*/
@RunWith(AndroidTestingRunner.class)
@SmallTest
-public class RestartDialogLayoutTest extends CompatUIShellTestCase {
+public class RestartDialogLayoutTest extends ShellTestCase {
@Mock private Runnable mDismissCallback;
@Mock private Consumer<Boolean> mRestartCallback;
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 ebd0f412a0a1..779a5ca10648 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
@@ -28,6 +28,7 @@ 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;
@@ -50,7 +51,7 @@ import java.util.function.Consumer;
*/
@RunWith(AndroidTestingRunner.class)
@SmallTest
-public class RestartDialogWindowManagerTest extends CompatUIShellTestCase {
+public class RestartDialogWindowManagerTest extends ShellTestCase {
@Mock
private SyncTransactionQueue mSyncTransactionQueue;
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 c6532e13f3cc..2b4d5f125783 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
@@ -38,6 +38,7 @@ 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;
@@ -62,7 +63,7 @@ import java.util.function.BiConsumer;
*/
@RunWith(AndroidTestingRunner.class)
@SmallTest
-public class UserAspectRatioSettingsLayoutTest extends CompatUIShellTestCase {
+public class UserAspectRatioSettingsLayoutTest extends ShellTestCase {
private static final int TASK_ID = 1;
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 096e900199ba..af7c1f5d7692 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
@@ -54,6 +54,7 @@ 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;
@@ -83,7 +84,7 @@ import java.util.function.Supplier;
@RunWith(AndroidTestingRunner.class)
@RunWithLooper
@SmallTest
-public class UserAspectRatioSettingsWindowManagerTest extends CompatUIShellTestCase {
+public class UserAspectRatioSettingsWindowManagerTest extends ShellTestCase {
private static final int TASK_ID = 1;
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 09ffd946ea19..d6b13610c9c1 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
@@ -113,7 +113,7 @@ class DesktopActivityOrientationChangeHandlerTest : ShellTestCase() {
.strictness(Strictness.LENIENT)
.spyStatic(DesktopModeStatus::class.java)
.startMocking()
- doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
+ doReturn(true).`when` { DesktopModeStatus.isDeviceEligibleForDesktopMode(any()) }
testScope = CoroutineScope(Dispatchers.Unconfined + SupervisorJob())
shellInit = spy(ShellInit(testExecutor))
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 470c110fd49b..403d468a7034 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
@@ -112,7 +112,7 @@ class DesktopModeKeyGestureHandlerTest : ShellTestCase() {
.strictness(Strictness.LENIENT)
.spyStatic(DesktopModeStatus::class.java)
.startMocking()
- doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
+ doReturn(true).`when` { DesktopModeStatus.isDeviceEligibleForDesktopMode(any()) }
testScope = CoroutineScope(Dispatchers.Unconfined + SupervisorJob())
shellInit = spy(ShellInit(testExecutor))
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 6a343c56d364..8510441c0557 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
@@ -47,6 +47,7 @@ import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.runTest
import kotlinx.coroutines.test.setMain
import org.junit.After
+import org.junit.Assert.assertThrows
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -163,6 +164,69 @@ class DesktopRepositoryTest(flags: FlagsParameterization) : ShellTestCase() {
}
@Test
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun addTask_deskDoesNotExist_throws() {
+ repo.removeDesk(deskId = 0)
+
+ assertThrows(Exception::class.java) {
+ repo.addTask(displayId = DEFAULT_DISPLAY, taskId = 5, isVisible = true)
+ }
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun addTaskToDesk_deskDoesNotExist_throws() {
+ repo.removeDesk(deskId = 2)
+
+ assertThrows(Exception::class.java) {
+ repo.addTaskToDesk(
+ displayId = DEFAULT_DISPLAY,
+ deskId = 2,
+ taskId = 4,
+ isVisible = true,
+ )
+ }
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun addTaskToDesk_addsToZOrderList() {
+ repo.addDesk(DEFAULT_DISPLAY, deskId = 2)
+ repo.addDesk(DEFAULT_DISPLAY, deskId = 3)
+ repo.addTaskToDesk(displayId = DEFAULT_DISPLAY, deskId = 2, taskId = 5, isVisible = true)
+ repo.addTaskToDesk(displayId = DEFAULT_DISPLAY, deskId = 2, taskId = 6, isVisible = true)
+ repo.addTaskToDesk(displayId = DEFAULT_DISPLAY, deskId = 2, taskId = 7, isVisible = true)
+ repo.addTaskToDesk(displayId = DEFAULT_DISPLAY, deskId = 3, taskId = 8, isVisible = true)
+
+ val orderedTasks = repo.getFreeformTasksIdsInDeskInZOrder(deskId = 2)
+ assertThat(orderedTasks[0]).isEqualTo(7)
+ assertThat(orderedTasks[1]).isEqualTo(6)
+ assertThat(orderedTasks[2]).isEqualTo(5)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun addTaskToDesk_visible_addsToVisible() {
+ repo.addDesk(DEFAULT_DISPLAY, deskId = 2)
+
+ repo.addTaskToDesk(displayId = DEFAULT_DISPLAY, deskId = 2, taskId = 5, isVisible = true)
+
+ assertThat(repo.isVisibleTask(5)).isTrue()
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun addTaskToDesk_removesFromAllOtherDesks() {
+ repo.addDesk(DEFAULT_DISPLAY, deskId = 2)
+ repo.addDesk(DEFAULT_DISPLAY, deskId = 3)
+ repo.addTaskToDesk(displayId = DEFAULT_DISPLAY, deskId = 2, taskId = 7, isVisible = true)
+
+ repo.addTaskToDesk(displayId = DEFAULT_DISPLAY, deskId = 3, taskId = 7, isVisible = true)
+
+ assertThat(repo.getActiveTaskIdsInDesk(2)).doesNotContain(7)
+ }
+
+ @Test
fun removeActiveTask_notifiesActiveTaskListener() {
val listener = TestListener()
repo.addActiveTaskListener(listener)
@@ -467,8 +531,8 @@ class DesktopRepositoryTest(flags: FlagsParameterization) : ShellTestCase() {
val listener = TestVisibilityListener()
val executor = TestShellExecutor()
repo.addVisibleTasksListener(listener, executor)
- repo.updateTask(DEFAULT_DISPLAY, taskId = 1, isVisible = true)
- repo.updateTask(DEFAULT_DISPLAY, taskId = 2, isVisible = true)
+ repo.addTask(DEFAULT_DISPLAY, taskId = 1, isVisible = true)
+ repo.addTask(DEFAULT_DISPLAY, taskId = 2, isVisible = true)
executor.flushAll()
assertThat(listener.visibleTasksCountOnDefaultDisplay).isEqualTo(2)
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 0428ba58240d..aa7944cc837f 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
@@ -66,6 +66,7 @@ import android.widget.Toast
import android.window.DisplayAreaInfo
import android.window.IWindowContainerToken
import android.window.RemoteTransition
+import android.window.TransitionInfo
import android.window.TransitionRequestInfo
import android.window.WindowContainerToken
import android.window.WindowContainerTransaction
@@ -290,7 +291,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
.spyStatic(DesktopModeStatus::class.java)
.spyStatic(Toast::class.java)
.startMocking()
- doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
+ doReturn(true).`when` { DesktopModeStatus.isDeviceEligibleForDesktopMode(any()) }
testScope = CoroutineScope(Dispatchers.Unconfined + SupervisorJob())
shellInit = spy(ShellInit(testExecutor))
@@ -519,7 +520,10 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
}
@Test
- @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ @DisableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+ )
fun showDesktopApps_allAppsInvisible_bringsToFront_desktopWallpaperDisabled() {
val homeTask = setUpHomeTask()
val task1 = setUpFreeformTask()
@@ -540,6 +544,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
fun showDesktopApps_allAppsInvisible_bringsToFront_desktopWallpaperEnabled() {
val task1 = setUpFreeformTask()
val task2 = setUpFreeformTask()
@@ -558,6 +563,29 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
}
@Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+ )
+ fun showDesktopApps_deskInactive_bringsToFront_multipleDesksEnabled() {
+ whenever(transitions.startTransition(eq(TRANSIT_TO_FRONT), any(), anyOrNull()))
+ .thenReturn(Binder())
+ val deskId = 0
+ // Make desk inactive by activating another desk.
+ taskRepository.addDesk(DEFAULT_DISPLAY, deskId = 1)
+ taskRepository.setActiveDesk(DEFAULT_DISPLAY, deskId = 1)
+
+ controller.activateDesk(deskId, RemoteTransition(TestRemoteTransition()))
+
+ val wct =
+ getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java)
+ // Wallpaper is moved to front.
+ wct.assertPendingIntentAt(index = 0, desktopWallpaperIntent)
+ // Desk is activated.
+ verify(desksOrganizer).activateDesk(wct, deskId)
+ }
+
+ @Test
fun isDesktopModeShowing_noTasks_returnsFalse() {
assertThat(controller.isDesktopModeShowing(displayId = 0)).isFalse()
}
@@ -631,58 +659,83 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
Flags.FLAG_ENABLE_PER_DISPLAY_DESKTOP_WALLPAPER_ACTIVITY,
)
- @DisableFlags(
- /** TODO: b/362720497 - re-enable when activation is implemented. */
- Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND
- )
- fun showDesktopApps_onSecondaryDisplay_desktopWallpaperEnabled_perDisplayWallpaperEnabled_shouldShowWallpaper() {
+ @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun showDesktopApps_onSecondaryDisplay_desktopWallpaperEnabled_perDisplayWallpaperEnabled_bringsTasksToFront() {
taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY)
- val homeTask = setUpHomeTask(SECOND_DISPLAY)
+ setUpHomeTask(SECOND_DISPLAY)
val task1 = setUpFreeformTask(SECOND_DISPLAY)
val task2 = setUpFreeformTask(SECOND_DISPLAY)
markTaskHidden(task1)
markTaskHidden(task2)
+ assertThat(taskRepository.getExpandedTasksOrdered(SECOND_DISPLAY)).contains(task1.taskId)
+ assertThat(taskRepository.getExpandedTasksOrdered(SECOND_DISPLAY)).contains(task2.taskId)
controller.showDesktopApps(SECOND_DISPLAY, RemoteTransition(TestRemoteTransition()))
val wct =
getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java)
- assertThat(wct.hierarchyOps).hasSize(4)
- // Expect order to be from bottom: home, wallpaperIntent, task1, task2
- wct.assertReorderAt(index = 0, homeTask)
- wct.assertPendingIntentAt(index = 1, desktopWallpaperIntent)
- wct.assertReorderAt(index = 2, task1)
- wct.assertReorderAt(index = 3, task2)
+ wct.assertReorder(task1)
+ wct.assertReorder(task2)
}
@Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- @DisableFlags(
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
Flags.FLAG_ENABLE_PER_DISPLAY_DESKTOP_WALLPAPER_ACTIVITY,
- /** TODO: b/362720497 - re-enable when activation is implemented. */
Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
)
+ fun showDesktopApps_onSecondaryDisplay_desktopWallpaperEnabled_perDisplayWallpaperEnabled_multipleDesksEnabled_bringsDeskToFront() {
+ whenever(transitions.startTransition(eq(TRANSIT_TO_FRONT), any(), anyOrNull()))
+ .thenReturn(Binder())
+ taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = 2)
+ setUpHomeTask(SECOND_DISPLAY)
+
+ controller.showDesktopApps(SECOND_DISPLAY, RemoteTransition(TestRemoteTransition()))
+
+ val wct =
+ getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java)
+ verify(desksOrganizer).activateDesk(wct, deskId = 2)
+ }
+
+ @Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_PER_DISPLAY_DESKTOP_WALLPAPER_ACTIVITY,
+ )
+ fun showDesktopApps_onSecondaryDisplay_desktopWallpaperEnabled_perDisplayWallpaperEnabled_shouldShowWallpaper() {
+ whenever(transitions.startTransition(eq(TRANSIT_TO_FRONT), any(), anyOrNull()))
+ .thenReturn(Binder())
+ taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY)
+ setUpHomeTask(SECOND_DISPLAY)
+
+ controller.showDesktopApps(SECOND_DISPLAY, RemoteTransition(TestRemoteTransition()))
+
+ val wct =
+ getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java)
+ wct.assertPendingIntent(desktopWallpaperIntent)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ @DisableFlags(Flags.FLAG_ENABLE_PER_DISPLAY_DESKTOP_WALLPAPER_ACTIVITY)
fun showDesktopApps_onSecondaryDisplay_desktopWallpaperEnabled_shouldNotShowWallpaper() {
+ whenever(transitions.startTransition(eq(TRANSIT_TO_FRONT), any(), anyOrNull()))
+ .thenReturn(Binder())
taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY)
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)
+ wct.assertWithoutPendingIntent(desktopWallpaperIntent)
}
@Test
- @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ @DisableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+ )
fun showDesktopApps_appsAlreadyVisible_bringsToFront_desktopWallpaperDisabled() {
val homeTask = setUpHomeTask()
val task1 = setUpFreeformTask()
@@ -704,7 +757,6 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
@Test
@DisableFlags(
Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
- /** TODO: b/362720497 - re-enable when activation is implemented. */
Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
)
fun showDesktopApps_onSecondaryDisplay_desktopWallpaperDisabled_shouldNotMoveLauncher() {
@@ -728,6 +780,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
fun showDesktopApps_appsAlreadyVisible_bringsToFront_desktopWallpaperEnabled() {
val task1 = setUpFreeformTask()
val task2 = setUpFreeformTask()
@@ -746,7 +799,10 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
}
@Test
- @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ @DisableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+ )
fun showDesktopApps_someAppsInvisible_reordersAll_desktopWallpaperDisabled() {
val homeTask = setUpHomeTask()
val task1 = setUpFreeformTask()
@@ -767,6 +823,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
fun showDesktopApps_someAppsInvisible_reordersAll_desktopWallpaperEnabled() {
val task1 = setUpFreeformTask()
val task2 = setUpFreeformTask()
@@ -785,7 +842,10 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
}
@Test
- @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ @DisableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+ )
fun showDesktopApps_noActiveTasks_reorderHomeToTop_desktopWallpaperDisabled() {
val homeTask = setUpHomeTask()
@@ -800,6 +860,8 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
fun showDesktopApps_noActiveTasks_addDesktopWallpaper_desktopWallpaperEnabled() {
+ whenever(transitions.startTransition(eq(TRANSIT_TO_FRONT), any(), anyOrNull()))
+ .thenReturn(Binder())
controller.showDesktopApps(DEFAULT_DISPLAY, RemoteTransition(TestRemoteTransition()))
val wct =
@@ -808,7 +870,10 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
}
@Test
- @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ @DisableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+ )
fun showDesktopApps_twoDisplays_bringsToFrontOnlyOneDisplay_desktopWallpaperDisabled() {
taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY)
val homeTaskDefaultDisplay = setUpHomeTask(DEFAULT_DISPLAY)
@@ -831,6 +896,8 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
fun showDesktopApps_twoDisplays_bringsToFrontOnlyOneDisplay_desktopWallpaperEnabled() {
+ whenever(transitions.startTransition(eq(TRANSIT_TO_FRONT), any(), anyOrNull()))
+ .thenReturn(Binder())
taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY)
val homeTaskDefaultDisplay = setUpHomeTask(DEFAULT_DISPLAY)
val taskDefaultDisplay = setUpFreeformTask(DEFAULT_DISPLAY)
@@ -843,17 +910,63 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
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)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun showDesktopApps_twoDisplays_bringsToFrontOnlyOneDisplayTasks_desktopWallpaperEnabled_multiDesksDisabled() {
+ whenever(transitions.startTransition(eq(TRANSIT_TO_FRONT), any(), anyOrNull()))
+ .thenReturn(Binder())
+ taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY)
+ 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)
// Move freeform task to front
wct.assertReorderAt(index = 2, taskDefaultDisplay)
}
@Test
- @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+ )
+ fun showDesktopApps_twoDisplays_bringsToFrontOnlyOneDisplayTasks_desktopWallpaperEnabled_multiDesksEnabled() {
+ whenever(transitions.startTransition(eq(TRANSIT_TO_FRONT), any(), anyOrNull()))
+ .thenReturn(Binder())
+ taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY)
+ 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)
+ // Move desktop tasks to front
+ verify(desksOrganizer).activateDesk(wct, deskId = DEFAULT_DISPLAY)
+ }
+
+ @Test
+ @DisableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+ )
fun showDesktopApps_desktopWallpaperDisabled_dontReorderMinimizedTask() {
val homeTask = setUpHomeTask()
val freeformTask = setUpFreeformTask()
@@ -874,6 +987,8 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ /** TODO: b/362720497 - add multi-desk version when minimization is implemented. */
+ @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
fun showDesktopApps_desktopWallpaperEnabled_dontReorderMinimizedTask() {
val homeTask = setUpHomeTask()
val freeformTask = setUpFreeformTask()
@@ -1290,11 +1405,12 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
}
@Test
+ @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
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)
+ controller.moveTaskToDefaultDeskAndActivate(task.taskId, transitionSource = UNKNOWN)
val wct = getLatestEnterDesktopWct()
assertThat(wct.changes[task.token.asBinder()]?.windowingMode)
.isEqualTo(WINDOWING_MODE_FREEFORM)
@@ -1303,11 +1419,12 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
}
@Test
+ @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
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)
+ controller.moveTaskToDefaultDeskAndActivate(task.taskId, transitionSource = UNKNOWN)
val wct = getLatestEnterDesktopWct()
assertThat(wct.changes[task.token.asBinder()]?.windowingMode)
.isEqualTo(WINDOWING_MODE_UNDEFINED)
@@ -1316,11 +1433,78 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
}
@Test
- fun moveTaskToDesktop_nonExistentTask_doesNothing() {
- controller.moveTaskToDesktop(999, transitionSource = UNKNOWN)
- verifyEnterDesktopWCTNotExecuted()
- verify(desktopModeEnterExitTransitionListener, times(0))
- .onEnterDesktopModeTransitionStarted(anyInt())
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun moveRunningTaskToDesktop_movesTaskToDefaultDesk() {
+ val task = setUpFullscreenTask(displayId = DEFAULT_DISPLAY)
+
+ controller.moveTaskToDefaultDeskAndActivate(task.taskId, transitionSource = UNKNOWN)
+
+ val wct = getLatestEnterDesktopWct()
+ verify(desksOrganizer).moveTaskToDesk(wct, deskId = 0, task)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun moveRunningTaskToDesktop_activatesDesk() {
+ val task = setUpFullscreenTask(displayId = DEFAULT_DISPLAY)
+
+ controller.moveTaskToDefaultDeskAndActivate(task.taskId, transitionSource = UNKNOWN)
+
+ val wct = getLatestEnterDesktopWct()
+ verify(desksOrganizer).activateDesk(wct, deskId = 0)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun moveRunningTaskToDesktop_triggersEnterDesktopListener() {
+ val task = setUpFullscreenTask(displayId = DEFAULT_DISPLAY)
+
+ controller.moveTaskToDefaultDeskAndActivate(task.taskId, transitionSource = UNKNOWN)
+
+ verify(desktopModeEnterExitTransitionListener)
+ .onEnterDesktopModeTransitionStarted(FREEFORM_ANIMATION_DURATION)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun moveTaskToDesk_nonDefaultDesk_movesTaskToDesk() {
+ val transition = Binder()
+ whenever(enterDesktopTransitionHandler.moveToDesktop(any(), any())).thenReturn(transition)
+ taskRepository.addDesk(DEFAULT_DISPLAY, deskId = 3)
+ val task = setUpFullscreenTask(displayId = DEFAULT_DISPLAY)
+ task.isVisible = true
+
+ controller.moveTaskToDesk(taskId = task.taskId, deskId = 3, transitionSource = UNKNOWN)
+
+ val wct = getLatestEnterDesktopWct()
+ verify(desksOrganizer).moveTaskToDesk(wct, deskId = 3, task)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun moveTaskToDesk_nonDefaultDesk_activatesDesk() {
+ val transition = Binder()
+ whenever(enterDesktopTransitionHandler.moveToDesktop(any(), any())).thenReturn(transition)
+ taskRepository.addDesk(DEFAULT_DISPLAY, deskId = 3)
+ val task = setUpFullscreenTask(displayId = DEFAULT_DISPLAY)
+ task.isVisible = true
+
+ controller.moveTaskToDesk(taskId = task.taskId, deskId = 3, transitionSource = UNKNOWN)
+
+ val wct = getLatestEnterDesktopWct()
+ verify(desksOrganizer).activateDesk(wct, deskId = 3)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun moveTaskToDesk_nonDefaultDesk_triggersEnterDesktopListener() {
+ taskRepository.addDesk(DEFAULT_DISPLAY, deskId = 3)
+ val task = setUpFullscreenTask(displayId = DEFAULT_DISPLAY)
+
+ controller.moveTaskToDesk(taskId = task.taskId, deskId = 3, transitionSource = UNKNOWN)
+
+ verify(desktopModeEnterExitTransitionListener)
+ .onEnterDesktopModeTransitionStarted(FREEFORM_ANIMATION_DURATION)
}
@Test
@@ -1330,7 +1514,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
whenever(shellTaskOrganizer.getRunningTaskInfo(anyInt())).thenReturn(null)
whenever(recentTasksController.findTaskInBackground(anyInt())).thenReturn(task)
- controller.moveTaskToDesktop(task.taskId, transitionSource = UNKNOWN)
+ controller.moveTaskToDefaultDeskAndActivate(task.taskId, transitionSource = UNKNOWN)
with(getLatestEnterDesktopWct()) {
assertLaunchTaskAt(0, task.taskId, WINDOWING_MODE_FREEFORM)
@@ -1344,7 +1528,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
whenever(shellTaskOrganizer.getRunningTaskInfo(anyInt())).thenReturn(null)
whenever(recentTasksController.findTaskInBackground(anyInt())).thenReturn(task)
- controller.moveTaskToDesktop(task.taskId, transitionSource = UNKNOWN)
+ controller.moveTaskToDefaultDeskAndActivate(task.taskId, transitionSource = UNKNOWN)
with(getLatestEnterDesktopWct()) {
// Add desktop wallpaper activity
@@ -1356,7 +1540,8 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
- fun moveRunningTaskToDesktop_topActivityTranslucentWithoutDisplay_taskIsMovedToDesktop() {
+ @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun moveRunningTaskToDesktop_topActivityTranslucentWithoutDisplay_taskIsMovedToDesktop_multiDesksDisabled() {
val task =
setUpFullscreenTask().apply {
isActivityStackTransparent = true
@@ -1364,7 +1549,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
numActivities = 1
}
- controller.moveRunningTaskToDesktop(task, transitionSource = UNKNOWN)
+ controller.moveTaskToDefaultDeskAndActivate(task.taskId, transitionSource = UNKNOWN)
val wct = getLatestEnterDesktopWct()
assertThat(wct.changes[task.token.asBinder()]?.windowingMode)
.isEqualTo(WINDOWING_MODE_FREEFORM)
@@ -1373,6 +1558,26 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
}
@Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY,
+ Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+ )
+ fun moveRunningTaskToDesktop_topActivityTranslucentWithoutDisplay_taskIsMovedToDesktop_multiDesksEnabled() {
+ val task =
+ setUpFullscreenTask().apply {
+ isActivityStackTransparent = true
+ isTopActivityNoDisplay = true
+ numActivities = 1
+ }
+
+ controller.moveTaskToDefaultDeskAndActivate(task.taskId, transitionSource = UNKNOWN)
+ val wct = getLatestEnterDesktopWct()
+ verify(desksOrganizer).moveTaskToDesk(wct, deskId = 0, task = task)
+ verify(desktopModeEnterExitTransitionListener)
+ .onEnterDesktopModeTransitionStarted(FREEFORM_ANIMATION_DURATION)
+ }
+
+ @Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
fun moveRunningTaskToDesktop_topActivityTranslucentWithDisplay_doesNothing() {
val task =
@@ -1382,7 +1587,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
numActivities = 1
}
- controller.moveRunningTaskToDesktop(task, transitionSource = UNKNOWN)
+ controller.moveTaskToDefaultDeskAndActivate(task.taskId, transitionSource = UNKNOWN)
verifyEnterDesktopWCTNotExecuted()
verify(desktopModeEnterExitTransitionListener, times(0))
.onEnterDesktopModeTransitionStarted(FREEFORM_ANIMATION_DURATION)
@@ -1401,13 +1606,14 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
isTopActivityNoDisplay = false
}
- controller.moveRunningTaskToDesktop(task, transitionSource = UNKNOWN)
+ controller.moveTaskToDefaultDeskAndActivate(task.taskId, transitionSource = UNKNOWN)
verifyEnterDesktopWCTNotExecuted()
}
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
- fun moveRunningTaskToDesktop_systemUIActivityWithoutDisplay_doesNothing() {
+ @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun moveRunningTaskToDesktop_systemUIActivityWithoutDisplay_doesNothing_multiDesksDisabled() {
// Set task as systemUI package
val systemUIPackageName =
context.resources.getString(com.android.internal.R.string.config_systemUi)
@@ -1418,7 +1624,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
isTopActivityNoDisplay = true
}
- controller.moveRunningTaskToDesktop(task, transitionSource = UNKNOWN)
+ controller.moveTaskToDefaultDeskAndActivate(task.taskId, transitionSource = UNKNOWN)
val wct = getLatestEnterDesktopWct()
assertThat(wct.changes[task.token.asBinder()]?.windowingMode)
@@ -1437,7 +1643,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
mContext.setMockPackageManager(packageManager)
whenever(packageManager.getHomeActivities(any())).thenReturn(homeActivities)
- controller.moveRunningTaskToDesktop(task, transitionSource = UNKNOWN)
+ controller.moveTaskToDefaultDeskAndActivate(task.taskId, transitionSource = UNKNOWN)
verifyEnterDesktopWCTNotExecuted()
}
@@ -1453,7 +1659,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
mContext.setMockPackageManager(packageManager)
whenever(packageManager.getHomeActivities(any())).thenReturn(homeActivities)
- controller.moveRunningTaskToDesktop(task, transitionSource = UNKNOWN)
+ controller.moveTaskToDefaultDeskAndActivate(task.taskId, transitionSource = UNKNOWN)
val wct = getLatestEnterDesktopWct()
assertThat(wct.changes[task.token.asBinder()]?.windowingMode)
@@ -1461,6 +1667,28 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
}
@Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY,
+ Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+ )
+ fun moveRunningTaskToDesktop_systemUIActivityWithoutDisplay_doesNothing_multiDesksEnabled() {
+ // Set task as systemUI package
+ val systemUIPackageName =
+ context.resources.getString(com.android.internal.R.string.config_systemUi)
+ val baseComponent = ComponentName(systemUIPackageName, /* cls= */ "")
+ val task =
+ setUpFullscreenTask().apply {
+ baseActivity = baseComponent
+ isTopActivityNoDisplay = true
+ }
+
+ controller.moveTaskToDefaultDeskAndActivate(task.taskId, transitionSource = UNKNOWN)
+
+ val wct = getLatestEnterDesktopWct()
+ verify(desksOrganizer).moveTaskToDesk(wct, deskId = 0, task = task)
+ }
+
+ @Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
fun moveBackgroundTaskToDesktop_remoteTransition_usesOneShotHandler() {
val transitionHandlerArgCaptor = ArgumentCaptor.forClass(TransitionHandler::class.java)
@@ -1470,7 +1698,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
val task = createTaskInfo(1)
whenever(shellTaskOrganizer.getRunningTaskInfo(anyInt())).thenReturn(null)
whenever(recentTasksController.findTaskInBackground(anyInt())).thenReturn(task)
- controller.moveTaskToDesktop(
+ controller.moveTaskToDefaultDeskAndActivate(
taskId = task.taskId,
transitionSource = UNKNOWN,
remoteTransition = RemoteTransition(spy(TestRemoteTransition())),
@@ -1487,8 +1715,8 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
whenever(transitions.startTransition(anyInt(), any(), transitionHandlerArgCaptor.capture()))
.thenReturn(Binder())
- controller.moveRunningTaskToDesktop(
- task = setUpFullscreenTask(),
+ controller.moveTaskToDefaultDeskAndActivate(
+ taskId = setUpFullscreenTask().taskId,
transitionSource = UNKNOWN,
remoteTransition = RemoteTransition(spy(TestRemoteTransition())),
)
@@ -1499,14 +1727,20 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
}
@Test
- @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ @DisableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+ )
fun moveRunningTaskToDesktop_otherFreeformTasksBroughtToFront_desktopWallpaperDisabled() {
val homeTask = setUpHomeTask()
val freeformTask = setUpFreeformTask()
val fullscreenTask = setUpFullscreenTask()
markTaskHidden(freeformTask)
- controller.moveRunningTaskToDesktop(fullscreenTask, transitionSource = UNKNOWN)
+ controller.moveTaskToDefaultDeskAndActivate(
+ fullscreenTask.taskId,
+ transitionSource = UNKNOWN,
+ )
with(getLatestEnterDesktopWct()) {
// Operations should include home task, freeform task
@@ -1521,12 +1755,16 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
fun moveRunningTaskToDesktop_otherFreeformTasksBroughtToFront_desktopWallpaperEnabled() {
val freeformTask = setUpFreeformTask()
val fullscreenTask = setUpFullscreenTask()
markTaskHidden(freeformTask)
- controller.moveRunningTaskToDesktop(fullscreenTask, transitionSource = UNKNOWN)
+ controller.moveTaskToDefaultDeskAndActivate(
+ fullscreenTask.taskId,
+ transitionSource = UNKNOWN,
+ )
with(getLatestEnterDesktopWct()) {
// Operations should include wallpaper intent, freeform task, fullscreen task
@@ -1542,6 +1780,43 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
}
@Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+ )
+ fun moveRunningTaskToDesktop_desktopWallpaperEnabled_multiDesksEnabled() {
+ val freeformTask = setUpFreeformTask()
+ val fullscreenTask = setUpFullscreenTask()
+ markTaskHidden(freeformTask)
+
+ controller.moveTaskToDefaultDeskAndActivate(
+ fullscreenTask.taskId,
+ transitionSource = UNKNOWN,
+ )
+
+ val wct = getLatestEnterDesktopWct()
+ wct.assertPendingIntentAt(index = 0, desktopWallpaperIntent)
+ verify(desksOrganizer).moveTaskToDesk(wct, deskId = 0, fullscreenTask)
+ verify(desksOrganizer).activateDesk(wct, deskId = 0)
+ verify(desktopModeEnterExitTransitionListener)
+ .onEnterDesktopModeTransitionStarted(FREEFORM_ANIMATION_DURATION)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun moveRunningTaskToDesktop_activatesDesk_desktopWallpaperEnabled_multiDesksDisabled() {
+ val fullscreenTask = setUpFullscreenTask()
+
+ controller.moveTaskToDefaultDeskAndActivate(
+ fullscreenTask.taskId,
+ transitionSource = UNKNOWN,
+ )
+
+ assertThat(taskRepository.getActiveDeskId(DEFAULT_DISPLAY)).isEqualTo(DEFAULT_DISPLAY)
+ }
+
+ @Test
fun moveRunningTaskToDesktop_onlyFreeformTasksFromCurrentDisplayBroughtToFront() {
setUpHomeTask(displayId = DEFAULT_DISPLAY)
val freeformTaskDefault = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
@@ -1553,7 +1828,10 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
val freeformTaskSecond = setUpFreeformTask(displayId = SECOND_DISPLAY)
markTaskHidden(freeformTaskSecond)
- controller.moveRunningTaskToDesktop(fullscreenTaskDefault, transitionSource = UNKNOWN)
+ controller.moveTaskToDefaultDeskAndActivate(
+ fullscreenTaskDefault.taskId,
+ transitionSource = UNKNOWN,
+ )
with(getLatestEnterDesktopWct()) {
// Check that hierarchy operations do not include tasks from second display
@@ -1567,9 +1845,10 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
}
@Test
- fun moveRunningTaskToDesktop_splitTaskExitsSplit() {
+ @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun moveRunningTaskToDesktop_splitTaskExitsSplit_multiDesksDisabled() {
val task = setUpSplitScreenTask()
- controller.moveRunningTaskToDesktop(task, transitionSource = UNKNOWN)
+ controller.moveTaskToDefaultDeskAndActivate(task.taskId, transitionSource = UNKNOWN)
val wct = getLatestEnterDesktopWct()
assertThat(wct.changes[task.token.asBinder()]?.windowingMode)
.isEqualTo(WINDOWING_MODE_FREEFORM)
@@ -1584,12 +1863,27 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
}
@Test
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun moveRunningTaskToDesktop_splitTaskExitsSplit_multiDesksEnabled() {
+ val task = setUpSplitScreenTask()
+ controller.moveTaskToDefaultDeskAndActivate(task.taskId, transitionSource = UNKNOWN)
+ val wct = getLatestEnterDesktopWct()
+ verify(desksOrganizer).moveTaskToDesk(wct, deskId = 0, task)
+ 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)
+ controller.moveTaskToDefaultDeskAndActivate(task.taskId, 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())
@@ -1601,13 +1895,16 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
}
@Test
- @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ @DisableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+ Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+ )
fun moveRunningTaskToDesktop_desktopWallpaperDisabled_bringsTasksOver_dontShowBackTask() {
val freeformTasks = (1..MAX_TASK_LIMIT).map { _ -> setUpFreeformTask() }
val newTask = setUpFullscreenTask()
val homeTask = setUpHomeTask()
- controller.moveRunningTaskToDesktop(newTask, transitionSource = UNKNOWN)
+ controller.moveTaskToDefaultDeskAndActivate(newTask.taskId, transitionSource = UNKNOWN)
val wct = getLatestEnterDesktopWct()
verify(desktopModeEnterExitTransitionListener)
@@ -1623,12 +1920,13 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+ @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
fun moveRunningTaskToDesktop_desktopWallpaperEnabled_bringsTasksOverLimit_dontShowBackTask() {
val freeformTasks = (1..MAX_TASK_LIMIT).map { _ -> setUpFreeformTask() }
val newTask = setUpFullscreenTask()
val homeTask = setUpHomeTask()
- controller.moveRunningTaskToDesktop(newTask, transitionSource = UNKNOWN)
+ controller.moveTaskToDefaultDeskAndActivate(newTask.taskId, transitionSource = UNKNOWN)
val wct = getLatestEnterDesktopWct()
verify(desktopModeEnterExitTransitionListener)
@@ -3449,7 +3747,8 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
}
@Test
- fun moveFocusedTaskToDesktop_fullscreenTaskIsMovedToDesktop() {
+ @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun moveFocusedTaskToDesktop_fullscreenTaskIsMovedToDesktop_multiDesksDisabled() {
val task1 = setUpFullscreenTask()
val task2 = setUpFullscreenTask()
val task3 = setUpFullscreenTask()
@@ -3466,7 +3765,25 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
}
@Test
- fun moveFocusedTaskToDesktop_splitScreenTaskIsMovedToDesktop() {
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun moveFocusedTaskToDesktop_fullscreenTaskIsMovedToDesktop_multiDesksEnabled() {
+ 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()
+ verify(desksOrganizer).moveTaskToDesk(wct, deskId = 0, task1)
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun moveFocusedTaskToDesktop_splitScreenTaskIsMovedToDesktop_multiDesksDisabled() {
val task1 = setUpSplitScreenTask()
val task2 = setUpFullscreenTask()
val task3 = setUpFullscreenTask()
@@ -3493,6 +3810,33 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
}
@Test
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun moveFocusedTaskToDesktop_splitScreenTaskIsMovedToDesktop_multiDesksEnabled() {
+ 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()
+ verify(desksOrganizer).moveTaskToDesk(wct, deskId = 0, task4)
+ verify(splitScreenController)
+ .prepareExitSplitScreen(
+ any(),
+ anyInt(),
+ eq(SplitScreenController.EXIT_REASON_DESKTOP_MODE),
+ )
+ }
+
+ @Test
fun moveFocusedTaskToFullscreen() {
val task1 = setUpFreeformTask()
val task2 = setUpFreeformTask()
@@ -3641,6 +3985,59 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
}
@Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION,
+ Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+ )
+ fun activateDesk_multipleDesks_addsPendingTransition() {
+ val deskId = 0
+ val transition = Binder()
+ val deskChange = mock(TransitionInfo.Change::class.java)
+ whenever(transitions.startTransition(eq(TRANSIT_TO_FRONT), any(), anyOrNull()))
+ .thenReturn(transition)
+ whenever(desksOrganizer.isDeskActiveAtEnd(deskChange, deskId)).thenReturn(true)
+ // Make desk inactive by activating another desk.
+ taskRepository.addDesk(DEFAULT_DISPLAY, deskId = 1)
+ taskRepository.setActiveDesk(DEFAULT_DISPLAY, deskId = 1)
+
+ controller.activateDesk(deskId, RemoteTransition(TestRemoteTransition()))
+
+ verify(desksTransitionsObserver)
+ .addPendingTransition(
+ argThat {
+ this is DeskTransition.ActivateDesk &&
+ this.token == transition &&
+ this.deskId == 0
+ }
+ )
+ }
+
+ @Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION,
+ Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+ )
+ fun moveTaskToDesk_multipleDesks_addsPendingTransition() {
+ val transition = Binder()
+ whenever(enterDesktopTransitionHandler.moveToDesktop(any(), any())).thenReturn(transition)
+ taskRepository.addDesk(DEFAULT_DISPLAY, deskId = 3)
+ val task = setUpFullscreenTask(displayId = DEFAULT_DISPLAY)
+ task.isVisible = true
+
+ controller.moveTaskToDesk(taskId = task.taskId, deskId = 3, transitionSource = UNKNOWN)
+
+ verify(desksTransitionsObserver)
+ .addPendingTransition(
+ argThat {
+ this is DeskTransition.ActiveDeskWithTask &&
+ this.token == transition &&
+ this.deskId == 3 &&
+ this.enterTaskId == task.taskId
+ }
+ )
+ }
+
+ @Test
@EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
fun dragToDesktop_landscapeDevice_resizable_undefinedOrientation_defaultLandscapeBounds() {
val spyController = spy(controller)
@@ -5011,7 +5408,11 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
.thenReturn(ExitResult.Exit(exitingTask = 5, runOnTransitionStart = runOnStartTransit))
whenever(enterDesktopTransitionHandler.moveToDesktop(wct, UNKNOWN)).thenReturn(transition)
- controller.moveTaskToDesktop(taskId = task.taskId, wct = wct, transitionSource = UNKNOWN)
+ controller.moveTaskToDefaultDeskAndActivate(
+ taskId = task.taskId,
+ wct = wct,
+ transitionSource = UNKNOWN,
+ )
verify(mMockDesktopImmersiveController)
.exitImmersiveIfApplicable(eq(wct), eq(task.displayId), eq(task.taskId), any())
@@ -5035,7 +5436,11 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
.thenReturn(ExitResult.Exit(exitingTask = 5, runOnTransitionStart = runOnStartTransit))
whenever(enterDesktopTransitionHandler.moveToDesktop(wct, UNKNOWN)).thenReturn(transition)
- controller.moveTaskToDesktop(taskId = task.taskId, wct = wct, transitionSource = UNKNOWN)
+ controller.moveTaskToDefaultDeskAndActivate(
+ taskId = task.taskId,
+ wct = wct,
+ transitionSource = UNKNOWN,
+ )
verify(mMockDesktopImmersiveController)
.exitImmersiveIfApplicable(eq(wct), eq(task.displayId), eq(task.taskId), any())
@@ -5617,6 +6022,29 @@ private fun WindowContainerTransaction.assertIndexInBounds(index: Int) {
.isGreaterThan(index)
}
+private fun WindowContainerTransaction.assertHop(
+ predicate: (WindowContainerTransaction.HierarchyOp) -> Boolean
+) {
+ assertThat(hierarchyOps.any(predicate)).isTrue()
+}
+
+private fun WindowContainerTransaction.assertWithoutHop(
+ predicate: (WindowContainerTransaction.HierarchyOp) -> Boolean
+) {
+ assertThat(hierarchyOps.none(predicate)).isTrue()
+}
+
+private fun WindowContainerTransaction.assertReorder(
+ task: RunningTaskInfo,
+ toTop: Boolean? = null,
+) {
+ assertHop { hop ->
+ hop.type == HIERARCHY_OP_TYPE_REORDER &&
+ (toTop == null || hop.toTop == toTop) &&
+ hop.container == task.token.asBinder()
+ }
+}
+
private fun WindowContainerTransaction.assertReorderAt(
index: Int,
task: RunningTaskInfo,
@@ -5678,6 +6106,20 @@ private fun WindowContainerTransaction.hasRemoveAt(index: Int, token: WindowCont
assertThat(op.container).isEqualTo(token.asBinder())
}
+private fun WindowContainerTransaction.assertPendingIntent(intent: Intent) {
+ assertHop { hop ->
+ hop.type == HIERARCHY_OP_TYPE_PENDING_INTENT &&
+ hop.pendingIntent?.intent?.component == intent.component
+ }
+}
+
+private fun WindowContainerTransaction.assertWithoutPendingIntent(intent: Intent) {
+ assertWithoutHop { hop ->
+ hop.type == HIERARCHY_OP_TYPE_PENDING_INTENT &&
+ hop.pendingIntent?.intent?.component == intent.component
+ }
+}
+
private fun WindowContainerTransaction.assertPendingIntentAt(index: Int, intent: Intent) {
assertIndexInBounds(index)
val op = hierarchyOps[index]
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 33dc1aadf548..25246d9984c3 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
@@ -477,6 +477,40 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() {
}
@Test
+ fun mergeAnimation_endTransition_springHandler_noStartHomeChange_doesntCrash() {
+ whenever(dragAnimator.computeCurrentVelocity()).thenReturn(PointF())
+ val playingFinishTransaction = mock<SurfaceControl.Transaction>()
+ val mergedStartTransaction = mock<SurfaceControl.Transaction>()
+ val mergedFinishTransaction = mock<SurfaceControl.Transaction>()
+ val finishCallback = mock<Transitions.TransitionFinishCallback>()
+ val task = createTask()
+ val startTransition = startDrag(
+ springHandler, task, finishTransaction = playingFinishTransaction, homeChange = null)
+ springHandler.onTaskResizeAnimationListener = mock()
+
+ springHandler.mergeAnimation(
+ transition = mock<IBinder>(),
+ info =
+ createTransitionInfo(
+ type = TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP,
+ draggedTask = task,
+ ),
+ startT = mergedStartTransaction,
+ finishT = mergedFinishTransaction,
+ mergeTarget = startTransition,
+ finishCallback = finishCallback,
+ )
+
+ // Should show dragged task layer in start and finish transaction
+ verify(mergedStartTransaction).show(draggedTaskLeash)
+ verify(playingFinishTransaction).show(draggedTaskLeash)
+ // Should update the dragged task layer
+ verify(mergedStartTransaction).setLayer(eq(draggedTaskLeash), anyInt())
+ // Should merge animation
+ verify(finishCallback).onTransitionFinished(null)
+ }
+
+ @Test
fun propertyValue_returnsSystemPropertyValue() {
val name = "property_name"
val value = 10f
@@ -589,6 +623,7 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() {
handler: DragToDesktopTransitionHandler,
task: RunningTaskInfo = createTask(),
finishTransaction: SurfaceControl.Transaction = mock(),
+ homeChange: TransitionInfo.Change? = createHomeChange(),
): IBinder {
whenever(dragAnimator.position).thenReturn(PointF())
// Simulate transition is started and is ready to animate.
@@ -599,6 +634,7 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() {
createTransitionInfo(
type = TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP,
draggedTask = task,
+ homeChange = homeChange,
),
startTransaction = mock(),
finishTransaction = finishTransaction,
@@ -684,16 +720,12 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() {
}
}
- private fun createTransitionInfo(type: Int, draggedTask: RunningTaskInfo) =
+ private fun createTransitionInfo(
+ type: Int,
+ draggedTask: RunningTaskInfo,
+ homeChange: TransitionInfo.Change? = createHomeChange()) =
TransitionInfo(type, /* flags= */ 0).apply {
- addChange( // Home.
- TransitionInfo.Change(mock(), homeTaskLeash).apply {
- parent = null
- taskInfo =
- TestRunningTaskInfoBuilder().setActivityType(ACTIVITY_TYPE_HOME).build()
- flags = flags or FLAG_IS_WALLPAPER
- }
- )
+ homeChange?.let { addChange(it) }
addChange( // Dragged Task.
TransitionInfo.Change(mock(), draggedTaskLeash).apply {
parent = null
@@ -709,6 +741,12 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() {
)
}
+ private fun createHomeChange() = TransitionInfo.Change(mock(), homeTaskLeash).apply {
+ parent = null
+ taskInfo = TestRunningTaskInfoBuilder().setActivityType(ACTIVITY_TYPE_HOME).build()
+ flags = flags or FLAG_IS_WALLPAPER
+ }
+
private fun systemPropertiesKey(name: String) =
"${SpringDragToDesktopTransitionHandler.SYSTEM_PROPERTIES_GROUP}.$name"
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/multidesks/DesksTransitionObserverTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/multidesks/DesksTransitionObserverTest.kt
index bfbaa84e9312..9f09e3f57927 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/multidesks/DesksTransitionObserverTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/multidesks/DesksTransitionObserverTest.kt
@@ -21,20 +21,26 @@ import android.platform.test.flag.junit.SetFlagsRule
import android.testing.AndroidTestingRunner
import android.view.Display.DEFAULT_DISPLAY
import android.view.WindowManager.TRANSIT_CLOSE
+import android.view.WindowManager.TRANSIT_TO_FRONT
import android.window.TransitionInfo
+import android.window.TransitionInfo.Change
import androidx.test.filters.SmallTest
import com.android.window.flags.Flags
import com.android.wm.shell.ShellTestCase
import com.android.wm.shell.TestShellExecutor
import com.android.wm.shell.desktopmode.DesktopRepository
+import com.android.wm.shell.desktopmode.DesktopTestHelpers.createFreeformTask
import com.android.wm.shell.desktopmode.DesktopUserRepositories
import com.android.wm.shell.sysui.ShellInit
import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
/**
* Tests for [DesksTransitionObserver].
@@ -47,6 +53,9 @@ class DesksTransitionObserverTest : ShellTestCase() {
@JvmField @Rule val setFlagsRule = SetFlagsRule()
+ private val mockDesksOrganizer = mock<DesksOrganizer>()
+ val testScope = TestScope()
+
private lateinit var desktopUserRepositories: DesktopUserRepositories
private lateinit var observer: DesksTransitionObserver
@@ -62,10 +71,10 @@ class DesksTransitionObserverTest : ShellTestCase() {
/* shellController= */ mock(),
/* persistentRepository= */ mock(),
/* repositoryInitializer= */ mock(),
- /* mainCoroutineScope= */ mock(),
+ testScope,
/* userManager= */ mock(),
)
- observer = DesksTransitionObserver(desktopUserRepositories)
+ observer = DesksTransitionObserver(desktopUserRepositories, mockDesksOrganizer)
}
@Test
@@ -121,4 +130,51 @@ class DesksTransitionObserverTest : ShellTestCase() {
assertThat(removeListener.lastDeskRemoved).isEqualTo(5)
}
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun onTransitionReady_activateDesk_updatesRepository() {
+ val transition = Binder()
+ val change = Change(mock(), mock())
+ whenever(mockDesksOrganizer.isDeskActiveAtEnd(change, deskId = 5)).thenReturn(true)
+ val activateTransition =
+ DeskTransition.ActivateDesk(transition, displayId = DEFAULT_DISPLAY, deskId = 5)
+ repository.addDesk(DEFAULT_DISPLAY, deskId = 5)
+
+ observer.addPendingTransition(activateTransition)
+ observer.onTransitionReady(
+ transition = transition,
+ info = TransitionInfo(TRANSIT_TO_FRONT, /* flags= */ 0).apply { addChange(change) },
+ )
+
+ assertThat(repository.getActiveDeskId(DEFAULT_DISPLAY)).isEqualTo(5)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun onTransitionReady_activateDeskWithTask_updatesRepository() =
+ testScope.runTest {
+ val deskId = 5
+ val task = createFreeformTask(DEFAULT_DISPLAY).apply { isVisibleRequested = true }
+ val transition = Binder()
+ val change = Change(mock(), mock()).apply { taskInfo = task }
+ whenever(mockDesksOrganizer.getDeskAtEnd(change)).thenReturn(deskId)
+ val activateTransition =
+ DeskTransition.ActiveDeskWithTask(
+ transition,
+ displayId = DEFAULT_DISPLAY,
+ deskId = deskId,
+ enterTaskId = task.taskId,
+ )
+ repository.addDesk(DEFAULT_DISPLAY, deskId = deskId)
+
+ observer.addPendingTransition(activateTransition)
+ observer.onTransitionReady(
+ transition = transition,
+ info = TransitionInfo(TRANSIT_TO_FRONT, /* flags= */ 0).apply { addChange(change) },
+ )
+
+ assertThat(repository.getActiveDeskId(DEFAULT_DISPLAY)).isEqualTo(deskId)
+ assertThat(repository.getActiveTaskIdsInDesk(deskId)).contains(task.taskId)
+ }
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/multidesks/RootTaskDesksOrganizerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/multidesks/RootTaskDesksOrganizerTest.kt
index a07203d86b75..4d4b15389eca 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/multidesks/RootTaskDesksOrganizerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/multidesks/RootTaskDesksOrganizerTest.kt
@@ -15,9 +15,11 @@
*/
package com.android.wm.shell.desktopmode.multidesks
+import android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED
import android.testing.AndroidTestingRunner
import android.view.Display
import android.view.SurfaceControl
+import android.view.WindowManager.TRANSIT_TO_FRONT
import android.window.TransitionInfo
import android.window.WindowContainerTransaction
import android.window.WindowContainerTransaction.HierarchyOp
@@ -216,6 +218,13 @@ class RootTaskDesksOrganizerTest : ShellTestCase() {
}
)
.isTrue()
+ assertThat(
+ wct.changes.any { change ->
+ change.key == desktopTask.token.asBinder() &&
+ change.value.windowingMode == WINDOWING_MODE_UNDEFINED
+ }
+ )
+ .isTrue()
}
@Test
@@ -244,6 +253,26 @@ class RootTaskDesksOrganizerTest : ShellTestCase() {
assertThat(endDesk).isEqualTo(freeformRoot.taskId)
}
+ @Test
+ fun testIsDeskActiveAtEnd() {
+ organizer.createDesk(Display.DEFAULT_DISPLAY, FakeOnCreateCallback())
+ val freeformRoot = createFreeformTask().apply { parentTaskId = -1 }
+ freeformRoot.isVisibleRequested = true
+ organizer.onTaskAppeared(freeformRoot, SurfaceControl())
+
+ val isActive =
+ organizer.isDeskActiveAtEnd(
+ change =
+ TransitionInfo.Change(freeformRoot.token, SurfaceControl()).apply {
+ taskInfo = freeformRoot
+ mode = TRANSIT_TO_FRONT
+ },
+ deskId = freeformRoot.taskId,
+ )
+
+ assertThat(isActive).isTrue()
+ }
+
private class FakeOnCreateCallback : DesksOrganizer.OnCreateCallback {
var deskId: Int? = null
val created: Boolean
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropControllerTest.java
index 1b1a5a909220..06dcd8812350 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropControllerTest.java
@@ -47,6 +47,7 @@ import com.android.internal.logging.UiEventLogger;
import com.android.launcher3.icons.IconProvider;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.bubbles.bar.BubbleBarDragListener;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.sysui.ShellCommandHandler;
@@ -60,6 +61,8 @@ import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import dagger.Lazy;
+
/**
* Tests for the drag and drop controller.
*/
@@ -91,6 +94,8 @@ public class DragAndDropControllerTest extends ShellTestCase {
private Transitions mTransitions;
@Mock
private GlobalDragListener mGlobalDragListener;
+ @Mock
+ private Lazy<BubbleBarDragListener> mBubbleBarDragControllerLazy;
private DragAndDropController mController;
@@ -99,7 +104,8 @@ public class DragAndDropControllerTest extends ShellTestCase {
MockitoAnnotations.initMocks(this);
mController = new DragAndDropController(mContext, mShellInit, mShellController,
mShellCommandHandler, mShellTaskOrganizer, mDisplayController, mUiEventLogger,
- mIconProvider, mGlobalDragListener, mTransitions, mMainExecutor);
+ mIconProvider, mGlobalDragListener, mTransitions, mBubbleBarDragControllerLazy,
+ mMainExecutor);
mController.onInit();
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeCompatPolicyTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeCompatPolicyTest.kt
index a8a7be8fe7e3..d9791bb43489 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeCompatPolicyTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeCompatPolicyTest.kt
@@ -21,7 +21,7 @@ import android.content.pm.PackageManager
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.internal.R
-import com.android.wm.shell.compatui.CompatUIShellTestCase
+import com.android.wm.shell.ShellTestCase
import com.android.wm.shell.desktopmode.DesktopTestHelpers.createFreeformTask
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
@@ -39,7 +39,7 @@ import org.mockito.kotlin.whenever
*/
@RunWith(AndroidTestingRunner::class)
@SmallTest
-class DesktopModeCompatPolicyTest : CompatUIShellTestCase() {
+class DesktopModeCompatPolicyTest : ShellTestCase() {
private lateinit var desktopModeCompatPolicy: DesktopModeCompatPolicy
@Before
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatusTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatusTest.kt
index 4dac99b14aaf..33f14acd0f02 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatusTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatusTest.kt
@@ -39,6 +39,9 @@ import org.mockito.kotlin.eq
import org.mockito.kotlin.mock
import org.mockito.kotlin.whenever
+/**
+ * Test class for [DesktopModeStatus].
+ */
@SmallTest
@Presubmit
@EnableFlags(Flags.FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION)
@@ -56,6 +59,7 @@ class DesktopModeStatusTest : ShellTestCase() {
doReturn(false).whenever(mockResources).getBoolean(
eq(R.bool.config_isDesktopModeDevOptionSupported)
)
+ setDeviceEligibleForDesktopMode(false)
doReturn(context.contentResolver).whenever(mockContext).contentResolver
resetDesktopModeFlagsCache()
resetEnforceDeviceRestriction()
@@ -74,7 +78,7 @@ class DesktopModeStatusTest : ShellTestCase() {
Flags.FLAG_ENABLE_DESKTOP_MODE_THROUGH_DEV_OPTION
)
@Test
- fun canEnterDesktopMode_DWFlagDisabled_configsOff_returnsFalse() {
+ fun canEnterDesktopMode_DWFlagDisabled_deviceNotEligible_returnsFalse() {
assertThat(DesktopModeStatus.canEnterDesktopMode(mockContext)).isFalse()
}
@@ -83,8 +87,8 @@ class DesktopModeStatusTest : ShellTestCase() {
Flags.FLAG_ENABLE_DESKTOP_MODE_THROUGH_DEV_OPTION
)
@Test
- fun canEnterDesktopMode_DWFlagDisabled_configsOn_disableDeviceRestrictions_returnsFalse() {
- doReturn(true).whenever(mockResources).getBoolean(eq(R.bool.config_isDesktopModeSupported))
+ fun canEnterDesktopMode_DWFlagDisabled_deviceEligible_configDevOptionOn_returnsFalse() {
+ setDeviceEligibleForDesktopMode(true)
doReturn(true).whenever(mockResources).getBoolean(
eq(R.bool.config_isDesktopModeDevOptionSupported)
)
@@ -98,7 +102,7 @@ class DesktopModeStatusTest : ShellTestCase() {
Flags.FLAG_ENABLE_DESKTOP_MODE_THROUGH_DEV_OPTION
)
@Test
- fun canEnterDesktopMode_DWFlagDisabled_configDevOptionOn_returnsFalse() {
+ fun canEnterDesktopMode_DWFlagDisabled_deviceNotEligible_configDevOptionOn_returnsFalse() {
doReturn(true).whenever(mockResources).getBoolean(
eq(R.bool.config_isDesktopModeDevOptionSupported)
)
@@ -111,7 +115,7 @@ class DesktopModeStatusTest : ShellTestCase() {
Flags.FLAG_ENABLE_DESKTOP_MODE_THROUGH_DEV_OPTION
)
@Test
- fun canEnterDesktopMode_DWFlagDisabled_configDevOptionOn_flagOverrideOn_returnsTrue() {
+ fun canEnterDesktopMode_DWFlagDisabled_deviceNotEligible_forceUsingDevOption_returnsTrue() {
doReturn(true).whenever(mockResources).getBoolean(
eq(R.bool.config_isDesktopModeDevOptionSupported)
)
@@ -123,14 +127,7 @@ class DesktopModeStatusTest : ShellTestCase() {
@DisableFlags(Flags.FLAG_ENABLE_DESKTOP_MODE_THROUGH_DEV_OPTION)
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
@Test
- fun canEnterDesktopMode_DWFlagEnabled_configsOff_returnsFalse() {
- assertThat(DesktopModeStatus.canEnterDesktopMode(mockContext)).isFalse()
- }
-
- @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_MODE_THROUGH_DEV_OPTION)
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
- @Test
- fun canEnterDesktopMode_DWFlagEnabled_configDesktopModeOff_returnsFalse() {
+ fun canEnterDesktopMode_DWFlagEnabled_deviceNotEligible_returnsFalse() {
doReturn(true).whenever(mockResources).getBoolean(
eq(R.bool.config_isDesktopModeDevOptionSupported)
)
@@ -141,17 +138,8 @@ class DesktopModeStatusTest : ShellTestCase() {
@DisableFlags(Flags.FLAG_ENABLE_DESKTOP_MODE_THROUGH_DEV_OPTION)
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
@Test
- fun canEnterDesktopMode_DWFlagEnabled_configDesktopModeOn_returnsTrue() {
- doReturn(true).whenever(mockResources).getBoolean(eq(R.bool.config_isDesktopModeSupported))
-
- assertThat(DesktopModeStatus.canEnterDesktopMode(mockContext)).isTrue()
- }
-
- @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_MODE_THROUGH_DEV_OPTION)
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
- @Test
- fun canEnterDesktopMode_DWFlagEnabled_configsOff_disableDeviceRestrictions_returnsTrue() {
- disableEnforceDeviceRestriction()
+ fun canEnterDesktopMode_DWFlagEnabled_deviceEligible_returnsTrue() {
+ setDeviceEligibleForDesktopMode(true)
assertThat(DesktopModeStatus.canEnterDesktopMode(mockContext)).isTrue()
}
@@ -159,7 +147,7 @@ class DesktopModeStatusTest : ShellTestCase() {
@DisableFlags(Flags.FLAG_ENABLE_DESKTOP_MODE_THROUGH_DEV_OPTION)
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
@Test
- fun canEnterDesktopMode_DWFlagEnabled_configDevOptionOn_flagOverrideOn_returnsTrue() {
+ fun canEnterDesktopMode_DWFlagEnabled_deviceNotEligible_forceUsingDevOption_returnsTrue() {
doReturn(true).whenever(mockResources).getBoolean(
eq(R.bool.config_isDesktopModeDevOptionSupported)
)
@@ -198,6 +186,28 @@ class DesktopModeStatusTest : ShellTestCase() {
assertThat(DesktopModeStatus.isDeviceEligibleForDesktopMode(mockContext)).isTrue()
}
+ @DisableFlags(Flags.FLAG_SHOW_DESKTOP_EXPERIENCE_DEV_OPTION)
+ @Test
+ fun canShowDesktopExperienceDevOption_flagDisabled_returnsFalse() {
+ setDeviceEligibleForDesktopMode(true)
+
+ assertThat(DesktopModeStatus.canShowDesktopExperienceDevOption(mockContext)).isFalse()
+ }
+
+ @EnableFlags(Flags.FLAG_SHOW_DESKTOP_EXPERIENCE_DEV_OPTION)
+ @Test
+ fun canShowDesktopExperienceDevOption_flagEnabled_deviceNotEligible_returnsFalse() {
+ assertThat(DesktopModeStatus.canShowDesktopExperienceDevOption(mockContext)).isFalse()
+ }
+
+ @EnableFlags(Flags.FLAG_SHOW_DESKTOP_EXPERIENCE_DEV_OPTION)
+ @Test
+ fun canShowDesktopExperienceDevOption_flagEnabled_deviceEligible_returnsTrue() {
+ setDeviceEligibleForDesktopMode(true)
+
+ assertThat(DesktopModeStatus.canShowDesktopExperienceDevOption(mockContext)).isTrue()
+ }
+
private fun resetEnforceDeviceRestriction() {
setEnforceDeviceRestriction(true)
}
@@ -232,4 +242,10 @@ class DesktopModeStatusTest : ShellTestCase() {
DEVELOPMENT_OVERRIDE_DESKTOP_MODE_FEATURES, override.setting
)
}
+
+ private fun setDeviceEligibleForDesktopMode(eligible: Boolean) {
+ val deviceRestrictions = DesktopModeStatus::class.java.getDeclaredField("ENFORCE_DEVICE_RESTRICTIONS")
+ deviceRestrictions.isAccessible = true
+ deviceRestrictions.setBoolean(/* obj= */ null, /* z= */ !eligible)
+ }
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelAppHandleOnlyTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelAppHandleOnlyTest.kt
index 8b4cf6d1fabe..e40d97c68554 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelAppHandleOnlyTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelAppHandleOnlyTest.kt
@@ -67,7 +67,7 @@ class DesktopModeWindowDecorViewModelAppHandleOnlyTest :
.spyStatic(DesktopModeStatus::class.java)
.spyStatic(DragPositioningCallbackUtility::class.java)
.startMocking()
- doReturn(false).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
+ doReturn(false).`when` { DesktopModeStatus.isDeviceEligibleForDesktopMode(any()) }
doReturn(true).`when` { DesktopModeStatus.overridesShowAppHandle(any())}
setUpCommon()
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
index 737780ed8024..f15418adf1e3 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
@@ -116,7 +116,7 @@ class DesktopModeWindowDecorViewModelTests : DesktopModeWindowDecorViewModelTest
.spyStatic(DragPositioningCallbackUtility::class.java)
.startMocking()
- doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(Mockito.any()) }
+ doReturn(true).`when` { DesktopModeStatus.isDeviceEligibleForDesktopMode(Mockito.any()) }
doReturn(false).`when` { DesktopModeStatus.overridesShowAppHandle(Mockito.any()) }
setUpCommon()
@@ -379,37 +379,21 @@ class DesktopModeWindowDecorViewModelTests : DesktopModeWindowDecorViewModelTest
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
- fun testWindowDecor_desktopModeUnsupportedOnDevice_deviceRestrictionsOverridden_decorCreated() {
- // Simulate enforce device restrictions system property overridden to false
- whenever(DesktopModeStatus.enforceDeviceRestrictions()).thenReturn(false)
- // Simulate device that doesn't support desktop mode
- doReturn(false).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
-
- val task = createTask(windowingMode = WINDOWING_MODE_FULLSCREEN)
- setUpMockDecorationsForTasks(task)
-
- onTaskOpening(task)
- assertTrue(windowDecorByTaskIdSpy.contains(task.taskId))
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
- fun testWindowDecor_deviceSupportsDesktopMode_decorCreated() {
+ fun testWindowDecor_deviceEligibleForDesktopMode_decorCreated() {
// Simulate default enforce device restrictions system property
whenever(DesktopModeStatus.enforceDeviceRestrictions()).thenReturn(true)
val task = createTask(windowingMode = WINDOWING_MODE_FULLSCREEN)
- doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
+ doReturn(true).`when` { DesktopModeStatus.isDeviceEligibleForDesktopMode(any()) }
setUpMockDecorationsForTasks(task)
onTaskOpening(task)
- assertTrue(windowDecorByTaskIdSpy.contains(task.taskId))
+ assertTrue(task.taskId in windowDecorByTaskIdSpy)
}
@Test
fun testOnDecorMaximizedOrRestored_togglesTaskSize_maximize() {
- val maxOrRestoreListenerCaptor = forClass(Function0::class.java)
- as ArgumentCaptor<Function0<Unit>>
+ val maxOrRestoreListenerCaptor = forClass(Function0::class.java as Class<Function0<Unit>>)
val decor = createOpenTaskDecoration(
windowingMode = WINDOWING_MODE_FREEFORM,
onMaxOrRestoreListenerCaptor = maxOrRestoreListenerCaptor
@@ -655,7 +639,7 @@ class DesktopModeWindowDecorViewModelTests : DesktopModeWindowDecorViewModelTest
toDesktopListenerCaptor.value.accept(DesktopModeTransitionSource.APP_HANDLE_MENU_BUTTON)
- verify(mockDesktopTasksController).moveTaskToDesktop(
+ verify(mockDesktopTasksController).moveTaskToDefaultDeskAndActivate(
eq(decor.mTaskInfo.taskId),
any(),
eq(DesktopModeTransitionSource.APP_HANDLE_MENU_BUTTON),
@@ -893,7 +877,7 @@ class DesktopModeWindowDecorViewModelTests : DesktopModeWindowDecorViewModelTest
)
verify(mockDesktopTasksController, times(1))
- .moveTaskToDesktop(any(), any(), any(), anyOrNull(), anyOrNull())
+ .moveTaskToDefaultDeskAndActivate(any(), any(), any(), anyOrNull(), anyOrNull())
}
@Test
diff --git a/libs/androidfw/Android.bp b/libs/androidfw/Android.bp
index 12b1dd794a03..3dc53c5051e9 100644
--- a/libs/androidfw/Android.bp
+++ b/libs/androidfw/Android.bp
@@ -288,6 +288,7 @@ cc_benchmark {
"tests/AttributeResolution_bench.cpp",
"tests/CursorWindow_bench.cpp",
"tests/Generic_bench.cpp",
+ "tests/LocaleDataLookup_bench.cpp",
"tests/SparseEntry_bench.cpp",
"tests/Theme_bench.cpp",
],
diff --git a/libs/androidfw/LocaleDataLookup.cpp b/libs/androidfw/LocaleDataLookup.cpp
index 6e751a77f355..ea9e9a2d4280 100644
--- a/libs/androidfw/LocaleDataLookup.cpp
+++ b/libs/androidfw/LocaleDataLookup.cpp
@@ -7518,6 +7518,13 @@ const char* lookupLikelyScript(uint32_t packed_lang_region) {
}
}
+/*
+ * TODO: Consider turning the below switch statement into binary search
+ * to save the disk space when the table is larger in the future.
+ * Disassembled code shows that the jump table emitted by clang can be
+ * 4x larger than the data in disk size, but it depends on the optimization option.
+ * However, a switch statement will benefit from the future of compiler improvement.
+ */
bool isLocaleRepresentative(uint32_t language_and_region, const char* script) {
const uint64_t packed_locale =
((static_cast<uint64_t>(language_and_region)) << 32u) |
diff --git a/libs/androidfw/tests/LocaleDataLookup_bench.cpp b/libs/androidfw/tests/LocaleDataLookup_bench.cpp
new file mode 100644
index 000000000000..60ce3b944551
--- /dev/null
+++ b/libs/androidfw/tests/LocaleDataLookup_bench.cpp
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "benchmark/benchmark.h"
+
+#include "androidfw/LocaleDataLookup.h"
+
+namespace android {
+
+static void BM_LocaleDataLookupIsLocaleRepresentative(benchmark::State& state) {
+ for (auto&& _ : state) {
+ isLocaleRepresentative(packLocale("en", "US"), "Latn");
+ isLocaleRepresentative(packLocale("es", "ES"), "Latn");
+ isLocaleRepresentative(packLocale("zh", "CN"), "Hans");
+ isLocaleRepresentative(packLocale("pt", "BR"), "Latn");
+ isLocaleRepresentative(packLocale("ar", "EG"), "Arab");
+ isLocaleRepresentative(packLocale("hi", "IN"), "Deva");
+ isLocaleRepresentative(packLocale("jp", "JP"), "Jpan");
+ }
+}
+BENCHMARK(BM_LocaleDataLookupIsLocaleRepresentative);
+
+static void BM_LocaleDataLookupLikelyScript(benchmark::State& state) {
+ for (auto&& _ : state) {
+ lookupLikelyScript(packLocale("en", ""));
+ lookupLikelyScript(packLocale("es", ""));
+ lookupLikelyScript(packLocale("zh", ""));
+ lookupLikelyScript(packLocale("pt", ""));
+ lookupLikelyScript(packLocale("ar", ""));
+ lookupLikelyScript(packLocale("hi", ""));
+ lookupLikelyScript(packLocale("jp", ""));
+ lookupLikelyScript(packLocale("en", "US"));
+ lookupLikelyScript(packLocale("es", "ES"));
+ lookupLikelyScript(packLocale("zh", "CN"));
+ lookupLikelyScript(packLocale("pt", "BR"));
+ lookupLikelyScript(packLocale("ar", "EG"));
+ lookupLikelyScript(packLocale("hi", "IN"));
+ lookupLikelyScript(packLocale("jp", "JP"));
+ }
+}
+BENCHMARK(BM_LocaleDataLookupLikelyScript);
+
+
+} // namespace android
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 71013f7f4e34..5dc49a07a6d6 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -10406,6 +10406,23 @@ public class AudioManager {
}
}
+ /**
+ * Enable strict audio hardening (background) enforcement, regardless of release or temporary
+ * exemptions for debugging purposes.
+ * Enforced hardening can be found in the audio dumpsys with the API being restricted and the
+ * level of restriction which was encountered.
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED)
+ public void setEnableHardening(boolean shouldEnable) {
+ final IAudioService service = getService();
+ try {
+ service.setEnableHardening(shouldEnable);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
//====================================================================
// Mute await connection
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index 2a740f85aa72..7b8d6663c957 100644
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -819,4 +819,8 @@ interface IAudioService {
@EnforcePermission("QUERY_AUDIO_STATE")
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.QUERY_AUDIO_STATE)")
boolean shouldNotificationSoundPlay(in AudioAttributes aa);
+
+ @EnforcePermission("MODIFY_AUDIO_SETTINGS_PRIVILEGED")
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED)")
+ void setEnableHardening(in boolean shouldEnable);
}
diff --git a/media/java/android/media/flags/media_better_together.aconfig b/media/java/android/media/flags/media_better_together.aconfig
index 405d292dfafa..2e8c28d8e65b 100644
--- a/media/java/android/media/flags/media_better_together.aconfig
+++ b/media/java/android/media/flags/media_better_together.aconfig
@@ -21,6 +21,16 @@ flag {
}
flag {
+ name: "disable_transfer_when_apps_do_not_support"
+ namespace: "media_better_together"
+ description: "Fixes a bug causing output switcher routes to be incorrectly enabled for media transfer."
+ bug: "373404114"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "enable_audio_input_device_routing_and_volume_control"
namespace: "media_better_together"
description: "Allows audio input devices routing and volume control via system settings."
diff --git a/nfc-non-updatable/flags/flags.aconfig b/nfc-non-updatable/flags/flags.aconfig
index 54ded0cddffa..eb30bbe1bfe7 100644
--- a/nfc-non-updatable/flags/flags.aconfig
+++ b/nfc-non-updatable/flags/flags.aconfig
@@ -198,10 +198,6 @@ flag {
bug: "380892385"
}
-flag {
- name: "nfc_hce_latency_events"
- is_exported: true
- namespace: "wallet_integration"
- description: "Enables tracking latency for HCE"
- bug: "379849603"
-}
+# Unless you are adding a flag for a file under nfc-non-updatable, you should
+# not add a flag here for Android 16+ targeting features. Use the flags
+# in com.android.nfc.module.flags (packages/modules/Nfc/flags) instead.
diff --git a/nfc-non-updatable/java/android/nfc/cardemulation/ApduServiceInfo.java b/nfc-non-updatable/java/android/nfc/cardemulation/ApduServiceInfo.java
index 93d6eb73dcae..e83b9f1afddb 100644
--- a/nfc-non-updatable/java/android/nfc/cardemulation/ApduServiceInfo.java
+++ b/nfc-non-updatable/java/android/nfc/cardemulation/ApduServiceInfo.java
@@ -572,8 +572,10 @@ public final class ApduServiceInfo implements Parcelable {
if (mAutoTransact.getOrDefault(plf.toUpperCase(Locale.ROOT), false)) {
return true;
}
- List<Pattern> patternMatches = mAutoTransactPatterns.keySet().stream()
- .filter(p -> p.matcher(plf).matches()).toList();
+ boolean isPattern = plf.contains("?") || plf.contains("*");
+ List<Pattern> patternMatches = mAutoTransactPatterns.keySet().stream().filter(
+ p -> isPattern ? p.toString().equals(plf) : p.matcher(plf).matches()).toList();
+
if (patternMatches == null || patternMatches.size() == 0) {
return false;
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
index 4ee9ff059502..ceb6f7b080df 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
@@ -16,8 +16,6 @@
package com.android.settingslib.media;
import static android.media.MediaRoute2Info.TYPE_AUX_LINE;
-import static android.media.MediaRoute2Info.TYPE_LINE_ANALOG;
-import static android.media.MediaRoute2Info.TYPE_LINE_DIGITAL;
import static android.media.MediaRoute2Info.TYPE_BLE_HEADSET;
import static android.media.MediaRoute2Info.TYPE_BLUETOOTH_A2DP;
import static android.media.MediaRoute2Info.TYPE_BUILTIN_SPEAKER;
@@ -27,6 +25,8 @@ import static android.media.MediaRoute2Info.TYPE_HDMI;
import static android.media.MediaRoute2Info.TYPE_HDMI_ARC;
import static android.media.MediaRoute2Info.TYPE_HDMI_EARC;
import static android.media.MediaRoute2Info.TYPE_HEARING_AID;
+import static android.media.MediaRoute2Info.TYPE_LINE_ANALOG;
+import static android.media.MediaRoute2Info.TYPE_LINE_DIGITAL;
import static android.media.MediaRoute2Info.TYPE_REMOTE_AUDIO_VIDEO_RECEIVER;
import static android.media.MediaRoute2Info.TYPE_REMOTE_CAR;
import static android.media.MediaRoute2Info.TYPE_REMOTE_COMPUTER;
@@ -254,6 +254,10 @@ public abstract class InfoMediaManager {
protected abstract List<MediaRoute2Info> getSelectableRoutes(@NonNull RoutingSessionInfo info);
@NonNull
+ protected abstract List<MediaRoute2Info> getTransferableRoutes(
+ @NonNull RoutingSessionInfo info);
+
+ @NonNull
protected abstract List<MediaRoute2Info> getDeselectableRoutes(
@NonNull RoutingSessionInfo info);
@@ -519,6 +523,22 @@ public abstract class InfoMediaManager {
}
/**
+ * Returns the list of {@link MediaDevice media devices} that can be transferred to with the
+ * current {@link RoutingSessionInfo routing session} by the media route provider.
+ */
+ @NonNull
+ List<MediaDevice> getTransferableMediaDevices() {
+ final RoutingSessionInfo info = getActiveRoutingSession();
+
+ final List<MediaDevice> deviceList = new ArrayList<>();
+ for (MediaRoute2Info route : getTransferableRoutes(info)) {
+ deviceList.add(
+ new InfoMediaDevice(mContext, route, mPreferenceItemMap.get(route.getId())));
+ }
+ return deviceList;
+ }
+
+ /**
* Returns the list of {@link MediaDevice media devices} that can be deselected from the current
* {@link RoutingSessionInfo routing session}.
*/
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
index fe6659d1dc4f..76f366d3d1b6 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
@@ -352,6 +352,17 @@ public class LocalMediaManager implements BluetoothCallback {
}
/**
+ * Gets the MediaDevice list that can be transferred to with the current media session by the
+ * media route provider.
+ *
+ * @return list of MediaDevice
+ */
+ @NonNull
+ public List<MediaDevice> getTransferableMediaDevices() {
+ return mInfoMediaManager.getTransferableMediaDevices();
+ }
+
+ /**
* Get the MediaDevice list that can be removed from current media session.
*
* @return list of MediaDevice
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/ManagerInfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/ManagerInfoMediaManager.java
index 82b197682459..9e511ffb4e34 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/ManagerInfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/ManagerInfoMediaManager.java
@@ -117,6 +117,12 @@ public class ManagerInfoMediaManager extends InfoMediaManager {
@Override
@NonNull
+ protected List<MediaRoute2Info> getTransferableRoutes(@NonNull RoutingSessionInfo info) {
+ return mRouterManager.getTransferableRoutes(info);
+ }
+
+ @Override
+ @NonNull
protected List<MediaRoute2Info> getDeselectableRoutes(@NonNull RoutingSessionInfo info) {
return mRouterManager.getDeselectableRoutes(info);
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java
index b01b7c9048ba..d018d1404623 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java
@@ -15,6 +15,7 @@
*/
package com.android.settingslib.media;
+import static android.media.MediaRoute2Info.TYPE_AUX_LINE;
import static android.media.MediaRoute2Info.TYPE_BLE_HEADSET;
import static android.media.MediaRoute2Info.TYPE_BLUETOOTH_A2DP;
import static android.media.MediaRoute2Info.TYPE_BUILTIN_SPEAKER;
@@ -24,6 +25,8 @@ import static android.media.MediaRoute2Info.TYPE_HDMI;
import static android.media.MediaRoute2Info.TYPE_HDMI_ARC;
import static android.media.MediaRoute2Info.TYPE_HDMI_EARC;
import static android.media.MediaRoute2Info.TYPE_HEARING_AID;
+import static android.media.MediaRoute2Info.TYPE_LINE_ANALOG;
+import static android.media.MediaRoute2Info.TYPE_LINE_DIGITAL;
import static android.media.MediaRoute2Info.TYPE_REMOTE_AUDIO_VIDEO_RECEIVER;
import static android.media.MediaRoute2Info.TYPE_REMOTE_SPEAKER;
import static android.media.MediaRoute2Info.TYPE_REMOTE_TV;
@@ -33,9 +36,6 @@ import static android.media.MediaRoute2Info.TYPE_USB_DEVICE;
import static android.media.MediaRoute2Info.TYPE_USB_HEADSET;
import static android.media.MediaRoute2Info.TYPE_WIRED_HEADPHONES;
import static android.media.MediaRoute2Info.TYPE_WIRED_HEADSET;
-import static android.media.MediaRoute2Info.TYPE_LINE_DIGITAL;
-import static android.media.MediaRoute2Info.TYPE_LINE_ANALOG;
-import static android.media.MediaRoute2Info.TYPE_AUX_LINE;
import static android.media.RouteListingPreference.Item.FLAG_ONGOING_SESSION;
import static android.media.RouteListingPreference.Item.FLAG_ONGOING_SESSION_MANAGED;
import static android.media.RouteListingPreference.Item.FLAG_SUGGESTED;
@@ -244,6 +244,11 @@ public abstract class MediaDevice implements Comparable<MediaDevice> {
*/
public abstract String getId();
+ /** Returns {@code true} if the device has a non-null {@link RouteListingPreference.Item}. */
+ public boolean hasRouteListingPreferenceItem() {
+ return mItem != null;
+ }
+
/**
* Get selection behavior of device
*
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/NoOpInfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/NoOpInfoMediaManager.java
index 2c7ec9302117..9fe5b1d58752 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/NoOpInfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/NoOpInfoMediaManager.java
@@ -114,6 +114,12 @@ import java.util.List;
@NonNull
@Override
+ protected List<MediaRoute2Info> getTransferableRoutes(@NonNull RoutingSessionInfo info) {
+ return Collections.emptyList();
+ }
+
+ @NonNull
+ @Override
protected List<MediaRoute2Info> getDeselectableRoutes(@NonNull RoutingSessionInfo info) {
return Collections.emptyList();
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/RouterInfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/RouterInfoMediaManager.java
index eced7b3a116b..6a2da182dbb1 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/RouterInfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/RouterInfoMediaManager.java
@@ -203,6 +203,13 @@ public final class RouterInfoMediaManager extends InfoMediaManager {
@NonNull
@Override
+ protected List<MediaRoute2Info> getTransferableRoutes(@NonNull RoutingSessionInfo info) {
+ RoutingController controller = getControllerForSession(info);
+ return getTransferableRoutes(controller);
+ }
+
+ @NonNull
+ @Override
protected List<MediaRoute2Info> getSelectedRoutes(@NonNull RoutingSessionInfo info) {
RoutingController controller = getControllerForSession(info);
if (controller == null) {
@@ -272,22 +279,27 @@ public final class RouterInfoMediaManager extends InfoMediaManager {
protected List<MediaRoute2Info> getTransferableRoutes(@NonNull String packageName) {
List<RoutingController> controllers = mRouter.getControllers();
RoutingController activeController = controllers.get(controllers.size() - 1);
- HashMap<String, MediaRoute2Info> transferableRoutes = new HashMap<>();
-
- activeController
- .getTransferableRoutes()
- .forEach(route -> transferableRoutes.put(route.getId(), route));
+ return getTransferableRoutes(activeController);
+ }
- if (activeController.getRoutingSessionInfo().isSystemSession()) {
- mRouter.getRoutes().stream()
- .filter(route -> !route.isSystemRoute())
- .forEach(route -> transferableRoutes.put(route.getId(), route));
- } else {
- mRouter.getRoutes().stream()
- .filter(route -> route.isSystemRoute())
+ @NonNull
+ private List<MediaRoute2Info> getTransferableRoutes(@Nullable RoutingController controller) {
+ HashMap<String, MediaRoute2Info> transferableRoutes = new HashMap<>();
+ if (controller != null) {
+ controller
+ .getTransferableRoutes()
.forEach(route -> transferableRoutes.put(route.getId(), route));
- }
+ if (controller.getRoutingSessionInfo().isSystemSession()) {
+ mRouter.getRoutes().stream()
+ .filter(route -> !route.isSystemRoute())
+ .forEach(route -> transferableRoutes.put(route.getId(), route));
+ } else {
+ mRouter.getRoutes().stream()
+ .filter(route -> route.isSystemRoute())
+ .forEach(route -> transferableRoutes.put(route.getId(), route));
+ }
+ }
return new ArrayList<>(transferableRoutes.values());
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
index 1a83f0a2e775..219ad6ca3f1a 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
@@ -65,7 +65,8 @@ import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
@@ -78,6 +79,7 @@ import java.util.Set;
@RunWith(RobolectricTestRunner.class)
@Config(shadows = {ShadowRouter2Manager.class})
public class InfoMediaManagerTest {
+ @Rule public final MockitoRule mockito = MockitoJUnit.rule();
private static final String TEST_PACKAGE_NAME = "com.test.packagename";
private static final String TEST_PACKAGE_NAME_2 = "com.test.packagename2";
@@ -146,7 +148,6 @@ public class InfoMediaManagerTest {
@Before
public void setUp() {
- MockitoAnnotations.initMocks(this);
mContext = spy(RuntimeEnvironment.application);
doReturn(mMediaSessionManager).when(mContext).getSystemService(
@@ -663,6 +664,26 @@ public class InfoMediaManagerTest {
}
@Test
+ public void getTransferableMediaDevice_checkList() {
+ final List<MediaRoute2Info> mediaRoute2Infos = new ArrayList<>();
+ final MediaRoute2Info mediaRoute2Info = mock(MediaRoute2Info.class);
+ mediaRoute2Infos.add(mediaRoute2Info);
+ mShadowRouter2Manager.setTransferableRoutes(mediaRoute2Infos);
+ when(mediaRoute2Info.getName()).thenReturn(TEST_NAME);
+ when(mediaRoute2Info.getId()).thenReturn(TEST_ID);
+ mInfoMediaManager.mRouterManager = mRouterManager;
+ when(mRouterManager.getRoutingSessions(TEST_PACKAGE_NAME))
+ .thenReturn(List.of(TEST_REMOTE_ROUTING_SESSION));
+ when(mRouterManager.getTransferableRoutes(any(RoutingSessionInfo.class)))
+ .thenReturn(mediaRoute2Infos);
+
+ final List<MediaDevice> mediaDevices = mInfoMediaManager.getTransferableMediaDevices();
+
+ assertThat(mediaDevices.size()).isEqualTo(1);
+ assertThat(mediaDevices.get(0).getName()).isEqualTo(TEST_NAME);
+ }
+
+ @Test
public void getDeselectableMediaDevice_checkList() {
final List<RoutingSessionInfo> routingSessionInfos = new ArrayList<>();
final RoutingSessionInfo info = mock(RoutingSessionInfo.class);
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 8fe3a0c4b4ae..55f7317f25e4 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -1015,6 +1015,9 @@
<uses-permission android:name="android.permission.MANAGE_GLOBAL_SOUND_QUALITY_SERVICE" />
<uses-permission android:name="android.permission.READ_COLOR_ZONES" />
+ <!-- Permission required for trade-in mode testing -->
+ <uses-permission android:name="android.permission.ENTER_TRADE_IN_MODE" />
+
<application
android:label="@string/app_label"
android:theme="@android:style/Theme.DeviceDefault.DayNight"
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 0a7d880677d8..744388f47d0e 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -314,6 +314,7 @@ filegroup {
"tests/src/**/systemui/statusbar/policy/WalletControllerImplTest.kt",
"tests/src/**/keyguard/ClockEventControllerTest.kt",
"tests/src/**/systemui/bluetooth/qsdialog/BluetoothStateInteractorTest.kt",
+ "tests/src/**/systemui/bluetooth/qsdialog/BluetoothDetailsContentManagerTest.kt",
"tests/src/**/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegateTest.kt",
"tests/src/**/systemui/bluetooth/qsdialog/BluetoothTileDialogRepositoryTest.kt",
"tests/src/**/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModelTest.kt",
@@ -424,7 +425,6 @@ android_library {
manifest: "AndroidManifest-res.xml",
flags_packages: [
"android.app.flags-aconfig",
- "com_android_systemui_flags",
],
}
diff --git a/packages/SystemUI/aconfig/accessibility.aconfig b/packages/SystemUI/aconfig/accessibility.aconfig
index fb21be4c3bd1..3cb30258fcb1 100644
--- a/packages/SystemUI/aconfig/accessibility.aconfig
+++ b/packages/SystemUI/aconfig/accessibility.aconfig
@@ -120,6 +120,16 @@ flag {
}
flag {
+ name: "update_window_magnifier_bottom_boundary"
+ namespace: "accessibility"
+ description: "Update the window magnifier boundary at the bottom to the top of the system gesture inset."
+ bug: "380320995"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "hearing_devices_dialog_related_tools"
namespace: "accessibility"
description: "Shows the related tools for hearing devices dialog."
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 6c96279711d0..ac53dcb1982a 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -2013,3 +2013,10 @@ flag {
description: "Show a Locked by your watch indicator on the keyguard when the device is locked by the watch."
bug: "387322459"
}
+
+flag {
+ name: "decouple_view_controller_in_animlib"
+ namespace: "systemui"
+ description: "Decouple view and controller in AnimLib."
+ bug: "393241010"
+}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt
index c8d3430bf54b..f03bd3d9a2a7 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt
@@ -73,6 +73,9 @@ import com.android.wm.shell.shared.ShellTransitions
import com.android.wm.shell.shared.TransitionUtil
import java.util.concurrent.Executor
import kotlin.math.roundToInt
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withTimeoutOrNull
private const val TAG = "ActivityTransitionAnimator"
@@ -241,7 +244,7 @@ constructor(
override fun onTransitionAnimationProgress(linearProgress: Float) {
LinkedHashSet(listeners).forEach {
- it.onTransitionAnimationProgress(linearProgress)
+ it.onTransitionAnimationProgress(linearProgress)
}
}
@@ -494,15 +497,19 @@ constructor(
/**
* Create a new animation [Runner] controlled by the [Controller] that [controllerFactory] can
- * create based on [forLaunch].
+ * create based on [forLaunch] and within the given [scope].
*
* This method must only be used for long-lived registrations. Otherwise, use
* [createEphemeralRunner].
*/
@VisibleForTesting
- fun createLongLivedRunner(controllerFactory: ControllerFactory, forLaunch: Boolean): Runner {
+ fun createLongLivedRunner(
+ controllerFactory: ControllerFactory,
+ scope: CoroutineScope,
+ forLaunch: Boolean,
+ ): Runner {
assertLongLivedReturnAnimations()
- return Runner(callback!!, transitionAnimator, lifecycleListener) {
+ return Runner(scope, callback!!, transitionAnimator, lifecycleListener) {
controllerFactory.createController(forLaunch)
}
}
@@ -564,7 +571,7 @@ constructor(
* Creates a [Controller] for launching or returning from the activity linked to [cookie]
* and [component].
*/
- abstract fun createController(forLaunch: Boolean): Controller
+ abstract suspend fun createController(forLaunch: Boolean): Controller
}
/**
@@ -691,9 +698,14 @@ constructor(
* animations.
*
* The [Controller]s created by [controllerFactory] will only be used for transitions matching
- * the [cookie], or the [ComponentName] defined within it if the cookie matching fails.
+ * the [cookie], or the [ComponentName] defined within it if the cookie matching fails. These
+ * [Controller]s can only be created within [scope].
*/
- fun register(cookie: TransitionCookie, controllerFactory: ControllerFactory) {
+ fun register(
+ cookie: TransitionCookie,
+ controllerFactory: ControllerFactory,
+ scope: CoroutineScope,
+ ) {
assertLongLivedReturnAnimations()
if (transitionRegister == null) {
@@ -725,7 +737,7 @@ constructor(
}
val launchRemoteTransition =
RemoteTransition(
- OriginTransition(createLongLivedRunner(controllerFactory, forLaunch = true)),
+ OriginTransition(createLongLivedRunner(controllerFactory, scope, forLaunch = true)),
"${cookie}_launchTransition",
)
transitionRegister.register(launchFilter, launchRemoteTransition, includeTakeover = true)
@@ -749,7 +761,9 @@ constructor(
}
val returnRemoteTransition =
RemoteTransition(
- OriginTransition(createLongLivedRunner(controllerFactory, forLaunch = false)),
+ OriginTransition(
+ createLongLivedRunner(controllerFactory, scope, forLaunch = false)
+ ),
"${cookie}_returnTransition",
)
transitionRegister.register(returnFilter, returnRemoteTransition, includeTakeover = true)
@@ -952,7 +966,9 @@ constructor(
* Reusable factory to generate single-use controllers. In case of an ephemeral [Runner],
* this must be null and [controller] must be defined instead.
*/
- private val controllerFactory: (() -> Controller)?,
+ private val controllerFactory: (suspend () -> Controller)?,
+ /** The scope to use when this runner is based on [controllerFactory]. */
+ private val scope: CoroutineScope? = null,
private val callback: Callback,
/** The animator to use to animate the window transition. */
private val transitionAnimator: TransitionAnimator,
@@ -973,13 +989,15 @@ constructor(
)
constructor(
+ scope: CoroutineScope,
callback: Callback,
transitionAnimator: TransitionAnimator,
listener: Listener? = null,
- controllerFactory: () -> Controller,
+ controllerFactory: suspend () -> Controller,
) : this(
controller = null,
controllerFactory = controllerFactory,
+ scope = scope,
callback = callback,
transitionAnimator = transitionAnimator,
listener = listener,
@@ -994,12 +1012,12 @@ constructor(
assert((controller != null).xor(controllerFactory != null))
delegate = null
- if (controller != null) {
+ controller?.let {
// Ephemeral launches bundle the runner with the launch request (instead of being
// registered ahead of time for later use). This means that there could be a timeout
// between creation and invocation, so the delegate needs to exist from the
// beginning in order to handle such timeout.
- createDelegate()
+ createDelegate(it)
}
}
@@ -1040,49 +1058,79 @@ constructor(
finishedCallback: IRemoteAnimationFinishedCallback?,
performAnimation: (AnimationDelegate) -> Unit,
) {
- maybeSetUp()
- val delegate = delegate
- mainExecutor.execute {
- if (delegate == null) {
- Log.i(TAG, "onAnimationStart called after completion")
- // Animation started too late and timed out already. We need to still
- // signal back that we're done with it.
- finishedCallback?.onAnimationFinished()
- } else {
- performAnimation(delegate)
+ val controller = controller
+ val controllerFactory = controllerFactory
+
+ if (controller != null) {
+ maybeSetUp(controller)
+ val success = startAnimation(performAnimation)
+ if (!success) finishedCallback?.onAnimationFinished()
+ } else if (controllerFactory != null) {
+ scope?.launch {
+ val success =
+ withTimeoutOrNull(TRANSITION_TIMEOUT) {
+ setUp(controllerFactory)
+ startAnimation(performAnimation)
+ } ?: false
+ if (!success) finishedCallback?.onAnimationFinished()
}
+ } else {
+ // This should never happen, as either the controller or factory should always be
+ // defined. This final call is for safety in case something goes wrong.
+ Log.wtf(TAG, "initAndRun with neither a controller nor factory")
+ finishedCallback?.onAnimationFinished()
+ }
+ }
+
+ /** Tries to start the animation on the main thread and returns whether it succeeded. */
+ @BinderThread
+ private fun startAnimation(performAnimation: (AnimationDelegate) -> Unit): Boolean {
+ val delegate = delegate
+ return if (delegate != null) {
+ mainExecutor.execute { performAnimation(delegate) }
+ true
+ } else {
+ // Animation started too late and timed out already.
+ Log.i(TAG, "startAnimation called after completion")
+ false
}
}
@BinderThread
override fun onAnimationCancelled() {
val delegate = delegate
- mainExecutor.execute {
- delegate ?: Log.wtf(TAG, "onAnimationCancelled called after completion")
- delegate?.onAnimationCancelled()
+ if (delegate != null) {
+ mainExecutor.execute { delegate.onAnimationCancelled() }
+ } else {
+ Log.wtf(TAG, "onAnimationCancelled called after completion")
}
}
+ /**
+ * Posts the default animation timeouts. Since this only applies to ephemeral launches, this
+ * method is a no-op if [controller] is not defined.
+ */
@VisibleForTesting
@UiThread
fun postTimeouts() {
- maybeSetUp()
+ controller?.let { maybeSetUp(it) }
delegate?.postTimeouts()
}
@AnyThread
- private fun maybeSetUp() {
- if (controllerFactory == null || delegate != null) return
- createDelegate()
+ private fun maybeSetUp(controller: Controller) {
+ if (delegate != null) return
+ createDelegate(controller)
}
@AnyThread
- private fun createDelegate() {
- var controller = controller
- val factory = controllerFactory
- if (controller == null && factory == null) return
+ private suspend fun setUp(createController: suspend () -> Controller) {
+ val controller = createController()
+ createDelegate(controller)
+ }
- controller = controller ?: factory!!.invoke()
+ @AnyThread
+ private fun createDelegate(controller: Controller) {
delegate =
AnimationDelegate(
mainExecutor,
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/ExpandableController.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/ExpandableController.kt
index 103a9b5cf5f4..c5d2802c8941 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/animation/ExpandableController.kt
+++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/ExpandableController.kt
@@ -319,7 +319,7 @@ internal class ExpandableControllerImpl(
override fun onTransitionAnimationStart(isExpandingFullyAbove: Boolean) {
delegate.onTransitionAnimationStart(isExpandingFullyAbove)
- overlay.value = composeViewRoot.rootView.overlay as ViewGroupOverlay
+ overlay.value = transitionContainer.overlay as ViewGroupOverlay
cujType?.let { InteractionJankMonitor.getInstance().begin(composeViewRoot, it) }
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
index 3ffbabb09710..4a4607b6e8fc 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
@@ -159,8 +159,7 @@ fun CommunalContainer(
content: CommunalContent,
) {
val coroutineScope = rememberCoroutineScope()
- val currentSceneKey: SceneKey by
- viewModel.currentScene.collectAsStateWithLifecycle(CommunalScenes.Blank)
+ val currentSceneKey: SceneKey by viewModel.currentScene.collectAsStateWithLifecycle()
val touchesAllowed by viewModel.touchesAllowed.collectAsStateWithLifecycle()
val backgroundType by
viewModel.communalBackground.collectAsStateWithLifecycle(
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 d3417022565b..fa5e8aceef1d 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
@@ -109,15 +109,22 @@ constructor(
}
if (isShadeLayoutWide && !isBypassEnabled) {
with(notificationSection) {
- Notifications(
- areNotificationsVisible = areNotificationsVisible,
- isShadeLayoutWide = true,
- burnInParams = null,
- modifier =
- Modifier.fillMaxWidth(0.5f)
- .fillMaxHeight()
- .align(alignment = Alignment.TopEnd),
- )
+ Box(modifier = Modifier.fillMaxHeight()) {
+ AodPromotedNotificationArea(
+ modifier =
+ Modifier.fillMaxWidth(0.5f)
+ .align(alignment = Alignment.TopStart)
+ )
+ Notifications(
+ areNotificationsVisible = areNotificationsVisible,
+ isShadeLayoutWide = true,
+ burnInParams = null,
+ modifier =
+ Modifier.fillMaxWidth(0.5f)
+ .fillMaxHeight()
+ .align(alignment = Alignment.TopEnd),
+ )
+ }
}
}
}
@@ -142,9 +149,18 @@ constructor(
}
} else {
Column {
- AodPromotedNotificationArea()
+ if (!isShadeLayoutWide) {
+ AodPromotedNotificationArea()
+ }
AodNotificationIcons(
- modifier = Modifier.padding(start = aodIconPadding)
+ modifier =
+ Modifier.padding(
+ top =
+ dimensionResource(
+ R.dimen.keyguard_status_view_bottom_margin
+ ),
+ start = aodIconPadding,
+ )
)
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeOverlay.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeOverlay.kt
index 931134795a53..26e7524f4fa8 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeOverlay.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeOverlay.kt
@@ -29,7 +29,6 @@ import com.android.compose.animation.scene.ContentScope
import com.android.compose.animation.scene.ElementKey
import com.android.compose.animation.scene.UserAction
import com.android.compose.animation.scene.UserActionResult
-import com.android.systemui.battery.BatteryMeterViewController
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
import com.android.systemui.keyguard.ui.composable.blueprint.rememberBurnIn
@@ -44,10 +43,7 @@ import com.android.systemui.scene.ui.composable.Overlay
import com.android.systemui.shade.ui.composable.OverlayShade
import com.android.systemui.shade.ui.composable.OverlayShadeHeader
import com.android.systemui.shade.ui.composable.SingleShadeMeasurePolicy
-import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerStatusBarViewBinder
import com.android.systemui.statusbar.notification.stack.ui.view.NotificationScrollView
-import com.android.systemui.statusbar.phone.ui.StatusBarIconController
-import com.android.systemui.statusbar.phone.ui.TintedIconManager
import dagger.Lazy
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
@@ -58,11 +54,6 @@ class NotificationsShadeOverlay
constructor(
private val actionsViewModelFactory: NotificationsShadeOverlayActionsViewModel.Factory,
private val contentViewModelFactory: NotificationsShadeOverlayContentViewModel.Factory,
- private val tintedIconManagerFactory: TintedIconManager.Factory,
- private val batteryMeterViewControllerFactory: BatteryMeterViewController.Factory,
- private val statusBarIconController: StatusBarIconController,
- private val notificationIconContainerStatusBarViewBinder:
- NotificationIconContainerStatusBarViewBinder,
private val shadeSession: SaveableSession,
private val stackScrollView: Lazy<NotificationScrollView>,
private val clockSection: DefaultClockSection,
@@ -94,18 +85,16 @@ constructor(
}
OverlayShade(
- isShadeLayoutWide = viewModel.isShadeLayoutWide,
panelAlignment = Alignment.TopStart,
modifier = modifier,
onScrimClicked = viewModel::onScrimClicked,
header = {
+ val headerViewModel =
+ rememberViewModel("NotificationsShadeOverlayHeader") {
+ viewModel.shadeHeaderViewModelFactory.create()
+ }
OverlayShadeHeader(
- viewModelFactory = viewModel.shadeHeaderViewModelFactory,
- createTintedIconManager = tintedIconManagerFactory::create,
- createBatteryMeterViewController = batteryMeterViewControllerFactory::create,
- statusBarIconController = statusBarIconController,
- notificationIconContainerStatusBarViewBinder =
- notificationIconContainerStatusBarViewBinder,
+ viewModel = headerViewModel,
modifier =
Modifier.element(NotificationsShade.Elements.StatusBar)
.layoutId(SingleShadeMeasurePolicy.LayoutId.ShadeHeader),
@@ -114,7 +103,7 @@ constructor(
) {
Box {
Column {
- if (viewModel.showHeader) {
+ if (viewModel.showClock) {
val burnIn = rememberBurnIn(clockInteractor)
with(clockSection) {
@@ -140,8 +129,7 @@ constructor(
modifier = Modifier.fillMaxWidth(),
)
}
- // Communicates the bottom position of the drawable area within the shade to
- // NSSL.
+ // Communicates the bottom position of the drawable area within the shade to NSSL.
NotificationStackCutoffGuideline(
stackScrollView = stackScrollView.get(),
viewModel = placeholderViewModel,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
index 4bfbb3a908fa..62a8cc5a7fe3 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
@@ -16,7 +16,6 @@
package com.android.systemui.qs.ui.composable
-import android.view.ViewGroup
import androidx.activity.compose.BackHandler
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.animateDpAsState
@@ -76,7 +75,6 @@ import com.android.compose.animation.scene.animateSceneFloatAsState
import com.android.compose.animation.scene.content.state.TransitionState
import com.android.compose.modifiers.thenIf
import com.android.compose.windowsizeclass.LocalWindowSizeClass
-import com.android.systemui.battery.BatteryMeterViewController
import com.android.systemui.common.ui.compose.windowinsets.CutoutLocation
import com.android.systemui.common.ui.compose.windowinsets.LocalDisplayCutout
import com.android.systemui.compose.modifiers.sysuiResTag
@@ -100,15 +98,14 @@ import com.android.systemui.res.R
import com.android.systemui.scene.session.ui.composable.SaveableSession
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.scene.ui.composable.Scene
+import com.android.systemui.settings.brightness.ui.viewModel.BrightnessMirrorViewModel
import com.android.systemui.shade.ui.composable.CollapsedShadeHeader
import com.android.systemui.shade.ui.composable.ExpandedShadeHeader
import com.android.systemui.shade.ui.composable.Shade
import com.android.systemui.shade.ui.composable.ShadeHeader
+import com.android.systemui.shade.ui.viewmodel.ShadeHeaderViewModel
import com.android.systemui.statusbar.notification.stack.ui.view.NotificationScrollView
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
-import com.android.systemui.statusbar.phone.StatusBarLocation
-import com.android.systemui.statusbar.phone.ui.StatusBarIconController
-import com.android.systemui.statusbar.phone.ui.TintedIconManager
import dagger.Lazy
import javax.inject.Inject
import javax.inject.Named
@@ -125,9 +122,6 @@ constructor(
private val notificationsPlaceholderViewModelFactory: NotificationsPlaceholderViewModel.Factory,
private val actionsViewModelFactory: QuickSettingsUserActionsViewModel.Factory,
private val contentViewModelFactory: QuickSettingsSceneContentViewModel.Factory,
- private val tintedIconManagerFactory: TintedIconManager.Factory,
- private val batteryMeterViewControllerFactory: BatteryMeterViewController.Factory,
- private val statusBarIconController: StatusBarIconController,
private val mediaCarouselController: MediaCarouselController,
@Named(MediaModule.QS_PANEL) private val mediaHost: MediaHost,
) : ExclusiveActivatable(), Scene {
@@ -145,16 +139,26 @@ constructor(
@Composable
override fun ContentScope.Content(modifier: Modifier) {
+ val viewModel =
+ rememberViewModel("QuickSettingsScene-viewModel") { contentViewModelFactory.create() }
+ val headerViewModel =
+ rememberViewModel("QuickSettingsScene-headerViewModel") {
+ viewModel.shadeHeaderViewModelFactory.create()
+ }
+ val brightnessMirrorViewModel =
+ rememberViewModel("QuickSettingsScene-brightnessMirrorViewModel") {
+ viewModel.brightnessMirrorViewModelFactory.create()
+ }
+ val notificationsPlaceholderViewModel =
+ rememberViewModel("QuickSettingsScene-notifPlaceholderViewModel") {
+ notificationsPlaceholderViewModelFactory.create()
+ }
QuickSettingsScene(
notificationStackScrollView = notificationStackScrollView.get(),
- viewModelFactory = contentViewModelFactory,
- notificationsPlaceholderViewModel =
- rememberViewModel("QuickSettingsScene-notifPlaceholderViewModel") {
- notificationsPlaceholderViewModelFactory.create()
- },
- createTintedIconManager = tintedIconManagerFactory::create,
- createBatteryMeterViewController = batteryMeterViewControllerFactory::create,
- statusBarIconController = statusBarIconController,
+ viewModel = viewModel,
+ headerViewModel = headerViewModel,
+ brightnessMirrorViewModel = brightnessMirrorViewModel,
+ notificationsPlaceholderViewModel = notificationsPlaceholderViewModel,
mediaCarouselController = mediaCarouselController,
mediaHost = mediaHost,
modifier = modifier,
@@ -166,23 +170,16 @@ constructor(
@Composable
private fun ContentScope.QuickSettingsScene(
notificationStackScrollView: NotificationScrollView,
- viewModelFactory: QuickSettingsSceneContentViewModel.Factory,
+ viewModel: QuickSettingsSceneContentViewModel,
+ headerViewModel: ShadeHeaderViewModel,
+ brightnessMirrorViewModel: BrightnessMirrorViewModel,
notificationsPlaceholderViewModel: NotificationsPlaceholderViewModel,
- createTintedIconManager: (ViewGroup, StatusBarLocation) -> TintedIconManager,
- createBatteryMeterViewController: (ViewGroup, StatusBarLocation) -> BatteryMeterViewController,
- statusBarIconController: StatusBarIconController,
mediaCarouselController: MediaCarouselController,
mediaHost: MediaHost,
modifier: Modifier = Modifier,
shadeSession: SaveableSession,
) {
val cutoutLocation = LocalDisplayCutout.current.location
-
- val viewModel = rememberViewModel("QuickSettingsScene-viewModel") { viewModelFactory.create() }
- val brightnessMirrorViewModel =
- rememberViewModel("QuickSettingsScene-brightnessMirrorViewModel") {
- viewModel.brightnessMirrorViewModelFactory.create()
- }
val brightnessMirrorShowing by brightnessMirrorViewModel.isShowing.collectAsStateWithLifecycle()
val contentAlpha by
animateFloatAsState(
@@ -222,8 +219,7 @@ private fun ContentScope.QuickSettingsScene(
.graphicsLayer { alpha = contentAlpha }
.thenIf(shouldPunchHoleBehindScrim) {
// Render the scene to an offscreen buffer so that BlendMode.DstOut only clears
- // this
- // scene (and not the one under it) during a scene transition.
+ // this scene (and not the one under it) during a scene transition.
Modifier.graphicsLayer(compositingStrategy = CompositingStrategy.Offscreen)
}
.thenIf(cutoutLocation != CutoutLocation.CENTER) { Modifier.displayCutoutPadding() }
@@ -348,21 +344,11 @@ private fun ContentScope.QuickSettingsScene(
fadeOut(tween(customizingAnimationDuration)),
) {
ExpandedShadeHeader(
- viewModelFactory = viewModel.shadeHeaderViewModelFactory,
- createTintedIconManager = createTintedIconManager,
- createBatteryMeterViewController =
- createBatteryMeterViewController,
- statusBarIconController = statusBarIconController,
+ viewModel = headerViewModel,
modifier = Modifier.padding(horizontal = 16.dp),
)
}
- else ->
- CollapsedShadeHeader(
- viewModelFactory = viewModel.shadeHeaderViewModelFactory,
- createTintedIconManager = createTintedIconManager,
- createBatteryMeterViewController = createBatteryMeterViewController,
- statusBarIconController = statusBarIconController,
- )
+ else -> CollapsedShadeHeader(viewModel = headerViewModel)
}
Spacer(modifier = Modifier.height(16.dp))
// This view has its own horizontal padding
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeOverlay.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeOverlay.kt
index 3ec14a23421c..2fa370458ab0 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeOverlay.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeOverlay.kt
@@ -45,7 +45,6 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.compose.animation.scene.ContentScope
import com.android.compose.animation.scene.UserAction
import com.android.compose.animation.scene.UserActionResult
-import com.android.systemui.battery.BatteryMeterViewController
import com.android.systemui.brightness.ui.compose.BrightnessSliderContainer
import com.android.systemui.compose.modifiers.sysuiResTag
import com.android.systemui.dagger.SysUISingleton
@@ -67,13 +66,10 @@ import com.android.systemui.shade.ui.composable.OverlayShade
import com.android.systemui.shade.ui.composable.OverlayShadeHeader
import com.android.systemui.shade.ui.composable.QuickSettingsOverlayHeader
import com.android.systemui.shade.ui.composable.SingleShadeMeasurePolicy
-import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerStatusBarViewBinder
import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimBounds
import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimShape
import com.android.systemui.statusbar.notification.stack.ui.view.NotificationScrollView
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
-import com.android.systemui.statusbar.phone.ui.StatusBarIconController
-import com.android.systemui.statusbar.phone.ui.TintedIconManager
import dagger.Lazy
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
@@ -84,11 +80,7 @@ class QuickSettingsShadeOverlay
constructor(
private val actionsViewModelFactory: QuickSettingsShadeOverlayActionsViewModel.Factory,
private val contentViewModelFactory: QuickSettingsShadeOverlayContentViewModel.Factory,
- private val tintedIconManagerFactory: TintedIconManager.Factory,
- private val batteryMeterViewControllerFactory: BatteryMeterViewController.Factory,
- private val statusBarIconController: StatusBarIconController,
- private val notificationIconContainerStatusBarViewBinder:
- NotificationIconContainerStatusBarViewBinder,
+ private val quickSettingsContainerViewModelFactory: QuickSettingsContainerViewModel.Factory,
private val notificationStackScrollView: Lazy<NotificationScrollView>,
private val notificationsPlaceholderViewModelFactory: NotificationsPlaceholderViewModel.Factory,
) : Overlay {
@@ -107,35 +99,35 @@ constructor(
@Composable
override fun ContentScope.Content(modifier: Modifier) {
- val viewModel =
- rememberViewModel("QuickSettingsShadeOverlay") { contentViewModelFactory.create() }
+ val contentViewModel =
+ rememberViewModel("QuickSettingsShadeOverlayContent") {
+ contentViewModelFactory.create()
+ }
+ val quickSettingsContainerViewModel =
+ rememberViewModel("QuickSettingsShadeOverlayContainer") {
+ // TODO(b/393054014): Add support for brightness mirroring.
+ quickSettingsContainerViewModelFactory.create(supportsBrightnessMirroring = false)
+ }
val panelCornerRadius =
with(LocalDensity.current) { OverlayShade.Dimensions.PanelCornerRadius.toPx().toInt() }
- // set the bounds to null when the QuickSettings overlay disappears
- DisposableEffect(Unit) { onDispose { viewModel.onPanelShapeChanged(null) } }
+ // Set the bounds to null when the QuickSettings overlay disappears.
+ DisposableEffect(Unit) { onDispose { contentViewModel.onPanelShapeChanged(null) } }
Box(modifier = modifier) {
SnoozeableHeadsUpNotificationSpace(
stackScrollView = notificationStackScrollView.get(),
viewModel =
- rememberViewModel("QuickSettingsShadeOverlay") {
+ rememberViewModel("QuickSettingsShadeOverlayPlaceholder") {
notificationsPlaceholderViewModelFactory.create()
},
)
OverlayShade(
- isShadeLayoutWide = viewModel.isShadeLayoutWide,
panelAlignment = Alignment.TopEnd,
- onScrimClicked = viewModel::onScrimClicked,
+ onScrimClicked = contentViewModel::onScrimClicked,
header = {
OverlayShadeHeader(
- viewModelFactory = viewModel.shadeHeaderViewModelFactory,
- createTintedIconManager = tintedIconManagerFactory::create,
- createBatteryMeterViewController =
- batteryMeterViewControllerFactory::create,
- statusBarIconController = statusBarIconController,
- notificationIconContainerStatusBarViewBinder =
- notificationIconContainerStatusBarViewBinder,
+ viewModel = quickSettingsContainerViewModel.shadeHeaderViewModel,
modifier =
Modifier.element(NotificationsShade.Elements.StatusBar)
.layoutId(SingleShadeMeasurePolicy.LayoutId.ShadeHeader),
@@ -143,7 +135,7 @@ constructor(
},
) {
ShadeBody(
- viewModel = viewModel.quickSettingsContainerViewModel,
+ viewModel = quickSettingsContainerViewModel,
modifier =
Modifier.onPlaced { coordinates ->
val boundsInWindow = coordinates.boundsInWindow()
@@ -160,14 +152,12 @@ constructor(
topRadius = 0,
bottomRadius = panelCornerRadius,
)
- viewModel.onPanelShapeChanged(shape)
+ contentViewModel.onPanelShapeChanged(shape)
},
header = {
- if (viewModel.isShadeLayoutWide) {
+ if (quickSettingsContainerViewModel.showHeader) {
QuickSettingsOverlayHeader(
- viewModelFactory = viewModel.shadeHeaderViewModelFactory,
- createBatteryMeterViewController =
- batteryMeterViewControllerFactory::create,
+ viewModel = quickSettingsContainerViewModel.shadeHeaderViewModel,
modifier =
Modifier.padding(top = QuickSettingsShade.Dimensions.Padding),
)
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/ribbon/ui/composable/Ribbon.kt b/packages/SystemUI/compose/features/src/com/android/systemui/ribbon/ui/composable/Ribbon.kt
index daa15929b9ce..377bebc404e7 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/ribbon/ui/composable/Ribbon.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/ribbon/ui/composable/Ribbon.kt
@@ -20,8 +20,12 @@ import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.drawWithCache
import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.ColorFilter
+import androidx.compose.ui.graphics.ColorMatrix
import androidx.compose.ui.graphics.graphicsLayer
+import androidx.compose.ui.graphics.layer.drawLayer
import androidx.compose.ui.layout.layout
import com.android.compose.modifiers.thenIf
import kotlin.math.PI
@@ -39,6 +43,9 @@ import kotlin.math.tan
* The background color of the strip can be modified by passing a value to the [backgroundColor] or
* `null` to remove the strip background.
*
+ * The [colorSaturation] is a function that returns that amount of color saturation to apply to the
+ * entire ribbon. If it's `1`, the full color will be used, if it's `0` it will be greyscale.
+ *
* Note: this function assumes that it's been placed at the bottom right of its parent by its
* caller. It's the caller's responsibility to meet that assumption by actually placing this
* composable element at the bottom right.
@@ -49,6 +56,7 @@ fun BottomRightCornerRibbon(
modifier: Modifier = Modifier,
degrees: Int = 45,
alpha: Float = 0.6f,
+ colorSaturation: () -> Float = { 1f },
backgroundColor: Color? = Color.Red,
) {
check(degrees in 1..89)
@@ -73,6 +81,22 @@ fun BottomRightCornerRibbon(
translationY = (h - w * sine + h * cosine) / 2f
rotationZ = 360f - degrees
}
+ .drawWithCache {
+ val layer =
+ obtainGraphicsLayer().apply {
+ record {
+ colorFilter =
+ ColorFilter.colorMatrix(
+ colorMatrix =
+ ColorMatrix().apply {
+ setToSaturation(colorSaturation())
+ }
+ )
+ drawContent()
+ }
+ }
+ onDrawWithContent { drawLayer(layer) }
+ }
.thenIf(backgroundColor != null) { Modifier.background(backgroundColor!!) }
.layout { measurable, constraints ->
val placeable = measurable.measure(constraints)
@@ -87,6 +111,6 @@ fun BottomRightCornerRibbon(
) {
placeable.place(leftPadding, 0)
}
- }
+ },
)
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt
index 6c0c5c7e49b9..40e3000ee8a7 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt
@@ -232,6 +232,7 @@ fun SceneContainer(
BottomRightCornerRibbon(
content = { Text(text = "flexi\uD83E\uDD43", color = Color.White) },
+ colorSaturation = { viewModel.ribbonColorSaturation },
modifier = Modifier.align(Alignment.BottomEnd),
)
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt
index fc59d40ec443..3d2d7c37ce48 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt
@@ -58,29 +58,31 @@ import com.android.systemui.res.R
/** Renders a lightweight shade UI container, as an overlay. */
@Composable
fun ContentScope.OverlayShade(
- isShadeLayoutWide: Boolean,
panelAlignment: Alignment,
onScrimClicked: () -> Unit,
modifier: Modifier = Modifier,
header: @Composable () -> Unit,
content: @Composable () -> Unit,
) {
+ val isFullWidth = isFullWidthShade()
Box(modifier) {
Scrim(onClicked = onScrimClicked)
- Box(modifier = Modifier.fillMaxSize().panelPadding(), contentAlignment = panelAlignment) {
+ Box(
+ modifier = Modifier.fillMaxSize().panelContainerPadding(isFullWidth),
+ contentAlignment = panelAlignment,
+ ) {
Panel(
- isShadeLayoutWide = isShadeLayoutWide,
modifier =
Modifier.overscroll(verticalOverscrollEffect)
.element(OverlayShade.Elements.Panel)
- .panelSize(),
- header = header,
+ .panelWidth(isFullWidth),
+ header = header.takeIf { isFullWidth },
content = content,
)
}
- if (isShadeLayoutWide) {
+ if (!isFullWidth) {
header()
}
}
@@ -100,9 +102,8 @@ private fun ContentScope.Scrim(onClicked: () -> Unit, modifier: Modifier = Modif
@Composable
private fun ContentScope.Panel(
- isShadeLayoutWide: Boolean,
modifier: Modifier = Modifier,
- header: @Composable () -> Unit,
+ header: (@Composable () -> Unit)?,
content: @Composable () -> Unit,
) {
Box(modifier = modifier.clip(OverlayShade.Shapes.RoundedCornerPanel)) {
@@ -117,9 +118,7 @@ private fun ContentScope.Panel(
)
Column {
- if (!isShadeLayoutWide) {
- header()
- }
+ header?.invoke()
// This content is intentionally rendered as a separate element from the background in
// order to allow for more flexibility when defining transitions.
@@ -129,14 +128,12 @@ private fun ContentScope.Panel(
}
@Composable
-private fun Modifier.panelSize(): Modifier {
- return this.then(
- if (isFullWidthShade()) {
- Modifier.fillMaxWidth()
- } else {
- Modifier.width(dimensionResource(id = R.dimen.shade_panel_width))
- }
- )
+private fun Modifier.panelWidth(isFullWidthPanel: Boolean): Modifier {
+ return if (isFullWidthPanel) {
+ fillMaxWidth()
+ } else {
+ width(dimensionResource(id = R.dimen.shade_panel_width))
+ }
}
@Composable
@@ -146,27 +143,23 @@ internal fun isFullWidthShade(): Boolean {
}
@Composable
-private fun Modifier.panelPadding(): Modifier {
- val widthSizeClass = LocalWindowSizeClass.current.widthSizeClass
+private fun Modifier.panelContainerPadding(isFullWidthPanel: Boolean): Modifier {
+ if (isFullWidthPanel) {
+ return this
+ }
val systemBars = WindowInsets.systemBarsIgnoringVisibility
val displayCutout = WindowInsets.displayCutout
val waterfall = WindowInsets.waterfall
val horizontalPadding =
PaddingValues(horizontal = dimensionResource(id = R.dimen.shade_panel_margin_horizontal))
-
- val combinedPadding =
+ return padding(
combinePaddings(
systemBars.asPaddingValues(),
displayCutout.asPaddingValues(),
waterfall.asPaddingValues(),
horizontalPadding,
)
-
- return if (widthSizeClass == WindowWidthSizeClass.Compact) {
- padding(bottom = combinedPadding.calculateBottomPadding())
- } else {
- padding(combinedPadding)
- }
+ )
}
/** Creates a union of [paddingValues] by using the max padding of each edge. */
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt
index c5d28adce601..02de78bc84ce 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt
@@ -74,23 +74,20 @@ import com.android.systemui.common.ui.compose.windowinsets.CutoutLocation
import com.android.systemui.common.ui.compose.windowinsets.LocalDisplayCutout
import com.android.systemui.common.ui.compose.windowinsets.LocalScreenCornerRadius
import com.android.systemui.compose.modifiers.sysuiResTag
-import com.android.systemui.lifecycle.rememberViewModel
import com.android.systemui.privacy.OngoingPrivacyChip
import com.android.systemui.res.R
import com.android.systemui.scene.shared.model.Scenes
-import com.android.systemui.shade.shared.flag.DualShade
import com.android.systemui.shade.ui.composable.ShadeHeader.Colors.chipBackground
import com.android.systemui.shade.ui.composable.ShadeHeader.Colors.chipHighlighted
import com.android.systemui.shade.ui.composable.ShadeHeader.Colors.onScrimDim
import com.android.systemui.shade.ui.composable.ShadeHeader.Dimensions.CollapsedHeight
import com.android.systemui.shade.ui.composable.ShadeHeader.Values.ClockScale
import com.android.systemui.shade.ui.viewmodel.ShadeHeaderViewModel
+import com.android.systemui.shade.ui.viewmodel.ShadeHeaderViewModel.HeaderChipHighlight
import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerStatusBarViewBinder
import com.android.systemui.statusbar.phone.NotificationIconContainer
import com.android.systemui.statusbar.phone.StatusBarLocation
import com.android.systemui.statusbar.phone.StatusIconContainer
-import com.android.systemui.statusbar.phone.ui.StatusBarIconController
-import com.android.systemui.statusbar.phone.ui.TintedIconManager
import com.android.systemui.statusbar.pipeline.mobile.ui.view.ModernShadeCarrierGroupMobileView
import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.ShadeCarrierGroupMobileIconViewModel
import com.android.systemui.statusbar.policy.Clock
@@ -137,14 +134,9 @@ object ShadeHeader {
/** The status bar that appears above the Shade scene on small screens */
@Composable
fun ContentScope.CollapsedShadeHeader(
- viewModelFactory: ShadeHeaderViewModel.Factory,
- createTintedIconManager: (ViewGroup, StatusBarLocation) -> TintedIconManager,
- createBatteryMeterViewController: (ViewGroup, StatusBarLocation) -> BatteryMeterViewController,
- statusBarIconController: StatusBarIconController,
+ viewModel: ShadeHeaderViewModel,
modifier: Modifier = Modifier,
) {
- val viewModel = rememberViewModel("CollapsedShadeHeader") { viewModelFactory.create() }
-
val cutoutLocation = LocalDisplayCutout.current.location
val horizontalPadding =
max(LocalScreenCornerRadius.current / 2f, Shade.Dimensions.HorizontalPadding)
@@ -157,12 +149,14 @@ fun ContentScope.CollapsedShadeHeader(
}
}
+ val longerDateText by viewModel.longerDateText.collectAsStateWithLifecycle()
+ val shorterDateText by viewModel.shorterDateText.collectAsStateWithLifecycle()
+
val isShadeLayoutWide = viewModel.isShadeLayoutWide
val isPrivacyChipVisible by viewModel.isPrivacyChipVisible.collectAsStateWithLifecycle()
- // This layout assumes it is globally positioned at (0, 0) and is the
- // same size as the screen.
+ // This layout assumes it is globally positioned at (0, 0) and is the same size as the screen.
CutoutAwareShadeHeader(
modifier = modifier.sysuiResTag(ShadeHeader.TestTags.Root),
startContent = {
@@ -171,9 +165,11 @@ fun ContentScope.CollapsedShadeHeader(
horizontalArrangement = Arrangement.spacedBy(5.dp),
modifier = Modifier.padding(horizontal = horizontalPadding),
) {
- Clock(scale = 1f, viewModel = viewModel)
+ Clock(scale = 1f, onClick = viewModel::onClockClicked)
VariableDayDate(
- viewModel = viewModel,
+ longerDateText = longerDateText,
+ shorterDateText = shorterDateText,
+ chipHighlight = viewModel.notificationsChipHighlight,
modifier = Modifier.element(ShadeHeader.Elements.CollapsedContentStart),
)
}
@@ -202,17 +198,17 @@ fun ContentScope.CollapsedShadeHeader(
if (isShadeLayoutWide) {
ShadeCarrierGroup(viewModel = viewModel)
}
- SystemIconChip(viewModel = viewModel, isClickable = isShadeLayoutWide) {
+ SystemIconChip(
+ onClick = viewModel::onSystemIconChipClicked.takeIf { isShadeLayoutWide }
+ ) {
StatusIcons(
viewModel = viewModel,
- createTintedIconManager = createTintedIconManager,
- statusBarIconController = statusBarIconController,
useExpandedFormat = useExpandedTextFormat,
modifier = Modifier.padding(end = 6.dp).weight(1f, fill = false),
)
BatteryIcon(
- viewModel = viewModel,
- createBatteryMeterViewController = createBatteryMeterViewController,
+ createBatteryMeterViewController =
+ viewModel.createBatteryMeterViewController,
useExpandedFormat = useExpandedTextFormat,
modifier = Modifier.padding(vertical = 8.dp),
)
@@ -226,18 +222,15 @@ fun ContentScope.CollapsedShadeHeader(
/** The status bar that appears above the Quick Settings scene on small screens */
@Composable
fun ContentScope.ExpandedShadeHeader(
- viewModelFactory: ShadeHeaderViewModel.Factory,
- createTintedIconManager: (ViewGroup, StatusBarLocation) -> TintedIconManager,
- createBatteryMeterViewController: (ViewGroup, StatusBarLocation) -> BatteryMeterViewController,
- statusBarIconController: StatusBarIconController,
+ viewModel: ShadeHeaderViewModel,
modifier: Modifier = Modifier,
) {
- val viewModel = rememberViewModel("ExpandedShadeHeader") { viewModelFactory.create() }
-
val useExpandedFormat by remember {
derivedStateOf { shouldUseExpandedFormat(layoutState.transitionState) }
}
+ val longerDateText by viewModel.longerDateText.collectAsStateWithLifecycle()
+ val shorterDateText by viewModel.shorterDateText.collectAsStateWithLifecycle()
val isPrivacyChipVisible by viewModel.isPrivacyChipVisible.collectAsStateWithLifecycle()
Box(modifier = modifier.sysuiResTag(ShadeHeader.TestTags.Root)) {
@@ -256,7 +249,7 @@ fun ContentScope.ExpandedShadeHeader(
Box {
Clock(
scale = 2.57f,
- viewModel = viewModel,
+ onClick = viewModel::onClockClicked,
modifier = Modifier.align(Alignment.CenterStart),
)
}
@@ -275,20 +268,23 @@ fun ContentScope.ExpandedShadeHeader(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.element(ShadeHeader.Elements.ExpandedContent),
) {
- VariableDayDate(viewModel = viewModel, modifier = Modifier.widthIn(max = 90.dp))
+ VariableDayDate(
+ longerDateText = longerDateText,
+ shorterDateText = shorterDateText,
+ chipHighlight = viewModel.notificationsChipHighlight,
+ modifier = Modifier.widthIn(max = 90.dp),
+ )
Spacer(modifier = Modifier.weight(1f))
- SystemIconChip(viewModel = viewModel) {
+ SystemIconChip {
StatusIcons(
viewModel = viewModel,
- createTintedIconManager = createTintedIconManager,
- statusBarIconController = statusBarIconController,
useExpandedFormat = useExpandedFormat,
modifier = Modifier.padding(end = 6.dp).weight(1f, fill = false),
)
BatteryIcon(
- viewModel = viewModel,
useExpandedFormat = useExpandedFormat,
- createBatteryMeterViewController = createBatteryMeterViewController,
+ createBatteryMeterViewController =
+ viewModel.createBatteryMeterViewController,
)
}
}
@@ -302,15 +298,9 @@ fun ContentScope.ExpandedShadeHeader(
*/
@Composable
fun ContentScope.OverlayShadeHeader(
- viewModelFactory: ShadeHeaderViewModel.Factory,
- createTintedIconManager: (ViewGroup, StatusBarLocation) -> TintedIconManager,
- createBatteryMeterViewController: (ViewGroup, StatusBarLocation) -> BatteryMeterViewController,
- statusBarIconController: StatusBarIconController,
- notificationIconContainerStatusBarViewBinder: NotificationIconContainerStatusBarViewBinder,
+ viewModel: ShadeHeaderViewModel,
modifier: Modifier = Modifier,
) {
- val viewModel = rememberViewModel("OverlayShadeHeader") { viewModelFactory.create() }
-
val horizontalPadding =
max(LocalScreenCornerRadius.current / 2f, Shade.Dimensions.HorizontalPadding)
@@ -318,8 +308,7 @@ fun ContentScope.OverlayShadeHeader(
val isPrivacyChipVisible by viewModel.isPrivacyChipVisible.collectAsStateWithLifecycle()
- // This layout assumes it is globally positioned at (0, 0) and is the
- // same size as the screen.
+ // This layout assumes it is globally positioned at (0, 0) and is the same size as the screen.
CutoutAwareShadeHeader(
modifier = modifier.sysuiResTag(ShadeHeader.TestTags.Root),
startContent = {
@@ -330,21 +319,32 @@ fun ContentScope.OverlayShadeHeader(
if (isShadeLayoutWide) {
Clock(
scale = 1f,
- viewModel = viewModel,
+ onClick = viewModel::onClockClicked,
modifier = Modifier.padding(horizontal = 4.dp),
)
Spacer(modifier = Modifier.width(5.dp))
}
- NotificationIconChip(viewModel = viewModel) {
+ val chipHighlight = viewModel.notificationsChipHighlight
+ NotificationIconChip(
+ chipHighlight = chipHighlight,
+ onClick = viewModel::onNotificationIconChipClicked,
+ ) {
if (isShadeLayoutWide) {
NotificationIcons(
- viewModel = viewModel,
+ chipHighlight = chipHighlight,
notificationIconContainerStatusBarViewBinder =
- notificationIconContainerStatusBarViewBinder,
+ viewModel.notificationIconContainerStatusBarViewBinder,
modifier = Modifier.width(IntrinsicSize.Min).height(20.dp),
)
} else {
- VariableDayDate(viewModel = viewModel)
+ val longerDateText by viewModel.longerDateText.collectAsStateWithLifecycle()
+ val shorterDateText by
+ viewModel.shorterDateText.collectAsStateWithLifecycle()
+ VariableDayDate(
+ longerDateText = longerDateText,
+ shorterDateText = shorterDateText,
+ chipHighlight = viewModel.notificationsChipHighlight,
+ )
}
}
}
@@ -355,20 +355,22 @@ fun ContentScope.OverlayShadeHeader(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.padding(horizontal = horizontalPadding),
) {
- SystemIconChip(viewModel = viewModel, isClickable = true, showBackground = true) {
+ val chipHighlight = viewModel.quickSettingsChipHighlight
+ SystemIconChip(
+ chipHighlight = chipHighlight,
+ onClick = viewModel::onSystemIconChipClicked,
+ ) {
StatusIcons(
viewModel = viewModel,
- createTintedIconManager = createTintedIconManager,
- statusBarIconController = statusBarIconController,
useExpandedFormat = false,
- highlightable = true,
modifier = Modifier.padding(end = 6.dp).weight(1f, fill = false),
+ chipHighlight = chipHighlight,
)
BatteryIcon(
- viewModel = viewModel,
- createBatteryMeterViewController = createBatteryMeterViewController,
+ createBatteryMeterViewController =
+ viewModel.createBatteryMeterViewController,
useExpandedFormat = false,
- highlightable = true,
+ chipHighlight = chipHighlight,
)
}
if (isPrivacyChipVisible) {
@@ -391,13 +393,7 @@ fun ContentScope.OverlayShadeHeader(
/** The header that appears at the top of the Quick Settings shade overlay. */
@Composable
-fun ContentScope.QuickSettingsOverlayHeader(
- viewModelFactory: ShadeHeaderViewModel.Factory,
- createBatteryMeterViewController: (ViewGroup, StatusBarLocation) -> BatteryMeterViewController,
- modifier: Modifier = Modifier,
-) {
- val viewModel = rememberViewModel("QuickSettingsOverlayHeader") { viewModelFactory.create() }
-
+fun QuickSettingsOverlayHeader(viewModel: ShadeHeaderViewModel, modifier: Modifier = Modifier) {
Row(
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
@@ -405,8 +401,7 @@ fun ContentScope.QuickSettingsOverlayHeader(
) {
ShadeCarrierGroup(viewModel = viewModel)
BatteryIcon(
- viewModel = viewModel,
- createBatteryMeterViewController = createBatteryMeterViewController,
+ createBatteryMeterViewController = viewModel.createBatteryMeterViewController,
useExpandedFormat = true,
)
}
@@ -468,11 +463,7 @@ private fun CutoutAwareShadeHeader(
}
@Composable
-private fun ContentScope.Clock(
- scale: Float,
- viewModel: ShadeHeaderViewModel,
- modifier: Modifier = Modifier,
-) {
+private fun ContentScope.Clock(scale: Float, onClick: () -> Unit, modifier: Modifier = Modifier) {
val layoutDirection = LocalLayoutDirection.current
Element(key = ShadeHeader.Elements.Clock, modifier = modifier) {
@@ -500,18 +491,17 @@ private fun ContentScope.Clock(
0.5f,
)
}
- .clickable { viewModel.onClockClicked() },
+ .clickable { onClick() },
)
}
}
@Composable
private fun BatteryIcon(
- viewModel: ShadeHeaderViewModel,
createBatteryMeterViewController: (ViewGroup, StatusBarLocation) -> BatteryMeterViewController,
useExpandedFormat: Boolean,
- highlightable: Boolean = false,
modifier: Modifier = Modifier,
+ chipHighlight: HeaderChipHighlight = HeaderChipHighlight.None,
) {
val localContext = LocalContext.current
val themedContext =
@@ -521,8 +511,6 @@ private fun BatteryIcon(
val inverseColor =
Utils.getColorAttrDefaultColor(themedContext, android.R.attr.textColorPrimaryInverse)
- val isHighlighted = viewModel.highlightQuickSettingsIcons
-
AndroidView(
factory = { context ->
val batteryIcon = BatteryMeterView(context, null)
@@ -544,18 +532,12 @@ private fun BatteryIcon(
// TODO(b/298525212): use MODE_ESTIMATE in collapsed view when the screen
// has no center cutout. See [QsBatteryModeController.getBatteryMode]
batteryIcon.setPercentShowMode(
- if (useExpandedFormat) {
- BatteryMeterView.MODE_ESTIMATE
- } else {
- BatteryMeterView.MODE_ON
- }
+ if (useExpandedFormat) BatteryMeterView.MODE_ESTIMATE else BatteryMeterView.MODE_ON
)
- if (highlightable) {
- if (isHighlighted) {
- batteryIcon.updateColors(primaryColor, inverseColor, inverseColor)
- } else {
- batteryIcon.updateColors(primaryColor, inverseColor, primaryColor)
- }
+ if (chipHighlight is HeaderChipHighlight.Strong) {
+ batteryIcon.updateColors(primaryColor, inverseColor, inverseColor)
+ } else if (chipHighlight is HeaderChipHighlight.Weak) {
+ batteryIcon.updateColors(primaryColor, inverseColor, primaryColor)
}
},
modifier = modifier,
@@ -590,14 +572,12 @@ private fun ShadeCarrierGroup(viewModel: ShadeHeaderViewModel, modifier: Modifie
@Composable
private fun NotificationIcons(
- viewModel: ShadeHeaderViewModel,
+ chipHighlight: HeaderChipHighlight,
notificationIconContainerStatusBarViewBinder: NotificationIconContainerStatusBarViewBinder,
modifier: Modifier = Modifier,
) {
val scope = rememberCoroutineScope()
- val isHighlighted = viewModel.highlightNotificationIcons
-
AndroidView(
factory = { context ->
NotificationIconContainer(context, null).also { view ->
@@ -610,7 +590,7 @@ private fun NotificationIcons(
}
}
},
- update = { it.setUseInverseOverrideIconColor(isHighlighted) },
+ update = { it.setUseInverseOverrideIconColor(chipHighlight is HeaderChipHighlight.Strong) },
modifier = modifier,
)
}
@@ -618,11 +598,9 @@ private fun NotificationIcons(
@Composable
private fun ContentScope.StatusIcons(
viewModel: ShadeHeaderViewModel,
- createTintedIconManager: (ViewGroup, StatusBarLocation) -> TintedIconManager,
- statusBarIconController: StatusBarIconController,
useExpandedFormat: Boolean,
- highlightable: Boolean = false,
modifier: Modifier = Modifier,
+ chipHighlight: HeaderChipHighlight = HeaderChipHighlight.None,
) {
val localContext = LocalContext.current
val themedContext =
@@ -632,8 +610,6 @@ private fun ContentScope.StatusIcons(
val inverseColor =
Utils.getColorAttrDefaultColor(themedContext, android.R.attr.textColorPrimaryInverse)
- val isHighlighted = viewModel.highlightQuickSettingsIcons
-
val carrierIconSlots =
listOf(stringResource(id = com.android.internal.R.string.status_bar_mobile))
val cameraSlot = stringResource(id = com.android.internal.R.string.status_bar_camera)
@@ -648,12 +624,14 @@ private fun ContentScope.StatusIcons(
viewModel.isLocationIndicationEnabled.collectAsStateWithLifecycle()
val iconContainer = remember { StatusIconContainer(themedContext, null) }
- val iconManager = remember { createTintedIconManager(iconContainer, StatusBarLocation.QS) }
+ val iconManager = remember {
+ viewModel.createTintedIconManager(iconContainer, StatusBarLocation.QS)
+ }
AndroidView(
factory = { context ->
iconManager.setTint(primaryColor, inverseColor)
- statusBarIconController.addIconGroup(iconManager)
+ viewModel.statusBarIconController.addIconGroup(iconManager)
iconContainer
},
@@ -686,12 +664,10 @@ private fun ContentScope.StatusIcons(
iconContainer.removeIgnoredSlot(locationSlot)
}
- if (highlightable) {
- if (isHighlighted) {
- iconManager.setTint(inverseColor, primaryColor)
- } else {
- iconManager.setTint(primaryColor, inverseColor)
- }
+ if (chipHighlight is HeaderChipHighlight.Strong) {
+ iconManager.setTint(inverseColor, primaryColor)
+ } else if (chipHighlight is HeaderChipHighlight.Weak) {
+ iconManager.setTint(primaryColor, inverseColor)
}
},
modifier = modifier,
@@ -700,15 +676,12 @@ private fun ContentScope.StatusIcons(
@Composable
private fun NotificationIconChip(
- viewModel: ShadeHeaderViewModel,
+ chipHighlight: HeaderChipHighlight,
+ onClick: () -> Unit,
modifier: Modifier = Modifier,
content: @Composable RowScope.() -> Unit,
) {
val interactionSource = remember { MutableInteractionSource() }
- val backgroundColor =
- if (viewModel.highlightNotificationIcons) MaterialTheme.colorScheme.chipHighlighted
- else MaterialTheme.colorScheme.chipBackground
-
Box(modifier = modifier) {
Row(
modifier =
@@ -716,16 +689,15 @@ private fun NotificationIconChip(
.clickable(
interactionSource = interactionSource,
indication = null,
- onClick = { viewModel.onNotificationIconChipClicked() },
+ onClick = { onClick() },
)
- .thenIf(DualShade.isEnabled) {
- Modifier.graphicsLayer {
- shape = RoundedCornerShape(25.dp)
- clip = true
- }
- .background(backgroundColor)
- .padding(horizontal = 8.dp, vertical = 4.dp)
- }
+ .clip(RoundedCornerShape(25.dp))
+ .background(
+ if (chipHighlight is HeaderChipHighlight.Strong)
+ MaterialTheme.colorScheme.chipHighlighted
+ else MaterialTheme.colorScheme.chipBackground
+ )
+ .padding(horizontal = 8.dp, vertical = 4.dp)
) {
content()
}
@@ -734,10 +706,9 @@ private fun NotificationIconChip(
@Composable
private fun SystemIconChip(
- viewModel: ShadeHeaderViewModel,
- isClickable: Boolean = false,
- showBackground: Boolean = false,
modifier: Modifier = Modifier,
+ chipHighlight: HeaderChipHighlight = HeaderChipHighlight.None,
+ onClick: (() -> Unit)? = null,
content: @Composable RowScope.() -> Unit,
) {
val interactionSource = remember { MutableInteractionSource() }
@@ -746,14 +717,14 @@ private fun SystemIconChip(
Modifier.clip(RoundedCornerShape(CollapsedHeight / 4))
.background(MaterialTheme.colorScheme.onScrimDim)
val backgroundColor =
- if (viewModel.highlightQuickSettingsIcons) MaterialTheme.colorScheme.chipHighlighted
+ if (chipHighlight is HeaderChipHighlight.Strong) MaterialTheme.colorScheme.chipHighlighted
else MaterialTheme.colorScheme.chipBackground
Row(
verticalAlignment = Alignment.CenterVertically,
modifier =
modifier
- .thenIf(showBackground) {
+ .thenIf(chipHighlight !is HeaderChipHighlight.None) {
Modifier.graphicsLayer {
shape = RoundedCornerShape(25.dp)
clip = true
@@ -761,11 +732,11 @@ private fun SystemIconChip(
.background(backgroundColor)
.padding(horizontal = 8.dp, vertical = 4.dp)
}
- .thenIf(isClickable) {
+ .thenIf(onClick != null) {
Modifier.clickable(
interactionSource = interactionSource,
indication = null,
- onClick = { viewModel.onSystemIconChipClicked() },
+ onClick = { onClick?.invoke() },
)
}
.thenIf(isHovered) { hoverModifier },
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
index f829a0d6facf..5040490da8f6 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
@@ -101,6 +101,7 @@ import com.android.systemui.scene.session.ui.composable.SaveableSession
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.scene.ui.composable.Scene
import com.android.systemui.shade.shared.model.ShadeMode
+import com.android.systemui.shade.ui.viewmodel.ShadeHeaderViewModel
import com.android.systemui.shade.ui.viewmodel.ShadeSceneContentViewModel
import com.android.systemui.shade.ui.viewmodel.ShadeUserActionsViewModel
import com.android.systemui.statusbar.notification.stack.ui.view.NotificationScrollView
@@ -124,8 +125,6 @@ object Shade {
object Dimensions {
val HorizontalPadding = 16.dp
- val ScrimOverscrollLimit = 32.dp
- const val ScrimVisibilityThreshold = 5f
}
}
@@ -160,15 +159,22 @@ constructor(
override val userActions: Flow<Map<UserAction, UserActionResult>> = actionsViewModel.actions
@Composable
- override fun ContentScope.Content(modifier: Modifier) =
+ override fun ContentScope.Content(modifier: Modifier) {
+ val viewModel =
+ rememberViewModel("ShadeScene-viewModel") { contentViewModelFactory.create() }
+ val headerViewModel =
+ rememberViewModel("ShadeScene-headerViewModel") {
+ viewModel.shadeHeaderViewModelFactory.create()
+ }
+ val notificationsPlaceholderViewModel =
+ rememberViewModel("ShadeScene-notifPlaceholderViewModel") {
+ notificationsPlaceholderViewModelFactory.create()
+ }
ShadeScene(
notificationStackScrollView.get(),
- viewModel =
- rememberViewModel("ShadeScene-viewModel") { contentViewModelFactory.create() },
- notificationsPlaceholderViewModel =
- rememberViewModel("ShadeScene-notifPlaceholderViewModel") {
- notificationsPlaceholderViewModelFactory.create()
- },
+ viewModel = viewModel,
+ headerViewModel = headerViewModel,
+ notificationsPlaceholderViewModel = notificationsPlaceholderViewModel,
createTintedIconManager = tintedIconManagerFactory::create,
createBatteryMeterViewController = batteryMeterViewControllerFactory::create,
statusBarIconController = statusBarIconController,
@@ -180,6 +186,7 @@ constructor(
usingCollapsedLandscapeMedia =
Utils.useCollapsedMediaInLandscape(LocalContext.current.resources),
)
+ }
init {
qqsMediaHost.expansion = EXPANDED
@@ -196,6 +203,7 @@ constructor(
private fun ContentScope.ShadeScene(
notificationStackScrollView: NotificationScrollView,
viewModel: ShadeSceneContentViewModel,
+ headerViewModel: ShadeHeaderViewModel,
notificationsPlaceholderViewModel: NotificationsPlaceholderViewModel,
createTintedIconManager: (ViewGroup, StatusBarLocation) -> TintedIconManager,
createBatteryMeterViewController: (ViewGroup, StatusBarLocation) -> BatteryMeterViewController,
@@ -207,13 +215,13 @@ private fun ContentScope.ShadeScene(
shadeSession: SaveableSession,
usingCollapsedLandscapeMedia: Boolean,
) {
-
val shadeMode by viewModel.shadeMode.collectAsStateWithLifecycle()
when (shadeMode) {
is ShadeMode.Single ->
SingleShade(
notificationStackScrollView = notificationStackScrollView,
viewModel = viewModel,
+ headerViewModel = headerViewModel,
notificationsPlaceholderViewModel = notificationsPlaceholderViewModel,
createTintedIconManager = createTintedIconManager,
createBatteryMeterViewController = createBatteryMeterViewController,
@@ -228,10 +236,8 @@ private fun ContentScope.ShadeScene(
SplitShade(
notificationStackScrollView = notificationStackScrollView,
viewModel = viewModel,
+ headerViewModel = headerViewModel,
notificationsPlaceholderViewModel = notificationsPlaceholderViewModel,
- createTintedIconManager = createTintedIconManager,
- createBatteryMeterViewController = createBatteryMeterViewController,
- statusBarIconController = statusBarIconController,
mediaCarouselController = mediaCarouselController,
mediaHost = qsMediaHost,
modifier = modifier,
@@ -245,6 +251,7 @@ private fun ContentScope.ShadeScene(
private fun ContentScope.SingleShade(
notificationStackScrollView: NotificationScrollView,
viewModel: ShadeSceneContentViewModel,
+ headerViewModel: ShadeHeaderViewModel,
notificationsPlaceholderViewModel: NotificationsPlaceholderViewModel,
createTintedIconManager: (ViewGroup, StatusBarLocation) -> TintedIconManager,
createBatteryMeterViewController: (ViewGroup, StatusBarLocation) -> BatteryMeterViewController,
@@ -332,10 +339,7 @@ private fun ContentScope.SingleShade(
},
content = {
CollapsedShadeHeader(
- viewModelFactory = viewModel.shadeHeaderViewModelFactory,
- createTintedIconManager = createTintedIconManager,
- createBatteryMeterViewController = createBatteryMeterViewController,
- statusBarIconController = statusBarIconController,
+ viewModel = headerViewModel,
modifier = Modifier.layoutId(SingleShadeMeasurePolicy.LayoutId.ShadeHeader),
)
@@ -413,10 +417,8 @@ private fun ContentScope.SingleShade(
private fun ContentScope.SplitShade(
notificationStackScrollView: NotificationScrollView,
viewModel: ShadeSceneContentViewModel,
+ headerViewModel: ShadeHeaderViewModel,
notificationsPlaceholderViewModel: NotificationsPlaceholderViewModel,
- createTintedIconManager: (ViewGroup, StatusBarLocation) -> TintedIconManager,
- createBatteryMeterViewController: (ViewGroup, StatusBarLocation) -> BatteryMeterViewController,
- statusBarIconController: StatusBarIconController,
mediaCarouselController: MediaCarouselController,
mediaHost: MediaHost,
modifier: Modifier = Modifier,
@@ -509,10 +511,7 @@ private fun ContentScope.SplitShade(
Column(modifier = Modifier.fillMaxSize()) {
CollapsedShadeHeader(
- viewModelFactory = viewModel.shadeHeaderViewModelFactory,
- createTintedIconManager = createTintedIconManager,
- createBatteryMeterViewController = createBatteryMeterViewController,
- statusBarIconController = statusBarIconController,
+ viewModel = headerViewModel,
modifier =
Modifier.then(brightnessMirrorShowingModifier)
.padding(horizontal = { unfoldTranslationXForStartSide.roundToInt() }),
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/VariableDayDate.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/VariableDayDate.kt
index 93eca86e15cf..64aada52626b 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/VariableDayDate.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/VariableDayDate.kt
@@ -5,17 +5,19 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.Layout
-import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.compose.theme.colorAttr
-import com.android.systemui.shade.ui.viewmodel.ShadeHeaderViewModel
+import com.android.systemui.shade.ui.viewmodel.ShadeHeaderViewModel.HeaderChipHighlight
@Composable
-fun VariableDayDate(viewModel: ShadeHeaderViewModel, modifier: Modifier = Modifier) {
- val longerText = viewModel.longerDateText.collectAsStateWithLifecycle()
- val shorterText = viewModel.shorterDateText.collectAsStateWithLifecycle()
-
+fun VariableDayDate(
+ longerDateText: String,
+ shorterDateText: String,
+ chipHighlight: HeaderChipHighlight,
+ modifier: Modifier = Modifier,
+) {
val textColor =
- if (viewModel.highlightNotificationIcons) colorAttr(android.R.attr.textColorPrimaryInverse)
+ if (chipHighlight is HeaderChipHighlight.Strong)
+ colorAttr(android.R.attr.textColorPrimaryInverse)
else colorAttr(android.R.attr.textColorPrimary)
Layout(
@@ -23,7 +25,7 @@ fun VariableDayDate(viewModel: ShadeHeaderViewModel, modifier: Modifier = Modifi
listOf(
{
Text(
- text = longerText.value,
+ text = longerDateText,
style = MaterialTheme.typography.bodyMedium,
color = textColor,
maxLines = 1,
@@ -31,7 +33,7 @@ fun VariableDayDate(viewModel: ShadeHeaderViewModel, modifier: Modifier = Modifi
},
{
Text(
- text = shorterText.value,
+ text = shorterDateText,
style = MaterialTheme.typography.bodyMedium,
color = textColor,
maxLines = 1,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/ui/viewmodel/BrightnessSliderViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/ui/viewmodel/BrightnessSliderViewModelTest.kt
index 2d093bf1630b..f9e88341316e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/ui/viewmodel/BrightnessSliderViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/ui/viewmodel/BrightnessSliderViewModelTest.kt
@@ -26,10 +26,8 @@ import com.android.systemui.brightness.domain.interactor.screenBrightnessInterac
import com.android.systemui.brightness.shared.model.GammaBrightness
import com.android.systemui.brightness.shared.model.LinearBrightness
import com.android.systemui.classifier.domain.interactor.falsingInteractor
-import com.android.systemui.common.shared.model.ContentDescription
-import com.android.systemui.common.shared.model.Icon
-import com.android.systemui.common.shared.model.Text
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.graphics.imageLoader
import com.android.systemui.haptics.slider.sliderHapticsViewModelFactory
import com.android.systemui.kosmos.testScope
import com.android.systemui.lifecycle.activateIn
@@ -65,6 +63,7 @@ class BrightnessSliderViewModelTest : SysuiTestCase() {
falsingInteractor,
supportsMirroring = true,
brightnessWarningToast,
+ imageLoader,
)
}
}
@@ -162,20 +161,21 @@ class BrightnessSliderViewModelTest : SysuiTestCase() {
}
@Test
- fun label() {
- assertThat(underTest.label)
- .isEqualTo(Text.Resource(R.string.quick_settings_brightness_dialog_title))
- }
-
- @Test
fun icon() {
- assertThat(underTest.icon)
- .isEqualTo(
- Icon.Resource(
- R.drawable.ic_brightness_full,
- ContentDescription.Resource(underTest.label.res),
- )
- )
+ assertThat(BrightnessSliderViewModel.getIconForPercentage(0f))
+ .isEqualTo(R.drawable.ic_brightness_low)
+ assertThat(BrightnessSliderViewModel.getIconForPercentage(20f))
+ .isEqualTo(R.drawable.ic_brightness_low)
+ assertThat(BrightnessSliderViewModel.getIconForPercentage(20.1f))
+ .isEqualTo(R.drawable.ic_brightness_medium)
+ assertThat(BrightnessSliderViewModel.getIconForPercentage(50f))
+ .isEqualTo(R.drawable.ic_brightness_medium)
+ assertThat(BrightnessSliderViewModel.getIconForPercentage(79.9f))
+ .isEqualTo(R.drawable.ic_brightness_medium)
+ assertThat(BrightnessSliderViewModel.getIconForPercentage(80f))
+ .isEqualTo(R.drawable.ic_brightness_full)
+ assertThat(BrightnessSliderViewModel.getIconForPercentage(100f))
+ .isEqualTo(R.drawable.ic_brightness_full)
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalSceneRepositoryImplTest.kt
index fd0bf4dae198..293d32471713 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalSceneRepositoryImplTest.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2025 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -21,34 +21,44 @@ import androidx.test.filters.SmallTest
import com.android.compose.animation.scene.ObservableTransitionState
import com.android.systemui.SysuiTestCase
import com.android.systemui.communal.shared.model.CommunalScenes
-import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
-import com.android.systemui.kosmos.testScope
-import com.android.systemui.scene.shared.model.sceneDataSource
+import com.android.systemui.kosmos.backgroundScope
+import com.android.systemui.kosmos.collectLastValue
+import com.android.systemui.kosmos.runTest
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
+import com.android.systemui.scene.shared.model.SceneDataSource
+import com.android.systemui.scene.shared.model.SceneDataSourceDelegator
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.flow.flowOf
-import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.verify
@SmallTest
@RunWith(AndroidJUnit4::class)
-class CommunalRepositoryImplTest : SysuiTestCase() {
+class CommunalSceneRepositoryImplTest : SysuiTestCase() {
+ private val kosmos = testKosmos().useUnconfinedTestDispatcher()
- private val kosmos = testKosmos()
- private val testScope = kosmos.testScope
- private val underTest by lazy {
- CommunalSceneRepositoryImpl(
- kosmos.applicationCoroutineScope,
- kosmos.applicationCoroutineScope,
- kosmos.sceneDataSource,
- )
- }
+ private val delegator = mock<SceneDataSourceDelegator> {}
+
+ private val Kosmos.underTest by
+ Kosmos.Fixture {
+ CommunalSceneRepositoryImpl(
+ applicationScope = applicationCoroutineScope,
+ backgroundScope = backgroundScope,
+ sceneDataSource = delegator,
+ delegator = delegator,
+ )
+ }
@Test
fun transitionState_idleByDefault() =
- testScope.runTest {
+ kosmos.runTest {
val transitionState by collectLastValue(underTest.transitionState)
assertThat(transitionState)
.isEqualTo(ObservableTransitionState.Idle(CommunalScenes.Default))
@@ -56,7 +66,7 @@ class CommunalRepositoryImplTest : SysuiTestCase() {
@Test
fun transitionState_setTransitionState_returnsNewValue() =
- testScope.runTest {
+ kosmos.runTest {
val expectedSceneKey = CommunalScenes.Communal
underTest.setTransitionState(flowOf(ObservableTransitionState.Idle(expectedSceneKey)))
@@ -66,7 +76,7 @@ class CommunalRepositoryImplTest : SysuiTestCase() {
@Test
fun transitionState_setNullTransitionState_returnsDefaultValue() =
- testScope.runTest {
+ kosmos.runTest {
// Set a value for the transition state flow.
underTest.setTransitionState(
flowOf(ObservableTransitionState.Idle(CommunalScenes.Communal))
@@ -80,4 +90,18 @@ class CommunalRepositoryImplTest : SysuiTestCase() {
assertThat(transitionState)
.isEqualTo(ObservableTransitionState.Idle(CommunalScenes.Default))
}
+
+ @Test
+ fun showHubFromPowerButton() =
+ kosmos.runTest {
+ fakeKeyguardRepository.setKeyguardShowing(false)
+
+ underTest.showHubFromPowerButton()
+
+ argumentCaptor<SceneDataSource>().apply {
+ verify(delegator).setDelegate(capture())
+
+ assertThat(firstValue.currentScene.value).isEqualTo(CommunalScenes.Communal)
+ }
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/complication/DreamClockTimeComplicationTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/complication/DreamClockTimeComplicationTest.java
index 22ab4994f026..92ccf1294554 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/complication/DreamClockTimeComplicationTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/complication/DreamClockTimeComplicationTest.java
@@ -23,7 +23,7 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.Context;
-import android.view.View;
+import android.widget.TextClock;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
@@ -41,6 +41,8 @@ import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.util.Locale;
+
@SmallTest
@RunWith(AndroidJUnit4.class)
public class DreamClockTimeComplicationTest extends SysuiTestCase {
@@ -68,7 +70,7 @@ public class DreamClockTimeComplicationTest extends SysuiTestCase {
private ComplicationViewModel mComplicationViewModel;
@Mock
- private View mView;
+ private TextClock mView;
@Mock
private ComplicationLayoutParams mLayoutParams;
@@ -86,6 +88,7 @@ public class DreamClockTimeComplicationTest extends SysuiTestCase {
MockitoAnnotations.initMocks(this);
when(mComponentFactory.create()).thenReturn(mComponent);
when(mComponent.getViewHolder()).thenReturn(mDreamClockTimeViewHolder);
+ when(mView.getTextLocale()).thenReturn(Locale.US);
mMonitor = SelfExecutingMonitor.createInstance();
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt
index 4e8a2a349283..49d324b27bb1 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt
@@ -79,6 +79,7 @@ import org.mockito.Mockito.clearInvocations
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.doAnswer
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.stub
@@ -473,6 +474,51 @@ class BiometricSettingsRepositoryTest : SysuiTestCase() {
}
@Test
+ @EnableFlags(com.android.settings.flags.Flags.FLAG_BIOMETRICS_ONBOARDING_EDUCATION)
+ fun registerEnabledOnKeyguardCallback_multipleUsers_shouldSendAllUpdates() =
+ testScope.runTest {
+
+ // Simulate call to register callback when in multiple users setup
+ biometricManager.stub {
+ on { registerEnabledOnKeyguardCallback(any()) } doAnswer
+ { invocation ->
+ val callback =
+ invocation.arguments[0] as IBiometricEnabledOnKeyguardCallback
+ callback.onChanged(true, PRIMARY_USER_ID, TYPE_FACE)
+ callback.onChanged(true, PRIMARY_USER_ID, TYPE_FINGERPRINT)
+ callback.onChanged(true, ANOTHER_USER_ID, TYPE_FACE)
+ callback.onChanged(true, ANOTHER_USER_ID, TYPE_FINGERPRINT)
+ }
+ }
+ authController.stub {
+ on { isFingerprintEnrolled(anyInt()) } doReturn true
+ on { isFaceAuthEnrolled(anyInt()) } doReturn true
+ }
+
+ // Check primary user status
+ createBiometricSettingsRepository()
+ var fingerprintAllowed = collectLastValue(underTest.isFingerprintEnrolledAndEnabled)
+ var faceAllowed = collectLastValue(underTest.isFaceAuthEnrolledAndEnabled)
+ runCurrent()
+
+ enrollmentChange(UNDER_DISPLAY_FINGERPRINT, PRIMARY_USER_ID, true)
+ enrollmentChange(FACE, PRIMARY_USER_ID, true)
+ assertThat(fingerprintAllowed()).isTrue()
+ assertThat(faceAllowed()).isTrue()
+
+ // Check secondary user status
+ userRepository.setSelectedUserInfo(ANOTHER_USER)
+ fingerprintAllowed = collectLastValue(underTest.isFingerprintEnrolledAndEnabled)
+ faceAllowed = collectLastValue(underTest.isFaceAuthEnrolledAndEnabled)
+ runCurrent()
+
+ enrollmentChange(UNDER_DISPLAY_FINGERPRINT, ANOTHER_USER_ID, true)
+ enrollmentChange(FACE, ANOTHER_USER_ID, true)
+ assertThat(fingerprintAllowed()).isTrue()
+ assertThat(faceAllowed()).isTrue()
+ }
+
+ @Test
fun devicePolicyControlsFaceAuthenticationEnabledState() =
testScope.runTest {
faceAuthIsEnrolled()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java
index 57ac90648f33..3078a943be32 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java
@@ -26,6 +26,7 @@ import static com.android.settingslib.media.MediaDevice.SelectionBehavior.SELECT
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
@@ -643,6 +644,132 @@ public class MediaOutputAdapterTest extends SysuiTestCase {
verify(mMediaSwitchingController).addDeviceToPlayMedia(mMediaDevice2);
}
+ @DisableFlags(Flags.FLAG_DISABLE_TRANSFER_WHEN_APPS_DO_NOT_SUPPORT)
+ @Test
+ public void clickFullItemOfSelectableDevice_flagOff_hasListingPreference_verifyConnectDevice() {
+ List<MediaDevice> mediaDevices = new ArrayList<>();
+ mediaDevices.add(mMediaDevice2);
+ when(mMediaDevice2.hasRouteListingPreferenceItem()).thenReturn(true);
+ when(mMediaSwitchingController.getSelectableMediaDevice()).thenReturn(mediaDevices);
+ when(mMediaSwitchingController.getTransferableMediaDevices()).thenReturn(List.of());
+ when(mMediaSwitchingController.isCurrentOutputDeviceHasSessionOngoing()).thenReturn(false);
+ mMediaOutputAdapter.onBindViewHolder(mViewHolder, 1);
+
+ assertThat(mViewHolder.mCheckBox.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(mViewHolder.mTitleText.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(mViewHolder.mTitleText.getText().toString()).isEqualTo(TEST_DEVICE_NAME_2);
+ assertThat(mViewHolder.mContainerLayout.isFocusable()).isTrue();
+
+ mViewHolder.mContainerLayout.performClick();
+
+ verify(mMediaSwitchingController).connectDevice(mMediaDevice2);
+ }
+
+ @EnableFlags(Flags.FLAG_DISABLE_TRANSFER_WHEN_APPS_DO_NOT_SUPPORT)
+ @Test
+ public void clickFullItemOfSelectableDevice_flagOn_hasListingPreference_verifyConnectDevice() {
+ List<MediaDevice> mediaDevices = new ArrayList<>();
+ mediaDevices.add(mMediaDevice2);
+ when(mMediaDevice2.hasRouteListingPreferenceItem()).thenReturn(true);
+ when(mMediaSwitchingController.getSelectableMediaDevice()).thenReturn(mediaDevices);
+ when(mMediaSwitchingController.getTransferableMediaDevices()).thenReturn(List.of());
+ when(mMediaSwitchingController.isCurrentOutputDeviceHasSessionOngoing()).thenReturn(false);
+ mMediaOutputAdapter.onBindViewHolder(mViewHolder, 1);
+
+ assertThat(mViewHolder.mCheckBox.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(mViewHolder.mTitleText.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(mViewHolder.mTitleText.getText().toString()).isEqualTo(TEST_DEVICE_NAME_2);
+ assertThat(mViewHolder.mContainerLayout.isFocusable()).isTrue();
+
+ mViewHolder.mContainerLayout.performClick();
+
+ verify(mMediaSwitchingController).connectDevice(mMediaDevice2);
+ }
+
+ @DisableFlags(Flags.FLAG_DISABLE_TRANSFER_WHEN_APPS_DO_NOT_SUPPORT)
+ @Test
+ public void clickFullItemOfSelectableDevice_flagOff_isTransferable_verifyConnectDevice() {
+ List<MediaDevice> mediaDevices = new ArrayList<>();
+ mediaDevices.add(mMediaDevice2);
+ when(mMediaDevice2.hasRouteListingPreferenceItem()).thenReturn(false);
+ when(mMediaSwitchingController.getSelectableMediaDevice()).thenReturn(mediaDevices);
+ when(mMediaSwitchingController.getTransferableMediaDevices()).thenReturn(mediaDevices);
+ when(mMediaSwitchingController.isCurrentOutputDeviceHasSessionOngoing()).thenReturn(false);
+ mMediaOutputAdapter.onBindViewHolder(mViewHolder, 1);
+
+ assertThat(mViewHolder.mCheckBox.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(mViewHolder.mTitleText.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(mViewHolder.mTitleText.getText().toString()).isEqualTo(TEST_DEVICE_NAME_2);
+ assertThat(mViewHolder.mContainerLayout.isFocusable()).isTrue();
+
+ mViewHolder.mContainerLayout.performClick();
+
+ verify(mMediaSwitchingController).connectDevice(mMediaDevice2);
+ }
+
+ @EnableFlags(Flags.FLAG_DISABLE_TRANSFER_WHEN_APPS_DO_NOT_SUPPORT)
+ @Test
+ public void clickFullItemOfSelectableDevice_flagOn_isTransferable_verifyConnectDevice() {
+ List<MediaDevice> mediaDevices = new ArrayList<>();
+ mediaDevices.add(mMediaDevice2);
+ when(mMediaDevice2.hasRouteListingPreferenceItem()).thenReturn(false);
+ when(mMediaSwitchingController.getSelectableMediaDevice()).thenReturn(mediaDevices);
+ when(mMediaSwitchingController.getTransferableMediaDevices()).thenReturn(mediaDevices);
+ when(mMediaSwitchingController.isCurrentOutputDeviceHasSessionOngoing()).thenReturn(false);
+ mMediaOutputAdapter.onBindViewHolder(mViewHolder, 1);
+
+ assertThat(mViewHolder.mCheckBox.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(mViewHolder.mTitleText.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(mViewHolder.mTitleText.getText().toString()).isEqualTo(TEST_DEVICE_NAME_2);
+ assertThat(mViewHolder.mContainerLayout.isFocusable()).isTrue();
+
+ mViewHolder.mContainerLayout.performClick();
+
+ verify(mMediaSwitchingController).connectDevice(mMediaDevice2);
+ }
+
+ @DisableFlags(Flags.FLAG_DISABLE_TRANSFER_WHEN_APPS_DO_NOT_SUPPORT)
+ @Test
+ public void clickFullItemOfSelectableDevice_flagOff_notTransferable_verifyConnectDevice() {
+ List<MediaDevice> mediaDevices = new ArrayList<>();
+ mediaDevices.add(mMediaDevice2);
+ when(mMediaDevice2.hasRouteListingPreferenceItem()).thenReturn(false);
+ when(mMediaSwitchingController.getSelectableMediaDevice()).thenReturn(mediaDevices);
+ when(mMediaSwitchingController.getTransferableMediaDevices()).thenReturn(List.of());
+ when(mMediaSwitchingController.isCurrentOutputDeviceHasSessionOngoing()).thenReturn(false);
+ mMediaOutputAdapter.onBindViewHolder(mViewHolder, 1);
+
+ assertThat(mViewHolder.mCheckBox.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(mViewHolder.mTitleText.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(mViewHolder.mTitleText.getText().toString()).isEqualTo(TEST_DEVICE_NAME_2);
+ assertThat(mViewHolder.mContainerLayout.isFocusable()).isTrue();
+
+ mViewHolder.mContainerLayout.performClick();
+
+ verify(mMediaSwitchingController).connectDevice(mMediaDevice2);
+ }
+
+ @EnableFlags(Flags.FLAG_DISABLE_TRANSFER_WHEN_APPS_DO_NOT_SUPPORT)
+ @Test
+ public void clickFullItemOfSelectableDevice_flagOn_notTransferable_verifyNotConnectDevice() {
+ List<MediaDevice> mediaDevices = new ArrayList<>();
+ mediaDevices.add(mMediaDevice2);
+ when(mMediaDevice2.hasRouteListingPreferenceItem()).thenReturn(false);
+ when(mMediaSwitchingController.getSelectableMediaDevice()).thenReturn(mediaDevices);
+ when(mMediaSwitchingController.getTransferableMediaDevices()).thenReturn(List.of());
+ when(mMediaSwitchingController.isCurrentOutputDeviceHasSessionOngoing()).thenReturn(false);
+ mMediaOutputAdapter.onBindViewHolder(mViewHolder, 1);
+
+ assertThat(mViewHolder.mCheckBox.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(mViewHolder.mTitleText.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(mViewHolder.mTitleText.getText().toString()).isEqualTo(TEST_DEVICE_NAME_2);
+ assertThat(mViewHolder.mContainerLayout.isFocusable()).isTrue();
+
+ mViewHolder.mContainerLayout.performClick();
+
+ verify(mMediaSwitchingController, never()).connectDevice(any(MediaDevice.class));
+ }
+
@Test
public void onGroupActionTriggered_clickSelectedRemoteDevice_triggerUngrouping() {
when(mMediaSwitchingController.getSelectableMediaDevice())
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayContentViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayContentViewModelTest.kt
index 675960832edc..43db50ad675f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayContentViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayContentViewModelTest.kt
@@ -124,35 +124,35 @@ class NotificationsShadeOverlayContentViewModelTest : SysuiTestCase() {
}
@Test
- fun showHeader_showsOnNarrowScreen() =
+ fun showClock_showsOnNarrowScreen() =
testScope.runTest {
kosmos.shadeRepository.setShadeLayoutWide(false)
// Shown when notifications are present.
kosmos.activeNotificationListRepository.setActiveNotifs(1)
runCurrent()
- assertThat(underTest.showHeader).isTrue()
+ assertThat(underTest.showClock).isTrue()
// Hidden when notifications are not present.
kosmos.activeNotificationListRepository.setActiveNotifs(0)
runCurrent()
- assertThat(underTest.showHeader).isFalse()
+ assertThat(underTest.showClock).isFalse()
}
@Test
- fun showHeader_hidesOnWideScreen() =
+ fun showClock_hidesOnWideScreen() =
testScope.runTest {
kosmos.shadeRepository.setShadeLayoutWide(true)
// Hidden when notifications are present.
kosmos.activeNotificationListRepository.setActiveNotifs(1)
runCurrent()
- assertThat(underTest.showHeader).isFalse()
+ assertThat(underTest.showClock).isFalse()
// Hidden when notifications are not present.
kosmos.activeNotificationListRepository.setActiveNotifs(0)
runCurrent()
- assertThat(underTest.showHeader).isFalse()
+ assertThat(underTest.showClock).isFalse()
}
private fun TestScope.lockDevice() {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsContainerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsContainerViewModelTest.kt
new file mode 100644
index 000000000000..6e26fa119888
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsContainerViewModelTest.kt
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.ui.viewmodel
+
+import android.testing.TestableLooper
+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.flags.EnableSceneContainer
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.lifecycle.activateIn
+import com.android.systemui.qs.composefragment.dagger.usingMediaInComposeFragment
+import com.android.systemui.scene.domain.startable.sceneContainerStartable
+import com.android.systemui.shade.domain.interactor.enableDualShade
+import com.android.systemui.shade.domain.interactor.shadeModeInteractor
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@TestableLooper.RunWithLooper
+@EnableSceneContainer
+class QuickSettingsContainerViewModelTest : SysuiTestCase() {
+
+ private val kosmos =
+ testKosmos().apply {
+ usingMediaInComposeFragment = false // This is not for the compose fragment
+ }
+ private val testScope = kosmos.testScope
+
+ private val shadeModeInteractor = kosmos.shadeModeInteractor
+
+ private val underTest by lazy {
+ kosmos.quickSettingsContainerViewModelFactory.create(supportsBrightnessMirroring = false)
+ }
+
+ @Before
+ fun setUp() {
+ kosmos.sceneContainerStartable.start()
+ kosmos.enableDualShade()
+ underTest.activateIn(testScope)
+ }
+
+ @Test
+ fun showHeader_showsOnNarrowScreen() =
+ testScope.runTest {
+ kosmos.enableDualShade(wideLayout = false)
+ val isShadeLayoutWide by collectLastValue(shadeModeInteractor.isShadeLayoutWide)
+ assertThat(isShadeLayoutWide).isFalse()
+
+ assertThat(underTest.showHeader).isTrue()
+ }
+
+ @Test
+ fun showHeader_hidesOnWideScreen() =
+ testScope.runTest {
+ kosmos.enableDualShade(wideLayout = true)
+ val isShadeLayoutWide by collectLastValue(shadeModeInteractor.isShadeLayoutWide)
+ assertThat(isShadeLayoutWide).isTrue()
+
+ assertThat(underTest.showHeader).isFalse()
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModelTest.kt
index 01714d7a4b87..b532554f5dfd 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModelTest.kt
@@ -127,24 +127,6 @@ class QuickSettingsShadeOverlayContentViewModelTest : SysuiTestCase() {
}
@Test
- fun showHeader_showsOnNarrowScreen() =
- testScope.runTest {
- kosmos.enableDualShade(wideLayout = false)
- runCurrent()
-
- assertThat(underTest.showHeader).isTrue()
- }
-
- @Test
- fun showHeader_hidesOnWideScreen() =
- testScope.runTest {
- kosmos.enableDualShade(wideLayout = true)
- runCurrent()
-
- assertThat(underTest.showHeader).isFalse()
- }
-
- @Test
fun onPanelShapeChanged() =
testScope.runTest {
var actual: ShadeScrimShape? = null
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/recordissue/IssueRecordingServiceSessionTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/recordissue/IssueRecordingServiceSessionTest.kt
index f2e658dc3759..7bcaeabfee69 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/recordissue/IssueRecordingServiceSessionTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/recordissue/IssueRecordingServiceSessionTest.kt
@@ -38,6 +38,7 @@ import com.android.systemui.util.settings.fakeGlobalSettings
import com.android.traceur.TraceConfig
import com.google.common.truth.Truth
import org.junit.Before
+import org.junit.Ignore
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.anyInt
@@ -126,6 +127,7 @@ class IssueRecordingServiceSessionTest : SysuiTestCase() {
verify(iActivityManager).requestBugReportWithExtraAttachments(any())
}
+ @Ignore("b/392753499")
@Test
fun sharesTracesDirectly_afterReceivingShareCommand_withTakeBugreportFalse() {
underTest.takeBugReport = false
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 62c360400582..85e59364d6b6 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
@@ -375,7 +375,6 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase {
SystemClock systemClock = new FakeSystemClock();
mStatusBarStateController = new StatusBarStateControllerImpl(
mUiEventLogger,
- () -> mKosmos.getInteractionJankMonitor(),
mJavaAdapter,
() -> mKeyguardInteractor,
() -> mKeyguardTransitionInteractor,
@@ -456,7 +455,6 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase {
mock(HeadsUpManager.class),
new StatusBarStateControllerImpl(
new UiEventLoggerFake(),
- () -> mKosmos.getInteractionJankMonitor(),
mJavaAdapter,
() -> mKeyguardInteractor,
() -> mKeyguardTransitionInteractor,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt
index 8ce20d2a05e9..d08c8a7c5974 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt
@@ -23,6 +23,9 @@ import com.android.systemui.scene.shared.model.Overlays
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.shade.domain.interactor.disableDualShade
import com.android.systemui.shade.domain.interactor.enableDualShade
+import com.android.systemui.shade.domain.interactor.enableSingleShade
+import com.android.systemui.shade.domain.interactor.enableSplitShade
+import com.android.systemui.shade.ui.viewmodel.ShadeHeaderViewModel.HeaderChipHighlight
import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.fakeMobileIconsInteractor
import com.android.systemui.testKosmos
@@ -273,6 +276,116 @@ class ShadeHeaderViewModelTest : SysuiTestCase() {
assertThat(currentOverlays).doesNotContain(Overlays.QuickSettingsShade)
}
+ @Test
+ fun highlightChips_notifsOpenInSingleShade_bothNone() =
+ testScope.runTest {
+ kosmos.enableSingleShade()
+ val currentScene by collectLastValue(sceneInteractor.currentScene)
+ val currentOverlays by collectLastValue(sceneInteractor.currentOverlays)
+ setScene(Scenes.Shade)
+ assertThat(currentScene).isEqualTo(Scenes.Shade)
+ assertThat(currentOverlays).isEmpty()
+
+ assertThat(underTest.notificationsChipHighlight).isEqualTo(HeaderChipHighlight.None)
+ assertThat(underTest.quickSettingsChipHighlight).isEqualTo(HeaderChipHighlight.None)
+ }
+
+ @Test
+ fun highlightChips_notifsOpenInSplitShade_bothNone() =
+ testScope.runTest {
+ kosmos.enableSplitShade()
+ val currentScene by collectLastValue(sceneInteractor.currentScene)
+ val currentOverlays by collectLastValue(sceneInteractor.currentOverlays)
+ setScene(Scenes.Shade)
+ assertThat(currentScene).isEqualTo(Scenes.Shade)
+ assertThat(currentOverlays).isEmpty()
+
+ assertThat(underTest.notificationsChipHighlight).isEqualTo(HeaderChipHighlight.None)
+ assertThat(underTest.quickSettingsChipHighlight).isEqualTo(HeaderChipHighlight.None)
+ }
+
+ @Test
+ fun highlightChips_quickSettingsOpenInSingleShade_bothNone() =
+ testScope.runTest {
+ kosmos.enableSingleShade()
+ val currentScene by collectLastValue(sceneInteractor.currentScene)
+ val currentOverlays by collectLastValue(sceneInteractor.currentOverlays)
+ setScene(Scenes.QuickSettings)
+ assertThat(currentScene).isEqualTo(Scenes.QuickSettings)
+ assertThat(currentOverlays).isEmpty()
+
+ assertThat(underTest.notificationsChipHighlight).isEqualTo(HeaderChipHighlight.None)
+ assertThat(underTest.quickSettingsChipHighlight).isEqualTo(HeaderChipHighlight.None)
+ }
+
+ @Test
+ fun highlightChips_notifsOpenInDualShade_notifsStrongQuickSettingsWeak() =
+ testScope.runTest {
+ kosmos.enableDualShade()
+ val currentScene by collectLastValue(sceneInteractor.currentScene)
+ val currentOverlays by collectLastValue(sceneInteractor.currentOverlays)
+
+ // Test the lockscreen scenario.
+ setScene(Scenes.Lockscreen)
+ setOverlay(Overlays.NotificationsShade)
+ assertThat(underTest.notificationsChipHighlight).isEqualTo(HeaderChipHighlight.Strong)
+ assertThat(underTest.quickSettingsChipHighlight).isEqualTo(HeaderChipHighlight.Weak)
+
+ // Test the unlocked scenario.
+ setDeviceEntered(true)
+ setScene(Scenes.Gone)
+ setOverlay(Overlays.NotificationsShade)
+ assertThat(currentScene).isEqualTo(Scenes.Gone)
+ assertThat(currentOverlays).isNotEmpty()
+ assertThat(underTest.notificationsChipHighlight).isEqualTo(HeaderChipHighlight.Strong)
+ assertThat(underTest.quickSettingsChipHighlight).isEqualTo(HeaderChipHighlight.Weak)
+ }
+
+ @Test
+ fun highlightChips_quickSettingsOpenInDualShade_notifsWeakQuickSettingsStrong() =
+ testScope.runTest {
+ kosmos.enableDualShade()
+ val currentScene by collectLastValue(sceneInteractor.currentScene)
+ val currentOverlays by collectLastValue(sceneInteractor.currentOverlays)
+
+ // Test the lockscreen scenario.
+ setScene(Scenes.Lockscreen)
+ setOverlay(Overlays.QuickSettingsShade)
+ assertThat(underTest.notificationsChipHighlight).isEqualTo(HeaderChipHighlight.Weak)
+ assertThat(underTest.quickSettingsChipHighlight).isEqualTo(HeaderChipHighlight.Strong)
+
+ // Test the unlocked scenario.
+ setDeviceEntered(true)
+ setScene(Scenes.Gone)
+ setOverlay(Overlays.QuickSettingsShade)
+ assertThat(currentScene).isEqualTo(Scenes.Gone)
+ assertThat(currentOverlays).isNotEmpty()
+ assertThat(underTest.notificationsChipHighlight).isEqualTo(HeaderChipHighlight.Weak)
+ assertThat(underTest.quickSettingsChipHighlight).isEqualTo(HeaderChipHighlight.Strong)
+ }
+
+ @Test
+ fun highlightChips_noOverlaysInDualShade_bothNone() =
+ testScope.runTest {
+ kosmos.enableDualShade()
+ val currentScene by collectLastValue(sceneInteractor.currentScene)
+ val currentOverlays by collectLastValue(sceneInteractor.currentOverlays)
+
+ // Test the lockscreen scenario.
+ setScene(Scenes.Lockscreen)
+ assertThat(currentOverlays).isEmpty()
+ assertThat(underTest.notificationsChipHighlight).isEqualTo(HeaderChipHighlight.None)
+ assertThat(underTest.quickSettingsChipHighlight).isEqualTo(HeaderChipHighlight.None)
+
+ // Test the unlocked scenario.
+ setDeviceEntered(true)
+ setScene(Scenes.Gone)
+ assertThat(currentScene).isEqualTo(Scenes.Gone)
+ assertThat(currentOverlays).isEmpty()
+ assertThat(underTest.notificationsChipHighlight).isEqualTo(HeaderChipHighlight.None)
+ assertThat(underTest.quickSettingsChipHighlight).isEqualTo(HeaderChipHighlight.None)
+ }
+
companion object {
private val SUB_1 =
SubscriptionModel(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
index a51e0c0add37..a458ab6e713c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
@@ -33,7 +33,6 @@ import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteract
import com.android.systemui.flags.DisableSceneContainer
import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.flags.parameterizeSceneContainerFlag
-import com.android.systemui.jank.interactionJankMonitor
import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.domain.interactor.keyguardClockInteractor
@@ -113,7 +112,6 @@ class StatusBarStateControllerImplTest(flags: FlagsParameterization) : SysuiTest
object :
StatusBarStateControllerImpl(
uiEventLogger,
- { kosmos.interactionJankMonitor },
JavaAdapter(testScope.backgroundScope),
{ kosmos.keyguardInteractor },
{ kosmos.keyguardTransitionInteractor },
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndCastScreenToOtherDeviceDialogDelegateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndCastScreenToOtherDeviceDialogDelegateTest.kt
index b297bed61ecb..fc13cdaae8ce 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndCastScreenToOtherDeviceDialogDelegateTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndCastScreenToOtherDeviceDialogDelegateTest.kt
@@ -16,8 +16,6 @@
package com.android.systemui.statusbar.chips.casttootherdevice.ui.view
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import org.junit.runner.RunWith
import android.content.ComponentName
import android.content.DialogInterface
import android.content.Intent
@@ -25,6 +23,11 @@ import android.content.applicationContext
import android.content.packageManager
import android.content.pm.ApplicationInfo
import android.content.pm.PackageManager
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
+import android.view.View
+import android.view.Window
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.kosmos.Kosmos
@@ -43,10 +46,12 @@ import kotlin.test.Test
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
+import org.junit.runner.RunWith
import org.mockito.kotlin.any
import org.mockito.kotlin.argumentCaptor
import org.mockito.kotlin.eq
import org.mockito.kotlin.mock
+import org.mockito.kotlin.never
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
@@ -250,6 +255,36 @@ class EndCastScreenToOtherDeviceDialogDelegateTest : SysuiTestCase() {
assertThat(kosmos.fakeMediaProjectionRepository.stopProjectingInvoked).isTrue()
}
+ @Test
+ @EnableFlags(com.android.media.projection.flags.Flags.FLAG_SHOW_STOP_DIALOG_POST_CALL_END)
+ fun accessibilityDataSensitive_flagEnabled_appliesSetting() {
+ createAndSetDelegate(ENTIRE_SCREEN)
+
+ val window = mock<Window>()
+ val decorView = mock<View>()
+ whenever(sysuiDialog.window).thenReturn(window)
+ whenever(window.decorView).thenReturn(decorView)
+
+ underTest.beforeCreate(sysuiDialog, /* savedInstanceState= */ null)
+
+ verify(decorView).setAccessibilityDataSensitive(View.ACCESSIBILITY_DATA_SENSITIVE_YES)
+ }
+
+ @Test
+ @DisableFlags(com.android.media.projection.flags.Flags.FLAG_SHOW_STOP_DIALOG_POST_CALL_END)
+ fun accessibilityDataSensitive_flagDisabled_doesNotApplySetting() {
+ createAndSetDelegate(ENTIRE_SCREEN)
+
+ val window = mock<Window>()
+ val decorView = mock<View>()
+ whenever(sysuiDialog.window).thenReturn(window)
+ whenever(window.decorView).thenReturn(decorView)
+
+ underTest.beforeCreate(sysuiDialog, /* savedInstanceState= */ null)
+
+ verify(decorView, never()).setAccessibilityDataSensitive(any())
+ }
+
private fun createAndSetDelegate(state: MediaProjectionState.Projecting) {
underTest =
EndCastScreenToOtherDeviceDialogDelegate(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndGenericCastToOtherDeviceDialogDelegateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndGenericCastToOtherDeviceDialogDelegateTest.kt
index 9e8f22e331ed..ddac98dde45d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndGenericCastToOtherDeviceDialogDelegateTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndGenericCastToOtherDeviceDialogDelegateTest.kt
@@ -18,6 +18,10 @@ package com.android.systemui.statusbar.chips.casttootherdevice.ui.view
import android.content.DialogInterface
import android.content.applicationContext
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
+import android.view.View
+import android.view.Window
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -37,10 +41,13 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.runner.RunWith
+import org.mockito.kotlin.any
import org.mockito.kotlin.argumentCaptor
import org.mockito.kotlin.eq
import org.mockito.kotlin.mock
+import org.mockito.kotlin.never
import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
@SmallTest
@RunWith(AndroidJUnit4::class)
@@ -132,7 +139,7 @@ class EndGenericCastToOtherDeviceDialogDelegateTest : SysuiTestCase() {
verify(sysuiDialog)
.setPositiveButton(
eq(R.string.cast_to_other_device_stop_dialog_button),
- clickListener.capture()
+ clickListener.capture(),
)
// Verify that clicking the button stops the recording
@@ -144,6 +151,36 @@ class EndGenericCastToOtherDeviceDialogDelegateTest : SysuiTestCase() {
assertThat(kosmos.fakeMediaRouterRepository.lastStoppedDevice).isEqualTo(device)
}
+ @Test
+ @EnableFlags(com.android.media.projection.flags.Flags.FLAG_SHOW_STOP_DIALOG_POST_CALL_END)
+ fun accessibilityDataSensitive_flagEnabled_appliesSetting() {
+ createAndSetDelegate()
+
+ val window = mock<Window>()
+ val decorView = mock<View>()
+ whenever(sysuiDialog.window).thenReturn(window)
+ whenever(window.decorView).thenReturn(decorView)
+
+ underTest.beforeCreate(sysuiDialog, /* savedInstanceState= */ null)
+
+ verify(decorView).setAccessibilityDataSensitive(View.ACCESSIBILITY_DATA_SENSITIVE_YES)
+ }
+
+ @Test
+ @DisableFlags(com.android.media.projection.flags.Flags.FLAG_SHOW_STOP_DIALOG_POST_CALL_END)
+ fun accessibilityDataSensitive_flagDisabled_doesNotApplySetting() {
+ createAndSetDelegate()
+
+ val window = mock<Window>()
+ val decorView = mock<View>()
+ whenever(sysuiDialog.window).thenReturn(window)
+ whenever(window.decorView).thenReturn(decorView)
+
+ underTest.beforeCreate(sysuiDialog, /* savedInstanceState= */ null)
+
+ verify(decorView, never()).setAccessibilityDataSensitive(any())
+ }
+
private fun createAndSetDelegate(deviceName: String? = null) {
underTest =
EndGenericCastToOtherDeviceDialogDelegate(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/screenrecord/ui/view/EndScreenRecordingDialogDelegateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/screenrecord/ui/view/EndScreenRecordingDialogDelegateTest.kt
index 709e0b57c02a..1f91babbfa47 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/screenrecord/ui/view/EndScreenRecordingDialogDelegateTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/screenrecord/ui/view/EndScreenRecordingDialogDelegateTest.kt
@@ -23,6 +23,10 @@ import android.content.Intent
import android.content.applicationContext
import android.content.packageManager
import android.content.pm.ApplicationInfo
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
+import android.view.View
+import android.view.Window
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -45,6 +49,7 @@ import org.mockito.kotlin.any
import org.mockito.kotlin.argumentCaptor
import org.mockito.kotlin.eq
import org.mockito.kotlin.mock
+import org.mockito.kotlin.never
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
@@ -130,7 +135,7 @@ class EndScreenRecordingDialogDelegateTest : SysuiTestCase() {
verify(sysuiDialog)
.setPositiveButton(
eq(R.string.screenrecord_stop_dialog_button),
- clickListener.capture()
+ clickListener.capture(),
)
// Verify that clicking the button stops the recording
@@ -142,6 +147,36 @@ class EndScreenRecordingDialogDelegateTest : SysuiTestCase() {
assertThat(kosmos.screenRecordRepository.stopRecordingInvoked).isTrue()
}
+ @Test
+ @EnableFlags(com.android.media.projection.flags.Flags.FLAG_SHOW_STOP_DIALOG_POST_CALL_END)
+ fun accessibilityDataSensitive_flagEnabled_appliesSetting() {
+ createAndSetDelegate(recordedTask = null)
+
+ val window = mock<Window>()
+ val decorView = mock<View>()
+ whenever(sysuiDialog.window).thenReturn(window)
+ whenever(window.decorView).thenReturn(decorView)
+
+ underTest.beforeCreate(sysuiDialog, /* savedInstanceState= */ null)
+
+ verify(decorView).setAccessibilityDataSensitive(View.ACCESSIBILITY_DATA_SENSITIVE_YES)
+ }
+
+ @Test
+ @DisableFlags(com.android.media.projection.flags.Flags.FLAG_SHOW_STOP_DIALOG_POST_CALL_END)
+ fun accessibilityDataSensitive_flagDisabled_doesNotApplySetting() {
+ createAndSetDelegate(recordedTask = null)
+
+ val window = mock<Window>()
+ val decorView = mock<View>()
+ whenever(sysuiDialog.window).thenReturn(window)
+ whenever(window.decorView).thenReturn(decorView)
+
+ underTest.beforeCreate(sysuiDialog, /* savedInstanceState= */ null)
+
+ verify(decorView, never()).setAccessibilityDataSensitive(any())
+ }
+
private fun createAndSetDelegate(recordedTask: ActivityManager.RunningTaskInfo?) {
underTest =
EndScreenRecordingDialogDelegate(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/view/EndGenericShareToAppDialogDelegateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/view/EndGenericShareToAppDialogDelegateTest.kt
index 411d306f163c..259d3872cfa2 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/view/EndGenericShareToAppDialogDelegateTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/view/EndGenericShareToAppDialogDelegateTest.kt
@@ -18,6 +18,11 @@ package com.android.systemui.statusbar.chips.sharetoapp.ui.view
import android.content.DialogInterface
import android.content.applicationContext
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
+import android.view.View
+import android.view.Window
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.kosmos.testScope
@@ -31,26 +36,26 @@ import kotlin.test.Test
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
+import org.junit.runner.RunWith
import org.mockito.kotlin.any
import org.mockito.kotlin.argumentCaptor
import org.mockito.kotlin.mock
+import org.mockito.kotlin.never
import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
@SmallTest
@OptIn(ExperimentalCoroutinesApi::class)
+@RunWith(AndroidJUnit4::class)
class EndGenericShareToAppDialogDelegateTest : SysuiTestCase() {
private val kosmos = testKosmos()
private val sysuiDialog = mock<SystemUIDialog>()
- private val underTest =
- EndGenericShareToAppDialogDelegate(
- kosmos.endMediaProjectionDialogHelper,
- kosmos.applicationContext,
- stopAction = kosmos.mediaProjectionChipInteractor::stopProjecting,
- )
+ private lateinit var underTest: EndGenericShareToAppDialogDelegate
@Test
fun positiveButton_clickStopsRecording() =
kosmos.testScope.runTest {
+ createAndSetDelegate()
underTest.beforeCreate(sysuiDialog, /* savedInstanceState= */ null)
assertThat(kosmos.fakeMediaProjectionRepository.stopProjectingInvoked).isFalse()
@@ -62,4 +67,43 @@ class EndGenericShareToAppDialogDelegateTest : SysuiTestCase() {
assertThat(kosmos.fakeMediaProjectionRepository.stopProjectingInvoked).isTrue()
}
+
+ @Test
+ @EnableFlags(com.android.media.projection.flags.Flags.FLAG_SHOW_STOP_DIALOG_POST_CALL_END)
+ fun accessibilityDataSensitive_flagEnabled_appliesSetting() {
+ createAndSetDelegate()
+
+ val window = mock<Window>()
+ val decorView = mock<View>()
+ whenever(sysuiDialog.window).thenReturn(window)
+ whenever(window.decorView).thenReturn(decorView)
+
+ underTest.beforeCreate(sysuiDialog, /* savedInstanceState= */ null)
+
+ verify(decorView).setAccessibilityDataSensitive(View.ACCESSIBILITY_DATA_SENSITIVE_YES)
+ }
+
+ @Test
+ @DisableFlags(com.android.media.projection.flags.Flags.FLAG_SHOW_STOP_DIALOG_POST_CALL_END)
+ fun accessibilityDataSensitive_flagDisabled_doesNotApplySetting() {
+ createAndSetDelegate()
+
+ val window = mock<Window>()
+ val decorView = mock<View>()
+ whenever(sysuiDialog.window).thenReturn(window)
+ whenever(window.decorView).thenReturn(decorView)
+
+ underTest.beforeCreate(sysuiDialog, /* savedInstanceState= */ null)
+
+ verify(decorView, never()).setAccessibilityDataSensitive(any())
+ }
+
+ private fun createAndSetDelegate() {
+ underTest =
+ EndGenericShareToAppDialogDelegate(
+ kosmos.endMediaProjectionDialogHelper,
+ kosmos.applicationContext,
+ stopAction = kosmos.mediaProjectionChipInteractor::stopProjecting,
+ )
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/view/EndShareScreenToAppDialogDelegateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/view/EndShareScreenToAppDialogDelegateTest.kt
index 6885a6bd7229..0ae0d178185e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/view/EndShareScreenToAppDialogDelegateTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/view/EndShareScreenToAppDialogDelegateTest.kt
@@ -23,6 +23,11 @@ import android.content.applicationContext
import android.content.packageManager
import android.content.pm.ApplicationInfo
import android.content.pm.PackageManager
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
+import android.view.View
+import android.view.Window
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.kosmos.Kosmos
@@ -41,15 +46,18 @@ import kotlin.test.Test
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
+import org.junit.runner.RunWith
import org.mockito.kotlin.any
import org.mockito.kotlin.argumentCaptor
import org.mockito.kotlin.eq
import org.mockito.kotlin.mock
+import org.mockito.kotlin.never
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
@SmallTest
@OptIn(ExperimentalCoroutinesApi::class)
+@RunWith(AndroidJUnit4::class)
class EndShareScreenToAppDialogDelegateTest : SysuiTestCase() {
private val kosmos = Kosmos().also { it.testCase = this }
private val sysuiDialog = mock<SystemUIDialog>()
@@ -193,6 +201,40 @@ class EndShareScreenToAppDialogDelegateTest : SysuiTestCase() {
assertThat(kosmos.fakeMediaProjectionRepository.stopProjectingInvoked).isTrue()
}
+ @Test
+ @EnableFlags(com.android.media.projection.flags.Flags.FLAG_SHOW_STOP_DIALOG_POST_CALL_END)
+ fun accessibilityDataSensitive_flagEnabled_appliesSetting() {
+ createAndSetDelegate(ENTIRE_SCREEN)
+ whenever(kosmos.packageManager.getApplicationInfo(eq(HOST_PACKAGE), any<Int>()))
+ .thenThrow(PackageManager.NameNotFoundException())
+
+ val window = mock<Window>()
+ val decorView = mock<View>()
+ whenever(sysuiDialog.window).thenReturn(window)
+ whenever(window.decorView).thenReturn(decorView)
+
+ underTest.beforeCreate(sysuiDialog, /* savedInstanceState= */ null)
+
+ verify(decorView).setAccessibilityDataSensitive(View.ACCESSIBILITY_DATA_SENSITIVE_YES)
+ }
+
+ @Test
+ @DisableFlags(com.android.media.projection.flags.Flags.FLAG_SHOW_STOP_DIALOG_POST_CALL_END)
+ fun accessibilityDataSensitive_flagDisabled_doesNotApplySetting() {
+ createAndSetDelegate(ENTIRE_SCREEN)
+ whenever(kosmos.packageManager.getApplicationInfo(eq(HOST_PACKAGE), any<Int>()))
+ .thenThrow(PackageManager.NameNotFoundException())
+
+ val window = mock<Window>()
+ val decorView = mock<View>()
+ whenever(sysuiDialog.window).thenReturn(window)
+ whenever(window.decorView).thenReturn(decorView)
+
+ underTest.beforeCreate(sysuiDialog, /* savedInstanceState= */ null)
+
+ verify(decorView, never()).setAccessibilityDataSensitive(any())
+ }
+
private fun createAndSetDelegate(state: MediaProjectionState.Projecting) {
underTest =
EndShareScreenToAppDialogDelegate(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModelTest.kt
index 5a66888e4da4..3961e177d1aa 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModelTest.kt
@@ -297,6 +297,33 @@ class ShareToAppChipViewModelTest : SysuiTestCase() {
}
@Test
+ @EnableFlags(com.android.media.projection.flags.Flags.FLAG_SHOW_STOP_DIALOG_POST_CALL_END)
+ fun stopDialog_flagEnabled_eventEmitted_dialogCannotBeDismissedByTouchOutside() =
+ kosmos.runTest {
+ val latestDialogModel by collectLastValue(underTest.stopDialogToShow)
+
+ mediaProjectionRepo.mediaProjectionState.value =
+ MediaProjectionState.Projecting.EntireScreen(NORMAL_PACKAGE)
+
+ fakeMediaProjectionRepository.emitProjectionStartedDuringCallAndActivePostCallEvent()
+
+ // Verify that the dialog is shown
+ assertThat(latestDialogModel)
+ .isInstanceOf(MediaProjectionStopDialogModel.Shown::class.java)
+
+ val dialogModel = latestDialogModel as MediaProjectionStopDialogModel.Shown
+
+ whenever(dialogModel.dialogDelegate.createDialog()).thenReturn(mockDialog)
+
+ dialogModel.createAndShowDialog()
+
+ verify(mockDialog).show()
+
+ // Verify that setCanceledOnTouchOutside(false) is called
+ verify(mockDialog).setCanceledOnTouchOutside(false)
+ }
+
+ @Test
fun chip_notProjectingState_isHidden() =
testScope.runTest {
val latest by collectLastValue(underTest.chip)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImplTest.kt
index 9dfc922eb7d0..339f8fac3820 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImplTest.kt
@@ -46,6 +46,7 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
import com.android.systemui.statusbar.notification.collection.provider.visualStabilityProvider
import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager
+import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUi
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.android.systemui.statusbar.notification.row.NotificationTestHelper
import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun
@@ -678,25 +679,32 @@ class HeadsUpManagerImplTest(flags: FlagsParameterization) : SysuiTestCase() {
}
@Test
- @DisableFlags(StatusBarNotifChips.FLAG_NAME)
- fun testIsSticky_promotedAndExpanded_notifChipsFlagOff_true() {
- val notif = Notification.Builder(mContext, "").setSmallIcon(R.drawable.ic_person).build()
- notif.flags = FLAG_PROMOTED_ONGOING
- val notifEntry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, notif)
- val row = testHelper.createRow().apply { setPinnedStatus(PinnedStatus.PinnedBySystem) }
- notifEntry.row = row
-
- underTest.showNotification(notifEntry)
+ @DisableFlags(StatusBarNotifChips.FLAG_NAME, PromotedNotificationUi.FLAG_NAME)
+ fun testIsSticky_promotedAndExpanded_notifChipsFlagOff_promotedUiFlagOff_true() {
+ assertThat(getIsSticky_promotedAndExpanded()).isTrue()
+ }
- val headsUpEntry = underTest.getHeadsUpEntry(notifEntry.key)
- headsUpEntry!!.setExpanded(true)
+ @Test
+ @EnableFlags(StatusBarNotifChips.FLAG_NAME, PromotedNotificationUi.FLAG_NAME)
+ fun testIsSticky_promotedAndExpanded_notifChipsFlagOn_promotedUiFlagOn_false() {
+ assertThat(getIsSticky_promotedAndExpanded()).isFalse()
+ }
- assertThat(underTest.isSticky(notifEntry.key)).isTrue()
+ @Test
+ @EnableFlags(PromotedNotificationUi.FLAG_NAME)
+ @DisableFlags(StatusBarNotifChips.FLAG_NAME)
+ fun testIsSticky_promotedAndExpanded_promotedUiFlagOn_false() {
+ assertThat(getIsSticky_promotedAndExpanded()).isFalse()
}
@Test
@EnableFlags(StatusBarNotifChips.FLAG_NAME)
+ @DisableFlags(PromotedNotificationUi.FLAG_NAME)
fun testIsSticky_promotedAndExpanded_notifChipsFlagOn_false() {
+ assertThat(getIsSticky_promotedAndExpanded()).isFalse()
+ }
+
+ private fun getIsSticky_promotedAndExpanded(): Boolean {
val notif = Notification.Builder(mContext, "").setSmallIcon(R.drawable.ic_person).build()
notif.flags = FLAG_PROMOTED_ONGOING
val notifEntry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, notif)
@@ -708,7 +716,7 @@ class HeadsUpManagerImplTest(flags: FlagsParameterization) : SysuiTestCase() {
val headsUpEntry = underTest.getHeadsUpEntry(notifEntry.key)
headsUpEntry!!.setExpanded(true)
- assertThat(underTest.isSticky(notifEntry.key)).isFalse()
+ return underTest.isSticky(notifEntry.key)
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractorImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractorImplTest.kt
index abb1edf8cb27..8054bd113771 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractorImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractorImplTest.kt
@@ -237,9 +237,9 @@ class PromotedNotificationContentExtractorImplTest : SysuiTestCase() {
assertThat(content).isNotNull()
assertThat(content?.style).isEqualTo(Style.Progress)
- assertThat(content?.progress).isNotNull()
- assertThat(content?.progress?.progress).isEqualTo(75)
- assertThat(content?.progress?.progressMax).isEqualTo(100)
+ assertThat(content?.newProgress).isNotNull()
+ assertThat(content?.newProgress?.progress).isEqualTo(75)
+ assertThat(content?.newProgress?.progressMax).isEqualTo(100)
}
@Test
@@ -255,6 +255,43 @@ class PromotedNotificationContentExtractorImplTest : SysuiTestCase() {
assertThat(content?.style).isEqualTo(Style.Ineligible)
}
+ @Test
+ @EnableFlags(PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME)
+ fun extractsContent_fromOldProgressDeterminate() {
+ val entry = createEntry {
+ setProgress(TEST_PROGRESS_MAX, TEST_PROGRESS, /* indeterminate= */ false)
+ }
+
+ val content = extractContent(entry)
+
+ assertThat(content).isNotNull()
+
+ val oldProgress = content?.oldProgress
+ assertThat(oldProgress).isNotNull()
+
+ assertThat(content).isNotNull()
+ assertThat(content?.oldProgress).isNotNull()
+ assertThat(content?.oldProgress?.progress).isEqualTo(TEST_PROGRESS)
+ assertThat(content?.oldProgress?.max).isEqualTo(TEST_PROGRESS_MAX)
+ assertThat(content?.oldProgress?.isIndeterminate).isFalse()
+ }
+
+ @Test
+ @EnableFlags(PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME)
+ fun extractsContent_fromOldProgressIndeterminate() {
+ val entry = createEntry {
+ setProgress(TEST_PROGRESS_MAX, TEST_PROGRESS, /* indeterminate= */ true)
+ }
+
+ val content = extractContent(entry)
+
+ assertThat(content).isNotNull()
+ assertThat(content?.oldProgress).isNotNull()
+ assertThat(content?.oldProgress?.progress).isEqualTo(TEST_PROGRESS)
+ assertThat(content?.oldProgress?.max).isEqualTo(TEST_PROGRESS_MAX)
+ assertThat(content?.oldProgress?.isIndeterminate).isTrue()
+ }
+
private fun extractContent(entry: NotificationEntry): PromotedNotificationContentModel? {
val recoveredBuilder = Notification.Builder(context, entry.sbn.notification)
return underTest.extractContent(entry, recoveredBuilder)
@@ -277,6 +314,9 @@ class PromotedNotificationContentExtractorImplTest : SysuiTestCase() {
private const val TEST_CONTENT_TEXT = "content text"
private const val TEST_SHORT_CRITICAL_TEXT = "short"
+ private const val TEST_PROGRESS = 50
+ private const val TEST_PROGRESS_MAX = 100
+
private const val TEST_PERSON_NAME = "person name"
private const val TEST_PERSON_KEY = "person key"
private val TEST_PERSON =
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt
index b0b80a9419e2..52c41a07198d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt
@@ -25,11 +25,14 @@ import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.ActivityTransitionAnimator
import com.android.systemui.flags.EnableSceneContainer
+import com.android.systemui.kosmos.testScope
import com.android.systemui.shared.Flags as SharedFlags
import com.android.systemui.statusbar.SysuiStatusBarStateController
+import com.android.systemui.testKosmos
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -48,6 +51,7 @@ class ActivityStarterImplTest : SysuiTestCase() {
@Mock private lateinit var activityStarterInternal: ActivityStarterInternalImpl
@Mock private lateinit var statusBarStateController: SysuiStatusBarStateController
private lateinit var underTest: ActivityStarterImpl
+ private val kosmos = testKosmos()
private val mainExecutor = FakeExecutor(FakeSystemClock())
@Before
@@ -69,12 +73,18 @@ class ActivityStarterImplTest : SysuiTestCase() {
@EnableSceneContainer
@Test
fun registerTransition_forwardsTheRequest() {
- val cookie = mock(ActivityTransitionAnimator.TransitionCookie::class.java)
- val controllerFactory = mock(ActivityTransitionAnimator.ControllerFactory::class.java)
-
- underTest.registerTransition(cookie, controllerFactory)
-
- verify(activityStarterInternal).registerTransition(eq(cookie), eq(controllerFactory))
+ with(kosmos) {
+ testScope.runTest {
+ val cookie = mock(ActivityTransitionAnimator.TransitionCookie::class.java)
+ val controllerFactory =
+ mock(ActivityTransitionAnimator.ControllerFactory::class.java)
+
+ underTest.registerTransition(cookie, controllerFactory, testScope)
+
+ verify(activityStarterInternal)
+ .registerTransition(eq(cookie), eq(controllerFactory), eq(testScope))
+ }
+ }
}
@DisableFlags(
@@ -83,12 +93,17 @@ class ActivityStarterImplTest : SysuiTestCase() {
)
@Test
fun registerTransition_doesNotForwardTheRequest_whenFlaggedOff() {
- val cookie = mock(ActivityTransitionAnimator.TransitionCookie::class.java)
- val controllerFactory = mock(ActivityTransitionAnimator.ControllerFactory::class.java)
+ with(kosmos) {
+ testScope.runTest {
+ val cookie = mock(ActivityTransitionAnimator.TransitionCookie::class.java)
+ val controllerFactory =
+ mock(ActivityTransitionAnimator.ControllerFactory::class.java)
- underTest.registerTransition(cookie, controllerFactory)
+ underTest.registerTransition(cookie, controllerFactory, testScope)
- verify(activityStarterInternal, never()).registerTransition(any(), any())
+ verify(activityStarterInternal, never()).registerTransition(any(), any(), any())
+ }
+ }
}
@EnableFlags(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImplTest.kt
index 5406acf694ff..dfa5c9a26d79 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImplTest.kt
@@ -42,6 +42,7 @@ import com.android.systemui.communal.domain.interactor.CommunalSettingsInteracto
import com.android.systemui.communal.domain.interactor.communalSettingsInteractor
import com.android.systemui.keyguard.KeyguardViewMediator
import com.android.systemui.keyguard.WakefulnessLifecycle
+import com.android.systemui.kosmos.testScope
import com.android.systemui.plugins.ActivityStarter.OnDismissAction
import com.android.systemui.settings.UserTracker
import com.android.systemui.shade.ShadeController
@@ -58,12 +59,14 @@ import com.android.systemui.statusbar.policy.DeviceProvisionedController
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.statusbar.window.StatusBarWindowController
import com.android.systemui.statusbar.window.StatusBarWindowControllerStore
+import com.android.systemui.testKosmos
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
import java.util.Optional
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.test.runTest
import org.junit.Assert.assertThrows
import org.junit.Before
import org.junit.Test
@@ -109,6 +112,7 @@ class LegacyActivityStarterInternalImplTest : SysuiTestCase() {
@Mock private lateinit var communalSceneInteractor: CommunalSceneInteractor
@Mock private lateinit var communalSettingsInteractor: CommunalSettingsInteractor
private lateinit var underTest: LegacyActivityStarterInternalImpl
+ private val kosmos = testKosmos()
private val mainExecutor = FakeExecutor(FakeSystemClock())
private val shadeAnimationInteractor =
ShadeAnimationInteractorLegacyImpl(ShadeAnimationRepository(), FakeShadeRepository())
@@ -157,13 +161,18 @@ class LegacyActivityStarterInternalImplTest : SysuiTestCase() {
)
@Test
fun registerTransition_registers() {
- val cookie = mock(ActivityTransitionAnimator.TransitionCookie::class.java)
- val controllerFactory = mock(ActivityTransitionAnimator.ControllerFactory::class.java)
- `when`(controllerFactory.cookie).thenReturn(cookie)
+ with(kosmos) {
+ testScope.runTest {
+ val cookie = mock(ActivityTransitionAnimator.TransitionCookie::class.java)
+ val controllerFactory =
+ mock(ActivityTransitionAnimator.ControllerFactory::class.java)
+ `when`(controllerFactory.cookie).thenReturn(cookie)
- underTest.registerTransition(cookie, controllerFactory)
+ underTest.registerTransition(cookie, controllerFactory, testScope)
- verify(activityTransitionAnimator).register(eq(cookie), any())
+ verify(activityTransitionAnimator).register(eq(cookie), any(), eq(testScope))
+ }
+ }
}
@DisableFlags(
@@ -172,14 +181,19 @@ class LegacyActivityStarterInternalImplTest : SysuiTestCase() {
)
@Test
fun registerTransition_throws_whenFlagsAreDisabled() {
- val cookie = mock(ActivityTransitionAnimator.TransitionCookie::class.java)
- val controllerFactory = mock(ActivityTransitionAnimator.ControllerFactory::class.java)
+ with(kosmos) {
+ testScope.runTest {
+ val cookie = mock(ActivityTransitionAnimator.TransitionCookie::class.java)
+ val controllerFactory =
+ mock(ActivityTransitionAnimator.ControllerFactory::class.java)
- assertThrows(IllegalStateException::class.java) {
- underTest.registerTransition(cookie, controllerFactory)
- }
+ assertThrows(IllegalStateException::class.java) {
+ underTest.registerTransition(cookie, controllerFactory, testScope)
+ }
- verify(activityTransitionAnimator, never()).register(any(), any())
+ verify(activityTransitionAnimator, never()).register(any(), any(), any())
+ }
+ }
}
@EnableFlags(
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java
index ca98cbf20c3a..18891dba4b0d 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java
@@ -25,6 +25,8 @@ import android.view.View;
import com.android.systemui.animation.ActivityTransitionAnimator;
import com.android.systemui.plugins.annotations.ProvidesInterface;
+import kotlinx.coroutines.CoroutineScope;
+
/**
* An interface to start activities. This is used as a callback from the views to
* {@link PhoneStatusBar} to allow custom handling for starting the activity, i.e. dismissing the
@@ -37,11 +39,12 @@ public interface ActivityStarter {
/**
* Registers the given {@link ActivityTransitionAnimator.ControllerFactory} for launching and
* closing transitions matching the {@link ActivityTransitionAnimator.TransitionCookie} and the
- * {@link ComponentName} that it contains.
+ * {@link ComponentName} that it contains, within the given {@link CoroutineScope}.
*/
void registerTransition(
ActivityTransitionAnimator.TransitionCookie cookie,
- ActivityTransitionAnimator.ControllerFactory controllerFactory);
+ ActivityTransitionAnimator.ControllerFactory controllerFactory,
+ CoroutineScope scope);
/**
* Unregisters the {@link ActivityTransitionAnimator.ControllerFactory} previously registered
diff --git a/packages/SystemUI/pods/com/android/systemui/util/settings/UserSettingsProxy.kt b/packages/SystemUI/pods/com/android/systemui/util/settings/UserSettingsProxy.kt
index 1a5517059ca4..68ad11e3ec01 100644
--- a/packages/SystemUI/pods/com/android/systemui/util/settings/UserSettingsProxy.kt
+++ b/packages/SystemUI/pods/com/android/systemui/util/settings/UserSettingsProxy.kt
@@ -157,7 +157,11 @@ interface UserSettingsProxy : SettingsProxy {
userHandle: Int,
) =
settingsScope.launch("registerContentObserverForUserAsync-A") {
- registerContentObserverForUserSync(getUriFor(name), settingsObserver, userHandle)
+ try {
+ registerContentObserverForUserSync(getUriFor(name), settingsObserver, userHandle)
+ } catch (e: SecurityException) {
+ throw SecurityException("registerContentObserverForUserAsync-A, name: $name", e)
+ }
}
/** Convenience wrapper around [ContentResolver.registerContentObserver] */
@@ -198,7 +202,11 @@ interface UserSettingsProxy : SettingsProxy {
userHandle: Int,
) =
settingsScope.launch("registerContentObserverForUserAsync-B") {
- registerContentObserverForUserSync(uri, settingsObserver, userHandle)
+ try {
+ registerContentObserverForUserSync(uri, settingsObserver, userHandle)
+ } catch (e: SecurityException) {
+ throw SecurityException("registerContentObserverForUserAsync-B, uri: $uri", e)
+ }
}
/**
@@ -215,7 +223,11 @@ interface UserSettingsProxy : SettingsProxy {
@WorkerThread registered: Runnable,
) =
settingsScope.launch("registerContentObserverForUserAsync-C") {
- registerContentObserverForUserSync(uri, settingsObserver, userHandle)
+ try {
+ registerContentObserverForUserSync(uri, settingsObserver, userHandle)
+ } catch (e: SecurityException) {
+ throw SecurityException("registerContentObserverForUserAsync-C, uri: $uri", e)
+ }
registered.run()
}
@@ -274,12 +286,16 @@ interface UserSettingsProxy : SettingsProxy {
userHandle: Int,
) {
settingsScope.launch("registerContentObserverForUserAsync-D") {
- registerContentObserverForUserSync(
- getUriFor(name),
- notifyForDescendants,
- settingsObserver,
- userHandle,
- )
+ try {
+ registerContentObserverForUserSync(
+ getUriFor(name),
+ notifyForDescendants,
+ settingsObserver,
+ userHandle,
+ )
+ } catch (e: SecurityException) {
+ throw SecurityException("registerContentObserverForUserAsync-D, name: $name", e)
+ }
}
}
@@ -338,12 +354,16 @@ interface UserSettingsProxy : SettingsProxy {
userHandle: Int,
) =
settingsScope.launch("registerContentObserverForUserAsync-E") {
- registerContentObserverForUserSync(
- uri,
- notifyForDescendants,
- settingsObserver,
- userHandle,
- )
+ try {
+ registerContentObserverForUserSync(
+ uri,
+ notifyForDescendants,
+ settingsObserver,
+ userHandle,
+ )
+ } catch (e: SecurityException) {
+ throw SecurityException("registerContentObserverForUserAsync-E, uri: $uri", e)
+ }
}
/**
diff --git a/packages/SystemUI/res/values-sw600dp/config.xml b/packages/SystemUI/res/values-sw600dp/config.xml
index ab0f788dbb13..b4383156dc71 100644
--- a/packages/SystemUI/res/values-sw600dp/config.xml
+++ b/packages/SystemUI/res/values-sw600dp/config.xml
@@ -19,7 +19,7 @@
<!-- These resources are around just to allow their values to be customized
for different hardware and product builds. -->
-<resources xmlns:android="http://schemas.android.com/apk/res/android">
+<resources>
<!-- The maximum number of rows in the QuickSettings -->
<integer name="quick_settings_max_rows">4</integer>
@@ -51,9 +51,7 @@
ignored. -->
<string-array name="config_keyguardQuickAffordanceDefaults" translatable="false">
<item>bottom_start:home</item>
- <!-- TODO(b/384119565): revisit decision on defaults -->
- <item android:featureFlag="!com.android.systemui.glanceable_hub_v2_resources">bottom_end:create_note</item>
- <item android:featureFlag="com.android.systemui.glanceable_hub_v2_resources">bottom_end:glanceable_hub</item>
+ <item>bottom_end:create_note</item>
</string-array>
<!-- Whether volume panel should use the large screen layout or not -->
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 351fffdad45b..a96ebe7b4fd6 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1783,6 +1783,7 @@
<dimen name="wallet_button_vertical_padding">8dp</dimen>
<!-- Ongoing activity chip -->
+ <dimen name="ongoing_activity_chip_min_text_width">12dp</dimen>
<dimen name="ongoing_activity_chip_max_text_width">74dp</dimen>
<dimen name="ongoing_activity_chip_margin_start">5dp</dimen>
<!-- The activity chip side padding, used with the default phone icon. -->
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index c266a5b47cff..0b66a0ffb711 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -296,7 +296,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
private final Provider<JavaAdapter> mJavaAdapter;
private final Provider<SceneInteractor> mSceneInteractor;
private final Provider<AlternateBouncerInteractor> mAlternateBouncerInteractor;
- private final CommunalSceneInteractor mCommunalSceneInteractor;
+ private final Provider<CommunalSceneInteractor> mCommunalSceneInteractor;
private final AuthController mAuthController;
private final UiEventLogger mUiEventLogger;
private final Set<String> mAllowFingerprintOnOccludingActivitiesFromPackage;
@@ -2210,7 +2210,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
Provider<AlternateBouncerInteractor> alternateBouncerInteractor,
Provider<JavaAdapter> javaAdapter,
Provider<SceneInteractor> sceneInteractor,
- CommunalSceneInteractor communalSceneInteractor) {
+ Provider<CommunalSceneInteractor> communalSceneInteractor) {
mContext = context;
mSubscriptionManager = subscriptionManager;
mUserTracker = userTracker;
@@ -2543,7 +2543,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
if (glanceableHubV2()) {
mJavaAdapter.get().alwaysCollectFlow(
- mCommunalSceneInteractor.isCommunalVisible(),
+ mCommunalSceneInteractor.get().isCommunalVisible(),
this::onCommunalShowingChanged
);
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationAnimationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationAnimationController.java
index 615363da073a..db2ca1dbff02 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationAnimationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationAnimationController.java
@@ -203,8 +203,8 @@ class WindowMagnificationAnimationController implements ValueAnimator.AnimatorUp
return;
}
final float currentScale = mController.getScale();
- final float currentCenterX = mController.getCenterX();
- final float currentCenterY = mController.getCenterY();
+ final float currentCenterX = mController.getMagnificationFrameCenterX();
+ final float currentCenterY = mController.getMagnificationFrameCenterY();
if (mState == STATE_DISABLED) {
// We don't need to offset the center during the animation.
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
index 1587ab16fc38..a67ec65cceda 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
@@ -497,6 +497,9 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
if (configDiff == 0) {
return;
}
+ if (Flags.updateWindowMagnifierBottomBoundary()) {
+ updateSystemGestureInsetsTop();
+ }
if ((configDiff & ActivityInfo.CONFIG_ORIENTATION) != 0) {
onRotate();
}
@@ -542,8 +545,11 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
}
mWindowBounds.set(currentWindowBounds);
final Size windowFrameSize = restoreMagnificationWindowFrameIndexAndSizeIfPossible();
- final float newCenterX = (getCenterX()) * mWindowBounds.width() / oldWindowBounds.width();
- final float newCenterY = (getCenterY()) * mWindowBounds.height() / oldWindowBounds.height();
+ final float newCenterX =
+ (getMagnificationFrameCenterX()) * mWindowBounds.width() / oldWindowBounds.width();
+ final float newCenterY =
+ (getMagnificationFrameCenterY()) * mWindowBounds.height()
+ / oldWindowBounds.height();
setMagnificationFrame(windowFrameSize.getWidth(), windowFrameSize.getHeight(),
(int) newCenterX, (int) newCenterY);
@@ -672,8 +678,12 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
}
private void onWindowInsetChanged() {
- if (updateSystemGestureInsetsTop()) {
- updateSystemUIStateIfNeeded();
+ if (Flags.updateWindowMagnifierBottomBoundary()) {
+ updateSystemGestureInsetsTop();
+ } else {
+ if (updateSystemGestureInsetsTop()) {
+ updateSystemUIStateIfNeeded();
+ }
}
}
@@ -939,7 +949,9 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
final int x = MathUtils.clamp(mMagnificationFrame.left - mMirrorSurfaceMargin, minX, maxX);
final int minY = -mOuterBorderSize;
- final int maxY = mWindowBounds.bottom - height + mOuterBorderSize;
+ final int maxY = Flags.updateWindowMagnifierBottomBoundary()
+ ? mSystemGestureTop - height + mOuterBorderSize
+ : mWindowBounds.bottom - height + mOuterBorderSize;
final int y = MathUtils.clamp(mMagnificationFrame.top - mMirrorSurfaceMargin, minY, maxY);
if (computeWindowSize) {
@@ -1098,6 +1110,10 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
}
private void updateSysUIState(boolean force) {
+ if (Flags.updateWindowMagnifierBottomBoundary()) {
+ return;
+ }
+
final boolean overlap = isActivated() && mSystemGestureTop > 0
&& mMirrorViewBounds.bottom > mSystemGestureTop;
if (force || overlap != mOverlapWithGestureInsets) {
@@ -1313,7 +1329,7 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
*
* @return the X coordinate. {@link Float#NaN} if the window is invisible.
*/
- float getCenterX() {
+ float getMagnificationFrameCenterX() {
return isActivated() ? mMagnificationFrame.exactCenterX() : Float.NaN;
}
@@ -1322,10 +1338,30 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
*
* @return the Y coordinate. {@link Float#NaN} if the window is invisible.
*/
- float getCenterY() {
+ float getMagnificationFrameCenterY() {
return isActivated() ? mMagnificationFrame.exactCenterY() : Float.NaN;
}
+ /**
+ * Returns the screen-relative X coordinate of the center of the magnifier window.
+ * This could be different from the position of the magnification frame since the magnification
+ * frame could overlap with the bottom inset, but the magnifier window would not.
+ * @return the Y coordinate. {@link Float#NaN} if the window is invisible.
+ */
+ float getMagnifierWindowX() {
+ return isActivated() ? (float) mMirrorViewBounds.left : Float.NaN;
+ }
+
+ /**
+ * Returns the screen-relative Y coordinate of the center of the magnifier window.
+ * This could be different from the position of the magnification frame since the magnification
+ * frame could overlap with the bottom inset, but the magnifier window would not.
+ * @return the Y coordinate. {@link Float#NaN} if the window is invisible.
+ */
+ float getMagnifierWindowY() {
+ return isActivated() ? (float) mMirrorViewBounds.top : Float.NaN;
+ }
+
@VisibleForTesting
boolean isDiagonalScrollingEnabled() {
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/AudioSharingDeviceItemActionInteractorImpl.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/AudioSharingDeviceItemActionInteractorImpl.kt
index 0303048436c9..94fca218c74f 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/AudioSharingDeviceItemActionInteractorImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/AudioSharingDeviceItemActionInteractorImpl.kt
@@ -53,7 +53,7 @@ constructor(
private val deviceItemActionInteractorImpl: DeviceItemActionInteractorImpl,
) : DeviceItemActionInteractor {
- override suspend fun onClick(deviceItem: DeviceItem, dialog: SystemUIDialog) {
+ override suspend fun onClick(deviceItem: DeviceItem, dialog: SystemUIDialog?) {
withContext(backgroundDispatcher) {
if (!audioSharingInteractor.audioSharingAvailable()) {
return@withContext deviceItemActionInteractorImpl.onClick(deviceItem, dialog)
@@ -70,10 +70,18 @@ constructor(
DeviceItemType.AVAILABLE_AUDIO_SHARING_MEDIA_BLUETOOTH_DEVICE -> {
if (audioSharingInteractor.qsDialogImprovementAvailable()) {
withContext(mainDispatcher) {
- delegateFactory
- .create(deviceItem.cachedBluetoothDevice)
- .createDialog()
- .let { dialogTransitionAnimator.showFromDialog(it, dialog) }
+ val audioSharingDialog =
+ delegateFactory
+ .create(deviceItem.cachedBluetoothDevice)
+ .createDialog()
+
+ if (dialog != null) {
+ audioSharingDialog.let {
+ dialogTransitionAnimator.showFromDialog(it, dialog)
+ }
+ } else {
+ audioSharingDialog.show()
+ }
}
} else {
launchSettings(deviceItem.cachedBluetoothDevice.device, dialog)
@@ -141,7 +149,7 @@ constructor(
)
}
- private fun launchSettings(device: BluetoothDevice, dialog: SystemUIDialog) {
+ private fun launchSettings(device: BluetoothDevice, dialog: SystemUIDialog?) {
val intent =
Intent(Settings.ACTION_BLUETOOTH_SETTINGS).apply {
putExtra(
@@ -155,7 +163,8 @@ constructor(
activityStarter.postStartActivityDismissingKeyguard(
intent,
0,
- dialogTransitionAnimator.createActivityTransitionController(dialog),
+ if (dialog == null) null
+ else dialogTransitionAnimator.createActivityTransitionController(dialog),
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothDetailsContentManager.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothDetailsContentManager.kt
new file mode 100644
index 000000000000..0be28f3c5a97
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothDetailsContentManager.kt
@@ -0,0 +1,442 @@
+/*
+ * 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.bluetooth.qsdialog
+
+import android.view.LayoutInflater
+import android.view.View
+import android.view.View.AccessibilityDelegate
+import android.view.View.GONE
+import android.view.View.INVISIBLE
+import android.view.View.VISIBLE
+import android.view.ViewGroup
+import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
+import android.view.accessibility.AccessibilityNodeInfo
+import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction
+import android.widget.Button
+import android.widget.ImageView
+import android.widget.ProgressBar
+import android.widget.Switch
+import android.widget.TextView
+import androidx.annotation.StringRes
+import androidx.recyclerview.widget.AsyncListDiffer
+import androidx.recyclerview.widget.DiffUtil
+import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.recyclerview.widget.RecyclerView
+import com.android.internal.R as InternalR
+import com.android.internal.logging.UiEventLogger
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.res.R
+import com.android.systemui.util.time.SystemClock
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.isActive
+import kotlinx.coroutines.withContext
+
+data class DeviceItemClick(val deviceItem: DeviceItem, val clickedView: View, val target: Target) {
+ enum class Target {
+ ENTIRE_ROW,
+ ACTION_ICON,
+ }
+}
+
+/** View content manager for showing active, connected and saved bluetooth devices. */
+class BluetoothDetailsContentManager
+@AssistedInject
+internal constructor(
+ @Assisted private val initialUiProperties: BluetoothTileDialogViewModel.UiProperties,
+ @Assisted private val cachedContentHeight: Int,
+ @Assisted private val bluetoothTileDialogCallback: BluetoothTileDialogCallback,
+ @Assisted private val isInDialog: Boolean,
+ @Assisted private val doneButtonCallback: () -> Unit,
+ @Main private val mainDispatcher: CoroutineDispatcher,
+ private val systemClock: SystemClock,
+ private val uiEventLogger: UiEventLogger,
+ private val logger: BluetoothTileDialogLogger,
+) {
+
+ private val mutableBluetoothStateToggle: MutableStateFlow<Boolean?> = MutableStateFlow(null)
+ internal val bluetoothStateToggle
+ get() = mutableBluetoothStateToggle.asStateFlow()
+
+ private val mutableBluetoothAutoOnToggle: MutableStateFlow<Boolean?> = MutableStateFlow(null)
+ internal val bluetoothAutoOnToggle
+ get() = mutableBluetoothAutoOnToggle.asStateFlow()
+
+ private val mutableDeviceItemClick: MutableStateFlow<DeviceItemClick?> = MutableStateFlow(null)
+ internal val deviceItemClick
+ get() = mutableDeviceItemClick.asStateFlow()
+
+ private val mutableContentHeight: MutableStateFlow<Int?> = MutableStateFlow(null)
+ internal val contentHeight
+ get() = mutableContentHeight.asStateFlow()
+
+ private val deviceItemAdapter: Adapter = Adapter()
+
+ private var lastUiUpdateMs: Long = -1
+
+ private var lastItemRow: Int = -1
+
+ // UI Components
+ private lateinit var contentView: View
+ private lateinit var doneButton: Button
+ private lateinit var bluetoothToggle: Switch
+ private lateinit var subtitleTextView: TextView
+ private lateinit var seeAllButton: View
+ private lateinit var pairNewDeviceButton: View
+ private lateinit var deviceListView: RecyclerView
+ private lateinit var autoOnToggle: Switch
+ private lateinit var autoOnToggleLayout: View
+ private lateinit var autoOnToggleInfoTextView: TextView
+ private lateinit var audioSharingButton: Button
+ private lateinit var progressBarAnimation: ProgressBar
+ private lateinit var progressBarBackground: View
+ private lateinit var scrollViewContent: View
+
+ @AssistedFactory
+ internal interface Factory {
+ fun create(
+ initialUiProperties: BluetoothTileDialogViewModel.UiProperties,
+ cachedContentHeight: Int,
+ dialogCallback: BluetoothTileDialogCallback,
+ isInDialog: Boolean,
+ doneButtonCallback: () -> Unit,
+ ): BluetoothDetailsContentManager
+ }
+
+ fun bind(contentView: View) {
+ this.contentView = contentView
+
+ doneButton = contentView.requireViewById(R.id.done_button)
+ bluetoothToggle = contentView.requireViewById(R.id.bluetooth_toggle)
+ subtitleTextView = contentView.requireViewById(R.id.bluetooth_tile_dialog_subtitle)
+ seeAllButton = contentView.requireViewById(R.id.see_all_button)
+ pairNewDeviceButton = contentView.requireViewById(R.id.pair_new_device_button)
+ deviceListView = contentView.requireViewById(R.id.device_list)
+ autoOnToggle = contentView.requireViewById(R.id.bluetooth_auto_on_toggle)
+ autoOnToggleLayout = contentView.requireViewById(R.id.bluetooth_auto_on_toggle_layout)
+ autoOnToggleInfoTextView =
+ contentView.requireViewById(R.id.bluetooth_auto_on_toggle_info_text)
+ audioSharingButton = contentView.requireViewById(R.id.audio_sharing_button)
+ progressBarAnimation =
+ contentView.requireViewById(R.id.bluetooth_tile_dialog_progress_animation)
+ progressBarBackground =
+ contentView.requireViewById(R.id.bluetooth_tile_dialog_progress_background)
+ scrollViewContent = contentView.requireViewById(R.id.scroll_view)
+
+ setupToggle()
+ setupRecyclerView()
+ setupDoneButton()
+
+ subtitleTextView.text = contentView.context.getString(initialUiProperties.subTitleResId)
+ seeAllButton.setOnClickListener { bluetoothTileDialogCallback.onSeeAllClicked(it) }
+ pairNewDeviceButton.setOnClickListener {
+ bluetoothTileDialogCallback.onPairNewDeviceClicked(it)
+ }
+ audioSharingButton.apply {
+ setOnClickListener { bluetoothTileDialogCallback.onAudioSharingButtonClicked(it) }
+ accessibilityDelegate =
+ object : AccessibilityDelegate() {
+ override fun onInitializeAccessibilityNodeInfo(
+ host: View,
+ info: AccessibilityNodeInfo,
+ ) {
+ super.onInitializeAccessibilityNodeInfo(host, info)
+ info.addAction(
+ AccessibilityAction(
+ AccessibilityAction.ACTION_CLICK.id,
+ contentView.context.getString(
+ R.string
+ .quick_settings_bluetooth_audio_sharing_button_accessibility
+ ),
+ )
+ )
+ }
+ }
+ }
+ scrollViewContent.apply {
+ minimumHeight =
+ resources.getDimensionPixelSize(initialUiProperties.scrollViewMinHeightResId)
+ layoutParams.height = maxOf(cachedContentHeight, minimumHeight)
+ }
+ }
+
+ fun start() {
+ lastUiUpdateMs = systemClock.elapsedRealtime()
+ }
+
+ fun releaseView() {
+ mutableContentHeight.value = scrollViewContent.measuredHeight
+ }
+
+ internal suspend fun animateProgressBar(animate: Boolean) {
+ withContext(mainDispatcher) {
+ if (animate) {
+ showProgressBar()
+ } else {
+ delay(PROGRESS_BAR_ANIMATION_DURATION_MS)
+ hideProgressBar()
+ }
+ }
+ }
+
+ internal suspend fun onDeviceItemUpdated(
+ deviceItem: List<DeviceItem>,
+ showSeeAll: Boolean,
+ showPairNewDevice: Boolean,
+ ) {
+ withContext(mainDispatcher) {
+ val start = systemClock.elapsedRealtime()
+ val itemRow = deviceItem.size + showSeeAll.toInt() + showPairNewDevice.toInt()
+ // If not the first load, add a slight delay for smoother dialog height change
+ if (itemRow != lastItemRow && lastItemRow != -1) {
+ delay(MIN_HEIGHT_CHANGE_INTERVAL_MS - (start - lastUiUpdateMs))
+ }
+ if (isActive) {
+ deviceItemAdapter.refreshDeviceItemList(deviceItem) {
+ seeAllButton.visibility = if (showSeeAll) VISIBLE else GONE
+ pairNewDeviceButton.visibility = if (showPairNewDevice) VISIBLE else GONE
+ // Update the height after data is updated
+ scrollViewContent.layoutParams.height = WRAP_CONTENT
+ lastUiUpdateMs = systemClock.elapsedRealtime()
+ lastItemRow = itemRow
+ logger.logDeviceUiUpdate(lastUiUpdateMs - start)
+ }
+ }
+ }
+ }
+
+ internal fun onBluetoothStateUpdated(
+ isEnabled: Boolean,
+ uiProperties: BluetoothTileDialogViewModel.UiProperties,
+ ) {
+ bluetoothToggle.apply {
+ isChecked = isEnabled
+ setEnabled(true)
+ alpha = ENABLED_ALPHA
+ }
+ subtitleTextView.text = contentView.context.getString(uiProperties.subTitleResId)
+ autoOnToggleLayout.visibility = uiProperties.autoOnToggleVisibility
+ }
+
+ internal fun onBluetoothAutoOnUpdated(isEnabled: Boolean, @StringRes infoResId: Int) {
+ autoOnToggle.isChecked = isEnabled
+ autoOnToggleInfoTextView.text = contentView.context.getString(infoResId)
+ }
+
+ internal fun onAudioSharingButtonUpdated(visibility: Int, label: String?, isActive: Boolean) {
+ audioSharingButton.apply {
+ this.visibility = visibility
+ label?.let { text = it }
+ this.isActivated = isActive
+ }
+ }
+
+ private fun setupToggle() {
+ bluetoothToggle.setOnCheckedChangeListener { view, isChecked ->
+ mutableBluetoothStateToggle.value = isChecked
+ view.apply {
+ isEnabled = false
+ alpha = DISABLED_ALPHA
+ }
+ logger.logBluetoothState(BluetoothStateStage.USER_TOGGLED, isChecked.toString())
+ uiEventLogger.log(BluetoothTileDialogUiEvent.BLUETOOTH_TOGGLE_CLICKED)
+ }
+
+ autoOnToggleLayout.visibility = initialUiProperties.autoOnToggleVisibility
+ autoOnToggle.setOnCheckedChangeListener { _, isChecked ->
+ mutableBluetoothAutoOnToggle.value = isChecked
+ uiEventLogger.log(BluetoothTileDialogUiEvent.BLUETOOTH_AUTO_ON_TOGGLE_CLICKED)
+ }
+ }
+
+ private fun setupDoneButton() {
+ if (isInDialog) {
+ doneButton.setOnClickListener { doneButtonCallback() }
+ } else {
+ doneButton.visibility = GONE
+ }
+ }
+
+ private fun setupRecyclerView() {
+ deviceListView.apply {
+ layoutManager = LinearLayoutManager(contentView.context)
+ adapter = deviceItemAdapter
+ }
+ }
+
+ private fun showProgressBar() {
+ if (progressBarAnimation.visibility != VISIBLE) {
+ progressBarAnimation.visibility = VISIBLE
+ progressBarBackground.visibility = INVISIBLE
+ }
+ }
+
+ private fun hideProgressBar() {
+ if (progressBarAnimation.visibility != INVISIBLE) {
+ progressBarAnimation.visibility = INVISIBLE
+ progressBarBackground.visibility = VISIBLE
+ }
+ }
+
+ internal inner class Adapter : RecyclerView.Adapter<Adapter.DeviceItemViewHolder>() {
+
+ private val diffUtilCallback =
+ object : DiffUtil.ItemCallback<DeviceItem>() {
+ override fun areItemsTheSame(
+ deviceItem1: DeviceItem,
+ deviceItem2: DeviceItem,
+ ): Boolean {
+ return deviceItem1.cachedBluetoothDevice == deviceItem2.cachedBluetoothDevice
+ }
+
+ override fun areContentsTheSame(
+ deviceItem1: DeviceItem,
+ deviceItem2: DeviceItem,
+ ): Boolean {
+ return deviceItem1.type == deviceItem2.type &&
+ deviceItem1.cachedBluetoothDevice == deviceItem2.cachedBluetoothDevice &&
+ deviceItem1.deviceName == deviceItem2.deviceName &&
+ deviceItem1.connectionSummary == deviceItem2.connectionSummary &&
+ // Ignored the icon drawable
+ deviceItem1.iconWithDescription?.second ==
+ deviceItem2.iconWithDescription?.second &&
+ deviceItem1.background == deviceItem2.background &&
+ deviceItem1.isEnabled == deviceItem2.isEnabled &&
+ deviceItem1.actionAccessibilityLabel == deviceItem2.actionAccessibilityLabel
+ }
+ }
+
+ private val asyncListDiffer = AsyncListDiffer(this, diffUtilCallback)
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DeviceItemViewHolder {
+ val view =
+ LayoutInflater.from(parent.context)
+ .inflate(R.layout.bluetooth_device_item, parent, false)
+ return DeviceItemViewHolder(view)
+ }
+
+ override fun getItemCount() = asyncListDiffer.currentList.size
+
+ override fun onBindViewHolder(holder: DeviceItemViewHolder, position: Int) {
+ val item = getItem(position)
+ holder.bind(item)
+ }
+
+ internal fun getItem(position: Int) = asyncListDiffer.currentList[position]
+
+ internal fun refreshDeviceItemList(updated: List<DeviceItem>, callback: () -> Unit) {
+ asyncListDiffer.submitList(updated, callback)
+ }
+
+ internal inner class DeviceItemViewHolder(view: View) : RecyclerView.ViewHolder(view) {
+ private val container = view.requireViewById<View>(R.id.bluetooth_device_row)
+ private val nameView = view.requireViewById<TextView>(R.id.bluetooth_device_name)
+ private val summaryView = view.requireViewById<TextView>(R.id.bluetooth_device_summary)
+ private val iconView = view.requireViewById<ImageView>(R.id.bluetooth_device_icon)
+ private val actionIcon = view.requireViewById<ImageView>(R.id.gear_icon_image)
+ private val actionIconView = view.requireViewById<View>(R.id.gear_icon)
+ private val divider = view.requireViewById<View>(R.id.divider)
+
+ internal fun bind(item: DeviceItem) {
+ container.apply {
+ isEnabled = item.isEnabled
+ background = item.background?.let { context.getDrawable(it) }
+ setOnClickListener {
+ mutableDeviceItemClick.value =
+ DeviceItemClick(item, it, DeviceItemClick.Target.ENTIRE_ROW)
+ uiEventLogger.log(BluetoothTileDialogUiEvent.DEVICE_CLICKED)
+ }
+
+ // updating icon colors
+ val tintColor =
+ context.getColor(
+ if (item.isActive) InternalR.color.materialColorOnPrimaryContainer
+ else InternalR.color.materialColorOnSurface
+ )
+
+ // update icons
+ iconView.apply {
+ item.iconWithDescription?.let {
+ setImageDrawable(it.first)
+ contentDescription = it.second
+ }
+ }
+
+ actionIcon.setImageResource(item.actionIconRes)
+ actionIcon.drawable?.setTint(tintColor)
+
+ divider.setBackgroundColor(tintColor)
+
+ // update text styles
+ nameView.setTextAppearance(
+ if (item.isActive) R.style.TextAppearance_BluetoothTileDialog_Active
+ else R.style.TextAppearance_BluetoothTileDialog
+ )
+ summaryView.setTextAppearance(
+ if (item.isActive) R.style.TextAppearance_BluetoothTileDialog_Active
+ else R.style.TextAppearance_BluetoothTileDialog
+ )
+
+ accessibilityDelegate =
+ object : AccessibilityDelegate() {
+ override fun onInitializeAccessibilityNodeInfo(
+ host: View,
+ info: AccessibilityNodeInfo,
+ ) {
+ super.onInitializeAccessibilityNodeInfo(host, info)
+ info.addAction(
+ AccessibilityAction(
+ AccessibilityAction.ACTION_CLICK.id,
+ item.actionAccessibilityLabel,
+ )
+ )
+ }
+ }
+ }
+ nameView.text = item.deviceName
+ summaryView.text = item.connectionSummary
+
+ actionIconView.setOnClickListener {
+ mutableDeviceItemClick.value =
+ DeviceItemClick(item, it, DeviceItemClick.Target.ACTION_ICON)
+ }
+ }
+ }
+ }
+
+ internal companion object {
+ const val MIN_HEIGHT_CHANGE_INTERVAL_MS = 800L
+ const val ACTION_BLUETOOTH_DEVICE_DETAILS =
+ "com.android.settings.BLUETOOTH_DEVICE_DETAIL_SETTINGS"
+ const val ACTION_PREVIOUSLY_CONNECTED_DEVICE =
+ "com.android.settings.PREVIOUSLY_CONNECTED_DEVICE"
+ const val ACTION_PAIR_NEW_DEVICE = "android.settings.BLUETOOTH_PAIRING_SETTINGS"
+ const val ACTION_AUDIO_SHARING = "com.android.settings.BLUETOOTH_AUDIO_SHARING_SETTINGS"
+ const val DISABLED_ALPHA = 0.3f
+ const val ENABLED_ALPHA = 1f
+ const val PROGRESS_BAR_ANIMATION_DURATION_MS = 1500L
+
+ private fun Boolean.toInt(): Int {
+ return if (this) 1 else 0
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegate.kt
index 56caddfbd637..3e61c45c7f25 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegate.kt
@@ -18,50 +18,14 @@ package com.android.systemui.bluetooth.qsdialog
import android.os.Bundle
import android.view.LayoutInflater
-import android.view.View
-import android.view.View.AccessibilityDelegate
-import android.view.View.GONE
-import android.view.View.INVISIBLE
-import android.view.View.VISIBLE
-import android.view.ViewGroup
-import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
-import android.view.accessibility.AccessibilityNodeInfo
-import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction
-import android.widget.Button
-import android.widget.ImageView
-import android.widget.ProgressBar
-import android.widget.Switch
-import android.widget.TextView
-import androidx.annotation.StringRes
-import androidx.recyclerview.widget.AsyncListDiffer
-import androidx.recyclerview.widget.DiffUtil
-import androidx.recyclerview.widget.LinearLayoutManager
-import androidx.recyclerview.widget.RecyclerView
-import com.android.internal.R as InternalR
import com.android.internal.logging.UiEventLogger
-import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.qs.flags.QsDetailedView
import com.android.systemui.res.R
import com.android.systemui.shade.domain.interactor.ShadeDialogContextInteractor
import com.android.systemui.statusbar.phone.SystemUIDialog
-import com.android.systemui.util.time.SystemClock
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
-import kotlinx.coroutines.CoroutineDispatcher
-import kotlinx.coroutines.delay
-import kotlinx.coroutines.flow.MutableSharedFlow
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.asSharedFlow
-import kotlinx.coroutines.flow.asStateFlow
-import kotlinx.coroutines.isActive
-import kotlinx.coroutines.withContext
-
-data class DeviceItemClick(val deviceItem: DeviceItem, val clickedView: View, val target: Target) {
- enum class Target {
- ENTIRE_ROW,
- ACTION_ICON,
- }
-}
/** Dialog for showing active, connected and saved bluetooth devices. */
class BluetoothTileDialogDelegate
@@ -71,37 +35,13 @@ internal constructor(
@Assisted private val cachedContentHeight: Int,
@Assisted private val bluetoothTileDialogCallback: BluetoothTileDialogCallback,
@Assisted private val dismissListener: Runnable,
- @Main private val mainDispatcher: CoroutineDispatcher,
- private val systemClock: SystemClock,
private val uiEventLogger: UiEventLogger,
- private val logger: BluetoothTileDialogLogger,
private val systemuiDialogFactory: SystemUIDialog.Factory,
private val shadeDialogContextInteractor: ShadeDialogContextInteractor,
+ private val bluetoothDetailsContentManagerFactory: BluetoothDetailsContentManager.Factory,
) : SystemUIDialog.Delegate {
- private val mutableBluetoothStateToggle: MutableStateFlow<Boolean?> = MutableStateFlow(null)
- internal val bluetoothStateToggle
- get() = mutableBluetoothStateToggle.asStateFlow()
-
- private val mutableBluetoothAutoOnToggle: MutableStateFlow<Boolean?> = MutableStateFlow(null)
- internal val bluetoothAutoOnToggle
- get() = mutableBluetoothAutoOnToggle.asStateFlow()
-
- private val mutableDeviceItemClick: MutableSharedFlow<DeviceItemClick> =
- MutableSharedFlow(extraBufferCapacity = 1)
- internal val deviceItemClick
- get() = mutableDeviceItemClick.asSharedFlow()
-
- private val mutableContentHeight: MutableSharedFlow<Int> =
- MutableSharedFlow(extraBufferCapacity = 1)
- internal val contentHeight
- get() = mutableContentHeight.asSharedFlow()
-
- private val deviceItemAdapter: Adapter = Adapter()
-
- private var lastUiUpdateMs: Long = -1
-
- private var lastItemRow: Int = -1
+ lateinit var contentManager: BluetoothDetailsContentManager
@AssistedFactory
internal interface Factory {
@@ -114,6 +54,9 @@ internal constructor(
}
override fun createDialog(): SystemUIDialog {
+ // If `QsDetailedView` is enabled, it should show the details view.
+ QsDetailedView.assertInLegacyMode()
+
return systemuiDialogFactory.create(this, shadeDialogContextInteractor.context)
}
@@ -127,362 +70,24 @@ internal constructor(
dialog.setContentView(this)
}
- setupToggle(dialog)
- setupRecyclerView(dialog)
-
- getSubtitleTextView(dialog).text = context.getString(initialUiProperties.subTitleResId)
- dialog.requireViewById<View>(R.id.done_button).setOnClickListener { dialog.dismiss() }
- getSeeAllButton(dialog).setOnClickListener {
- bluetoothTileDialogCallback.onSeeAllClicked(it)
- }
- getPairNewDeviceButton(dialog).setOnClickListener {
- bluetoothTileDialogCallback.onPairNewDeviceClicked(it)
- }
- getAudioSharingButtonView(dialog).apply {
- setOnClickListener { bluetoothTileDialogCallback.onAudioSharingButtonClicked(it) }
- accessibilityDelegate =
- object : AccessibilityDelegate() {
- override fun onInitializeAccessibilityNodeInfo(
- host: View,
- info: AccessibilityNodeInfo,
- ) {
- super.onInitializeAccessibilityNodeInfo(host, info)
- info.addAction(
- AccessibilityAction(
- AccessibilityAction.ACTION_CLICK.id,
- context.getString(
- R.string
- .quick_settings_bluetooth_audio_sharing_button_accessibility
- ),
- )
- )
- }
- }
- }
- getScrollViewContent(dialog).apply {
- minimumHeight =
- resources.getDimensionPixelSize(initialUiProperties.scrollViewMinHeightResId)
- layoutParams.height = maxOf(cachedContentHeight, minimumHeight)
- }
+ contentManager =
+ bluetoothDetailsContentManagerFactory.create(
+ initialUiProperties,
+ cachedContentHeight,
+ bluetoothTileDialogCallback,
+ /* isInDialog= */ true,
+ /* doneButtonCallback= */ fun() {
+ dialog.dismiss()
+ },
+ )
+ contentManager.bind(dialog.requireViewById(R.id.root))
}
override fun onStart(dialog: SystemUIDialog) {
- lastUiUpdateMs = systemClock.elapsedRealtime()
+ contentManager.start()
}
override fun onStop(dialog: SystemUIDialog) {
- mutableContentHeight.tryEmit(getScrollViewContent(dialog).measuredHeight)
- }
-
- internal suspend fun animateProgressBar(dialog: SystemUIDialog, animate: Boolean) {
- withContext(mainDispatcher) {
- if (animate) {
- showProgressBar(dialog)
- } else {
- delay(PROGRESS_BAR_ANIMATION_DURATION_MS)
- hideProgressBar(dialog)
- }
- }
- }
-
- internal suspend fun onDeviceItemUpdated(
- dialog: SystemUIDialog,
- deviceItem: List<DeviceItem>,
- showSeeAll: Boolean,
- showPairNewDevice: Boolean,
- ) {
- withContext(mainDispatcher) {
- val start = systemClock.elapsedRealtime()
- val itemRow = deviceItem.size + showSeeAll.toInt() + showPairNewDevice.toInt()
- // If not the first load, add a slight delay for smoother dialog height change
- if (itemRow != lastItemRow && lastItemRow != -1) {
- delay(MIN_HEIGHT_CHANGE_INTERVAL_MS - (start - lastUiUpdateMs))
- }
- if (isActive) {
- deviceItemAdapter.refreshDeviceItemList(deviceItem) {
- getSeeAllButton(dialog).visibility = if (showSeeAll) VISIBLE else GONE
- getPairNewDeviceButton(dialog).visibility =
- if (showPairNewDevice) VISIBLE else GONE
- // Update the height after data is updated
- getScrollViewContent(dialog).layoutParams.height = WRAP_CONTENT
- lastUiUpdateMs = systemClock.elapsedRealtime()
- lastItemRow = itemRow
- logger.logDeviceUiUpdate(lastUiUpdateMs - start)
- }
- }
- }
- }
-
- internal fun onBluetoothStateUpdated(
- dialog: SystemUIDialog,
- isEnabled: Boolean,
- uiProperties: BluetoothTileDialogViewModel.UiProperties,
- ) {
- getToggleView(dialog).apply {
- isChecked = isEnabled
- setEnabled(true)
- alpha = ENABLED_ALPHA
- }
- getSubtitleTextView(dialog).text = dialog.context.getString(uiProperties.subTitleResId)
- getAutoOnToggleView(dialog).visibility = uiProperties.autoOnToggleVisibility
- }
-
- internal fun onBluetoothAutoOnUpdated(
- dialog: SystemUIDialog,
- isEnabled: Boolean,
- @StringRes infoResId: Int,
- ) {
- getAutoOnToggle(dialog).isChecked = isEnabled
- getAutoOnToggleInfoTextView(dialog).text = dialog.context.getString(infoResId)
- }
-
- internal fun onAudioSharingButtonUpdated(
- dialog: SystemUIDialog,
- visibility: Int,
- label: String?,
- isActive: Boolean,
- ) {
- getAudioSharingButtonView(dialog).apply {
- this.visibility = visibility
- label?.let { text = it }
- this.isActivated = isActive
- }
- }
-
- private fun setupToggle(dialog: SystemUIDialog) {
- val toggleView = getToggleView(dialog)
- toggleView.setOnCheckedChangeListener { view, isChecked ->
- mutableBluetoothStateToggle.value = isChecked
- view.apply {
- isEnabled = false
- alpha = DISABLED_ALPHA
- }
- logger.logBluetoothState(BluetoothStateStage.USER_TOGGLED, isChecked.toString())
- uiEventLogger.log(BluetoothTileDialogUiEvent.BLUETOOTH_TOGGLE_CLICKED)
- }
-
- getAutoOnToggleView(dialog).visibility = initialUiProperties.autoOnToggleVisibility
- getAutoOnToggle(dialog).setOnCheckedChangeListener { _, isChecked ->
- mutableBluetoothAutoOnToggle.value = isChecked
- uiEventLogger.log(BluetoothTileDialogUiEvent.BLUETOOTH_AUTO_ON_TOGGLE_CLICKED)
- }
- }
-
- private fun getToggleView(dialog: SystemUIDialog): Switch {
- return dialog.requireViewById(R.id.bluetooth_toggle)
- }
-
- private fun getSubtitleTextView(dialog: SystemUIDialog): TextView {
- return dialog.requireViewById(R.id.bluetooth_tile_dialog_subtitle)
- }
-
- private fun getSeeAllButton(dialog: SystemUIDialog): View {
- return dialog.requireViewById(R.id.see_all_button)
- }
-
- private fun getPairNewDeviceButton(dialog: SystemUIDialog): View {
- return dialog.requireViewById(R.id.pair_new_device_button)
- }
-
- private fun getDeviceListView(dialog: SystemUIDialog): RecyclerView {
- return dialog.requireViewById(R.id.device_list)
- }
-
- private fun getAutoOnToggle(dialog: SystemUIDialog): Switch {
- return dialog.requireViewById(R.id.bluetooth_auto_on_toggle)
- }
-
- private fun getAudioSharingButtonView(dialog: SystemUIDialog): Button {
- return dialog.requireViewById(R.id.audio_sharing_button)
- }
-
- private fun getAutoOnToggleView(dialog: SystemUIDialog): View {
- return dialog.requireViewById(R.id.bluetooth_auto_on_toggle_layout)
- }
-
- private fun getAutoOnToggleInfoTextView(dialog: SystemUIDialog): TextView {
- return dialog.requireViewById(R.id.bluetooth_auto_on_toggle_info_text)
- }
-
- private fun getProgressBarAnimation(dialog: SystemUIDialog): ProgressBar {
- return dialog.requireViewById(R.id.bluetooth_tile_dialog_progress_animation)
- }
-
- private fun getProgressBarBackground(dialog: SystemUIDialog): View {
- return dialog.requireViewById(R.id.bluetooth_tile_dialog_progress_background)
- }
-
- private fun getScrollViewContent(dialog: SystemUIDialog): View {
- return dialog.requireViewById(R.id.scroll_view)
- }
-
- private fun setupRecyclerView(dialog: SystemUIDialog) {
- getDeviceListView(dialog).apply {
- layoutManager = LinearLayoutManager(dialog.context)
- adapter = deviceItemAdapter
- }
- }
-
- private fun showProgressBar(dialog: SystemUIDialog) {
- val progressBarAnimation = getProgressBarAnimation(dialog)
- val progressBarBackground = getProgressBarBackground(dialog)
- if (progressBarAnimation.visibility != VISIBLE) {
- progressBarAnimation.visibility = VISIBLE
- progressBarBackground.visibility = INVISIBLE
- }
- }
-
- private fun hideProgressBar(dialog: SystemUIDialog) {
- val progressBarAnimation = getProgressBarAnimation(dialog)
- val progressBarBackground = getProgressBarBackground(dialog)
- if (progressBarAnimation.visibility != INVISIBLE) {
- progressBarAnimation.visibility = INVISIBLE
- progressBarBackground.visibility = VISIBLE
- }
- }
-
- internal inner class Adapter : RecyclerView.Adapter<Adapter.DeviceItemViewHolder>() {
-
- private val diffUtilCallback =
- object : DiffUtil.ItemCallback<DeviceItem>() {
- override fun areItemsTheSame(
- deviceItem1: DeviceItem,
- deviceItem2: DeviceItem,
- ): Boolean {
- return deviceItem1.cachedBluetoothDevice == deviceItem2.cachedBluetoothDevice
- }
-
- override fun areContentsTheSame(
- deviceItem1: DeviceItem,
- deviceItem2: DeviceItem,
- ): Boolean {
- return deviceItem1.type == deviceItem2.type &&
- deviceItem1.cachedBluetoothDevice == deviceItem2.cachedBluetoothDevice &&
- deviceItem1.deviceName == deviceItem2.deviceName &&
- deviceItem1.connectionSummary == deviceItem2.connectionSummary &&
- // Ignored the icon drawable
- deviceItem1.iconWithDescription?.second ==
- deviceItem2.iconWithDescription?.second &&
- deviceItem1.background == deviceItem2.background &&
- deviceItem1.isEnabled == deviceItem2.isEnabled &&
- deviceItem1.actionAccessibilityLabel == deviceItem2.actionAccessibilityLabel
- }
- }
-
- private val asyncListDiffer = AsyncListDiffer(this, diffUtilCallback)
-
- override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DeviceItemViewHolder {
- val view =
- LayoutInflater.from(parent.context)
- .inflate(R.layout.bluetooth_device_item, parent, false)
- return DeviceItemViewHolder(view)
- }
-
- override fun getItemCount() = asyncListDiffer.currentList.size
-
- override fun onBindViewHolder(holder: DeviceItemViewHolder, position: Int) {
- val item = getItem(position)
- holder.bind(item)
- }
-
- internal fun getItem(position: Int) = asyncListDiffer.currentList[position]
-
- internal fun refreshDeviceItemList(updated: List<DeviceItem>, callback: () -> Unit) {
- asyncListDiffer.submitList(updated, callback)
- }
-
- internal inner class DeviceItemViewHolder(view: View) : RecyclerView.ViewHolder(view) {
- private val container = view.requireViewById<View>(R.id.bluetooth_device_row)
- private val nameView = view.requireViewById<TextView>(R.id.bluetooth_device_name)
- private val summaryView = view.requireViewById<TextView>(R.id.bluetooth_device_summary)
- private val iconView = view.requireViewById<ImageView>(R.id.bluetooth_device_icon)
- private val actionIcon = view.requireViewById<ImageView>(R.id.gear_icon_image)
- private val actionIconView = view.requireViewById<View>(R.id.gear_icon)
- private val divider = view.requireViewById<View>(R.id.divider)
-
- internal fun bind(item: DeviceItem) {
- container.apply {
- isEnabled = item.isEnabled
- background = item.background?.let { context.getDrawable(it) }
- setOnClickListener {
- mutableDeviceItemClick.tryEmit(
- DeviceItemClick(item, it, DeviceItemClick.Target.ENTIRE_ROW)
- )
- uiEventLogger.log(BluetoothTileDialogUiEvent.DEVICE_CLICKED)
- }
-
- // updating icon colors
- val tintColor =
- context.getColor(
- if (item.isActive) InternalR.color.materialColorOnPrimaryContainer
- else InternalR.color.materialColorOnSurface
- )
-
- // update icons
- iconView.apply {
- item.iconWithDescription?.let {
- setImageDrawable(it.first)
- contentDescription = it.second
- }
- }
-
- actionIcon.setImageResource(item.actionIconRes)
- actionIcon.drawable?.setTint(tintColor)
-
- divider.setBackgroundColor(tintColor)
-
- // update text styles
- nameView.setTextAppearance(
- if (item.isActive) R.style.TextAppearance_BluetoothTileDialog_Active
- else R.style.TextAppearance_BluetoothTileDialog
- )
- summaryView.setTextAppearance(
- if (item.isActive) R.style.TextAppearance_BluetoothTileDialog_Active
- else R.style.TextAppearance_BluetoothTileDialog
- )
-
- accessibilityDelegate =
- object : AccessibilityDelegate() {
- override fun onInitializeAccessibilityNodeInfo(
- host: View,
- info: AccessibilityNodeInfo,
- ) {
- super.onInitializeAccessibilityNodeInfo(host, info)
- info.addAction(
- AccessibilityAction(
- AccessibilityAction.ACTION_CLICK.id,
- item.actionAccessibilityLabel,
- )
- )
- }
- }
- }
- nameView.text = item.deviceName
- summaryView.text = item.connectionSummary
-
- actionIconView.setOnClickListener {
- mutableDeviceItemClick.tryEmit(
- DeviceItemClick(item, it, DeviceItemClick.Target.ACTION_ICON)
- )
- }
- }
- }
- }
-
- internal companion object {
- const val MIN_HEIGHT_CHANGE_INTERVAL_MS = 800L
- const val ACTION_BLUETOOTH_DEVICE_DETAILS =
- "com.android.settings.BLUETOOTH_DEVICE_DETAIL_SETTINGS"
- const val ACTION_PREVIOUSLY_CONNECTED_DEVICE =
- "com.android.settings.PREVIOUSLY_CONNECTED_DEVICE"
- const val ACTION_PAIR_NEW_DEVICE = "android.settings.BLUETOOTH_PAIRING_SETTINGS"
- const val ACTION_AUDIO_SHARING = "com.android.settings.BLUETOOTH_AUDIO_SHARING_SETTINGS"
- const val DISABLED_ALPHA = 0.3f
- const val ENABLED_ALPHA = 1f
- const val PROGRESS_BAR_ANIMATION_DURATION_MS = 1500L
-
- private fun Boolean.toInt(): Int {
- return if (this) 1 else 0
- }
+ contentManager.releaseView()
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModel.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModel.kt
index bf04897f6d10..9492abbeb087 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModel.kt
@@ -16,6 +16,7 @@
package com.android.systemui.bluetooth.qsdialog
+import android.content.Context
import android.content.Intent
import android.content.SharedPreferences
import android.os.Bundle
@@ -34,15 +35,16 @@ import com.android.systemui.Prefs
import com.android.systemui.animation.DialogCuj
import com.android.systemui.animation.DialogTransitionAnimator
import com.android.systemui.animation.Expandable
-import com.android.systemui.bluetooth.qsdialog.BluetoothTileDialogDelegate.Companion.ACTION_AUDIO_SHARING
-import com.android.systemui.bluetooth.qsdialog.BluetoothTileDialogDelegate.Companion.ACTION_PAIR_NEW_DEVICE
-import com.android.systemui.bluetooth.qsdialog.BluetoothTileDialogDelegate.Companion.ACTION_PREVIOUSLY_CONNECTED_DEVICE
+import com.android.systemui.bluetooth.qsdialog.BluetoothDetailsContentManager.Companion.ACTION_AUDIO_SHARING
+import com.android.systemui.bluetooth.qsdialog.BluetoothDetailsContentManager.Companion.ACTION_PAIR_NEW_DEVICE
+import com.android.systemui.bluetooth.qsdialog.BluetoothDetailsContentManager.Companion.ACTION_PREVIOUSLY_CONNECTED_DEVICE
import com.android.systemui.dagger.SysUISingleton
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.plugins.ActivityStarter
import com.android.systemui.res.R
+import com.android.systemui.statusbar.phone.SystemUIDialog
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
@@ -57,7 +59,12 @@ import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.withContext
-/** ViewModel for Bluetooth Dialog after clicking on the Bluetooth QS tile. */
+/**
+ * ViewModel for Bluetooth Dialog or Bluetooth Details View after clicking on the Bluetooth QS tile.
+ *
+ * TODO: b/378513956 Rename this class to BluetoothDetailsContentViewModel, since it's not only used
+ * by the dialog view.
+ */
@SysUISingleton
internal class BluetoothTileDialogViewModel
@Inject
@@ -78,36 +85,61 @@ constructor(
@Background private val backgroundDispatcher: CoroutineDispatcher,
@Main private val sharedPreferences: SharedPreferences,
private val bluetoothDialogDelegateFactory: BluetoothTileDialogDelegate.Factory,
+ private val bluetoothDetailsContentManagerFactory: BluetoothDetailsContentManager.Factory,
) : BluetoothTileDialogCallback {
+ lateinit var contentManager: BluetoothDetailsContentManager
private var job: Job? = null
/**
- * Shows the dialog.
+ * Shows the details content.
*
- * @param view The view from which the dialog is shown.
+ * @param view The view from which the dialog is shown. If view is null, it should show the
+ * bluetooth tile details view.
+ *
+ * TODO: b/378513956 Refactor this method into 2. One is called by the dialog to show the
+ * dialog, another is called by the details view model to bind the view.
*/
- fun showDialog(expandable: Expandable?) {
+ fun showDetailsContent(expandable: Expandable?, view: View?) {
cancelJob()
job =
coroutineScope.launch(context = mainDispatcher) {
var updateDeviceItemJob: Job?
var updateDialogUiJob: Job? = null
- val dialogDelegate = createBluetoothTileDialog()
- val dialog = dialogDelegate.createDialog()
- val context = dialog.context
-
- val controller =
- expandable?.dialogTransitionController(
- DialogCuj(
- InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN,
- INTERACTION_JANK_TAG,
+ val dialog: SystemUIDialog?
+ val context: Context
+
+ if (view == null) {
+ // Render with dialog
+ val dialogDelegate = createBluetoothTileDialog()
+ dialog = dialogDelegate.createDialog()
+ context = dialog.context
+
+ val controller =
+ expandable?.dialogTransitionController(
+ DialogCuj(
+ InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN,
+ INTERACTION_JANK_TAG,
+ )
)
- )
- controller?.let {
- dialogTransitionAnimator.show(dialog, it, animateBackgroundBoundsChange = true)
- } ?: dialog.show()
+ controller?.let {
+ dialogTransitionAnimator.show(
+ dialog,
+ it,
+ animateBackgroundBoundsChange = true,
+ )
+ } ?: dialog.show()
+ // contentManager is created after dialog.show
+ contentManager = dialogDelegate.contentManager
+ } else {
+ // Render with tile details view
+ dialog = null
+ context = view.context
+ contentManager = createContentManager()
+ contentManager.bind(view)
+ contentManager.start()
+ }
updateDeviceItemJob = launch {
deviceItemInteractor.updateDeviceItems(context, DeviceFetchTrigger.FIRST_LOAD)
@@ -121,15 +153,14 @@ constructor(
) { deviceItem, showSeeAll ->
updateDialogUiJob?.cancel()
updateDialogUiJob = launch {
- dialogDelegate.apply {
+ contentManager.apply {
onDeviceItemUpdated(
- dialog,
deviceItem,
showSeeAll,
showPairNewDevice =
bluetoothStateInteractor.isBluetoothEnabled(),
)
- animateProgressBar(dialog, false)
+ animateProgressBar(false)
}
}
}
@@ -150,7 +181,7 @@ constructor(
},
)
.onEach {
- dialogDelegate.animateProgressBar(dialog, true)
+ contentManager.animateProgressBar(true)
updateDeviceItemJob?.cancel()
updateDeviceItemJob = launch {
deviceItemInteractor.updateDeviceItems(
@@ -171,16 +202,14 @@ constructor(
.onEach {
when (it) {
is AudioSharingButtonState.Visible -> {
- dialogDelegate.onAudioSharingButtonUpdated(
- dialog,
+ contentManager.onAudioSharingButtonUpdated(
VISIBLE,
context.getString(it.resId),
it.isActive,
)
}
is AudioSharingButtonState.Gone -> {
- dialogDelegate.onAudioSharingButtonUpdated(
- dialog,
+ contentManager.onAudioSharingButtonUpdated(
GONE,
label = null,
isActive = false,
@@ -197,8 +226,7 @@ constructor(
// the device item list.
bluetoothStateInteractor.bluetoothStateUpdate
.onEach {
- dialogDelegate.onBluetoothStateUpdated(
- dialog,
+ contentManager.onBluetoothStateUpdated(
it,
UiProperties.build(it, isAutoOnToggleFeatureAvailable()),
)
@@ -214,16 +242,17 @@ constructor(
// bluetoothStateToggle is emitted when user toggles the bluetooth state switch,
// send the new value to the bluetoothStateInteractor and animate the progress bar.
- dialogDelegate.bluetoothStateToggle
+ contentManager.bluetoothStateToggle
.filterNotNull()
.onEach {
- dialogDelegate.animateProgressBar(dialog, true)
+ contentManager.animateProgressBar(true)
bluetoothStateInteractor.setBluetoothEnabled(it)
}
.launchIn(this)
// deviceItemClick is emitted when user clicked on a device item.
- dialogDelegate.deviceItemClick
+ contentManager.deviceItemClick
+ .filterNotNull()
.onEach {
when (it.target) {
DeviceItemClick.Target.ENTIRE_ROW -> {
@@ -245,7 +274,8 @@ constructor(
.launchIn(this)
// contentHeight is emitted when the dialog is dismissed.
- dialogDelegate.contentHeight
+ contentManager.contentHeight
+ .filterNotNull()
.onEach {
withContext(backgroundDispatcher) {
sharedPreferences.edit().putInt(CONTENT_HEIGHT_PREF_KEY, it).apply()
@@ -258,8 +288,7 @@ constructor(
// changed.
bluetoothAutoOnInteractor.isEnabled
.onEach {
- dialogDelegate.onBluetoothAutoOnUpdated(
- dialog,
+ contentManager.onBluetoothAutoOnUpdated(
it,
if (it) R.string.turn_on_bluetooth_auto_info_enabled
else R.string.turn_on_bluetooth_auto_info_disabled,
@@ -269,36 +298,48 @@ constructor(
// bluetoothAutoOnToggle is emitted when user toggles the bluetooth auto on
// switch, send the new value to the bluetoothAutoOnInteractor.
- dialogDelegate.bluetoothAutoOnToggle
+ contentManager.bluetoothAutoOnToggle
.filterNotNull()
.onEach { bluetoothAutoOnInteractor.setEnabled(it) }
.launchIn(this)
}
- produce<Unit> { awaitClose { dialog.cancel() } }
+ produce<Unit> { awaitClose { dialog?.cancel() } }
}
}
private suspend fun createBluetoothTileDialog(): BluetoothTileDialogDelegate {
- val cachedContentHeight =
- withContext(backgroundDispatcher) {
- sharedPreferences.getInt(
- CONTENT_HEIGHT_PREF_KEY,
- ViewGroup.LayoutParams.WRAP_CONTENT,
- )
- }
-
return bluetoothDialogDelegateFactory.create(
- UiProperties.build(
- bluetoothStateInteractor.isBluetoothEnabled(),
- isAutoOnToggleFeatureAvailable(),
- ),
- cachedContentHeight,
+ getUiProperties(),
+ getCachedContentHeight(),
this@BluetoothTileDialogViewModel,
{ cancelJob() },
)
}
+ private suspend fun createContentManager(): BluetoothDetailsContentManager {
+ return bluetoothDetailsContentManagerFactory.create(
+ getUiProperties(),
+ getCachedContentHeight(),
+ this@BluetoothTileDialogViewModel,
+ /* isInDialog= */ false,
+ /* doneButtonCallback= */ fun() {},
+ )
+ }
+
+ private suspend fun getUiProperties(): UiProperties {
+ return UiProperties.build(
+ bluetoothStateInteractor.isBluetoothEnabled(),
+ isAutoOnToggleFeatureAvailable(),
+ )
+ }
+
+ private suspend fun getCachedContentHeight(): Int {
+ return withContext(backgroundDispatcher) {
+ sharedPreferences.getInt(CONTENT_HEIGHT_PREF_KEY, ViewGroup.LayoutParams.WRAP_CONTENT)
+ }
+ }
+
override fun onSeeAllClicked(view: View) {
uiEventLogger.log(BluetoothTileDialogUiEvent.SEE_ALL_CLICKED)
startSettingsActivity(Intent(ACTION_PREVIOUSLY_CONNECTED_DEVICE), view)
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemActionInteractor.kt
index cb4ec37a1a66..26996ac1db39 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemActionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemActionInteractor.kt
@@ -27,7 +27,7 @@ import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.withContext
interface DeviceItemActionInteractor {
- suspend fun onClick(deviceItem: DeviceItem, dialog: SystemUIDialog)
+ suspend fun onClick(deviceItem: DeviceItem, dialog: SystemUIDialog?) {}
suspend fun onActionIconClick(deviceItem: DeviceItem, onIntent: (Intent) -> Unit)
}
@@ -40,7 +40,7 @@ constructor(
private val uiEventLogger: UiEventLogger,
) : DeviceItemActionInteractor {
- override suspend fun onClick(deviceItem: DeviceItem, dialog: SystemUIDialog) {
+ override suspend fun onClick(deviceItem: DeviceItem, dialog: SystemUIDialog?) {
withContext(backgroundDispatcher) {
deviceItem.cachedBluetoothDevice.apply {
when (deviceItem.type) {
diff --git a/packages/SystemUI/src/com/android/systemui/brightness/ui/compose/BrightnessSlider.kt b/packages/SystemUI/src/com/android/systemui/brightness/ui/compose/BrightnessSlider.kt
index 6f2a2c4ccaaa..b13f6df3f4f5 100644
--- a/packages/SystemUI/src/com/android/systemui/brightness/ui/compose/BrightnessSlider.kt
+++ b/packages/SystemUI/src/com/android/systemui/brightness/ui/compose/BrightnessSlider.kt
@@ -16,49 +16,75 @@
package com.android.systemui.brightness.ui.compose
+import android.content.Context
import android.view.MotionEvent
+import androidx.annotation.VisibleForTesting
+import androidx.compose.animation.core.Animatable
+import androidx.compose.animation.core.AnimationVector1D
+import androidx.compose.animation.core.VectorConverter
import androidx.compose.animation.core.animateFloatAsState
+import androidx.compose.animation.core.tween
import androidx.compose.foundation.clickable
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.interaction.DragInteraction
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.height
import androidx.compose.foundation.shape.CornerSize
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.Text
+import androidx.compose.material3.Slider
+import androidx.compose.material3.SliderDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.produceState
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.drawWithCache
+import androidx.compose.ui.draw.drawWithContent
import androidx.compose.ui.geometry.CornerRadius
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.ColorFilter
+import androidx.compose.ui.graphics.asImageBitmap
+import androidx.compose.ui.graphics.drawscope.DrawScope
+import androidx.compose.ui.graphics.drawscope.translate
+import androidx.compose.ui.graphics.painter.BitmapPainter
+import androidx.compose.ui.graphics.painter.ColorPainter
+import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.input.pointer.pointerInteropFilter
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.colorResource
-import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.unit.DpSize
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.app.tracing.coroutines.launchTraced as launch
-import com.android.compose.PlatformSlider
import com.android.compose.ui.graphics.drawInOverlay
import com.android.systemui.Flags
+import com.android.systemui.biometrics.Utils.toBitmap
import com.android.systemui.brightness.shared.model.GammaBrightness
+import com.android.systemui.brightness.ui.compose.AnimationSpecs.IconAppearSpec
+import com.android.systemui.brightness.ui.compose.AnimationSpecs.IconDisappearSpec
+import com.android.systemui.brightness.ui.compose.Dimensions.IconPadding
+import com.android.systemui.brightness.ui.compose.Dimensions.IconSize
+import com.android.systemui.brightness.ui.compose.Dimensions.SliderBackgroundFrameSize
+import com.android.systemui.brightness.ui.compose.Dimensions.SliderBackgroundRoundedCorner
+import com.android.systemui.brightness.ui.compose.Dimensions.SliderTrackRoundedCorner
+import com.android.systemui.brightness.ui.compose.Dimensions.ThumbTrackGapSize
import com.android.systemui.brightness.ui.viewmodel.BrightnessSliderViewModel
import com.android.systemui.brightness.ui.viewmodel.Drag
import com.android.systemui.common.shared.model.Icon
-import com.android.systemui.common.shared.model.Text
-import com.android.systemui.common.ui.compose.Icon
import com.android.systemui.compose.modifiers.sysuiResTag
import com.android.systemui.haptics.slider.SeekableSliderTrackerConfig
import com.android.systemui.haptics.slider.SliderHapticFeedbackConfig
@@ -67,27 +93,32 @@ import com.android.systemui.lifecycle.rememberViewModel
import com.android.systemui.qs.ui.compose.borderOnFocus
import com.android.systemui.res.R
import com.android.systemui.utils.PolicyRestriction
+import platform.test.motion.compose.values.MotionTestValueKey
+import platform.test.motion.compose.values.motionTestValues
+@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class)
@Composable
-private fun BrightnessSlider(
- viewModel: BrightnessSliderViewModel,
+@VisibleForTesting
+fun BrightnessSlider(
gammaValue: Int,
valueRange: IntRange,
- label: Text.Resource,
- icon: Icon,
+ iconResProvider: (Float) -> Int,
+ imageLoader: suspend (Int, Context) -> Icon.Loaded,
restriction: PolicyRestriction,
onRestrictedClick: (PolicyRestriction.Restricted) -> Unit,
onDrag: (Int) -> Unit,
onStop: (Int) -> Unit,
+ overriddenByAppState: Boolean,
modifier: Modifier = Modifier,
- formatter: (Int) -> String = { "$it" },
+ showToast: () -> Unit = {},
hapticsViewModelFactory: SliderHapticsViewModel.Factory,
) {
var value by remember(gammaValue) { mutableIntStateOf(gammaValue) }
val animatedValue by
animateFloatAsState(targetValue = value.toFloat(), label = "BrightnessSliderAnimatedValue")
val floatValueRange = valueRange.first.toFloat()..valueRange.last.toFloat()
- val isRestricted = remember(restriction) { restriction is PolicyRestriction.Restricted }
+ val isRestricted = restriction is PolicyRestriction.Restricted
+ val enabled = !isRestricted
val interactionSource = remember { MutableInteractionSource() }
val hapticsViewModel: SliderHapticsViewModel? =
if (Flags.hapticsForComposeSliders()) {
@@ -105,20 +136,56 @@ private fun BrightnessSlider(
} else {
null
}
+ val colors = SliderDefaults.colors()
- val overriddenByAppState by
- if (Flags.showToastWhenAppControlBrightness()) {
- viewModel.brightnessOverriddenByWindow.collectAsStateWithLifecycle()
- } else {
- remember { mutableStateOf(false) }
+ // The value state is recreated every time gammaValue changes, so we recreate this derivedState
+ // We have to use value as that's the value that changes when the user is dragging (gammaValue
+ // is always the starting value: actual (not temporary) brightness).
+ val iconRes by
+ remember(gammaValue, valueRange) {
+ derivedStateOf {
+ val percentage =
+ (value - valueRange.first) * 100f / (valueRange.last - valueRange.first)
+ iconResProvider(percentage)
+ }
+ }
+ val context = LocalContext.current
+ val painter: Painter by
+ produceState<Painter>(
+ initialValue = ColorPainter(Color.Transparent),
+ key1 = iconRes,
+ key2 = context,
+ ) {
+ val icon = imageLoader(iconRes, context)
+ // toBitmap is Drawable?.() -> Bitmap? and handles null internally.
+ val bitmap = icon.drawable.toBitmap()!!.asImageBitmap()
+ this@produceState.value = BitmapPainter(bitmap)
+ }
+
+ val activeIconColor = colors.activeTickColor
+ val inactiveIconColor = colors.inactiveTickColor
+ val trackIcon: DrawScope.(Offset, Color, Float) -> Unit =
+ remember(painter) {
+ { offset, color, alpha ->
+ translate(offset.x + IconPadding.toPx(), offset.y) {
+ with(painter) {
+ draw(
+ IconSize.toSize(),
+ colorFilter = ColorFilter.tint(color),
+ alpha = alpha,
+ )
+ }
+ }
+ }
}
- PlatformSlider(
+ Slider(
value = animatedValue,
valueRange = floatValueRange,
- enabled = !isRestricted,
+ enabled = enabled,
+ colors = colors,
onValueChange = {
- if (!isRestricted) {
+ if (enabled) {
if (!overriddenByAppState) {
hapticsViewModel?.onValueChange(it)
value = it.toInt()
@@ -127,7 +194,7 @@ private fun BrightnessSlider(
}
},
onValueChangeFinished = {
- if (!isRestricted) {
+ if (enabled) {
if (!overriddenByAppState) {
hapticsViewModel?.onValueChangeEnded()
onStop(value)
@@ -140,46 +207,117 @@ private fun BrightnessSlider(
onRestrictedClick(restriction)
}
},
- icon = { isDragging ->
- if (isDragging) {
- Text(text = formatter(value))
- } else {
- Icon(modifier = Modifier.size(24.dp), icon = icon)
- }
+ interactionSource = interactionSource,
+ thumb = {
+ SliderDefaults.Thumb(
+ interactionSource = interactionSource,
+ enabled = enabled,
+ thumbSize = DpSize(4.dp, 52.dp),
+ )
},
- label = {
- Text(
- text = stringResource(id = label.res),
- style = MaterialTheme.typography.titleMedium,
- maxLines = 1,
+ track = { sliderState ->
+ var showIconActive by remember { mutableStateOf(true) }
+ val iconActiveAlphaAnimatable = remember {
+ Animatable(
+ initialValue = 1f,
+ typeConverter = Float.VectorConverter,
+ label = "iconActiveAlpha",
+ )
+ }
+
+ val iconInactiveAlphaAnimatable = remember {
+ Animatable(
+ initialValue = 0f,
+ typeConverter = Float.VectorConverter,
+ label = "iconInactiveAlpha",
+ )
+ }
+
+ LaunchedEffect(iconActiveAlphaAnimatable, iconInactiveAlphaAnimatable, showIconActive) {
+ if (showIconActive) {
+ launch { iconActiveAlphaAnimatable.appear() }
+ launch { iconInactiveAlphaAnimatable.disappear() }
+ } else {
+ launch { iconActiveAlphaAnimatable.disappear() }
+ launch { iconInactiveAlphaAnimatable.appear() }
+ }
+ }
+
+ SliderDefaults.Track(
+ sliderState = sliderState,
+ modifier =
+ Modifier.motionTestValues {
+ (iconActiveAlphaAnimatable.isRunning ||
+ iconInactiveAlphaAnimatable.isRunning) exportAs
+ BrightnessSliderMotionTestKeys.AnimatingIcon
+
+ iconActiveAlphaAnimatable.value exportAs
+ BrightnessSliderMotionTestKeys.ActiveIconAlpha
+ iconInactiveAlphaAnimatable.value exportAs
+ BrightnessSliderMotionTestKeys.InactiveIconAlpha
+ }
+ .height(40.dp)
+ .drawWithContent {
+ drawContent()
+
+ val yOffset = size.height / 2 - IconSize.toSize().height / 2
+ val activeTrackStart = 0f
+ val activeTrackEnd =
+ size.width * sliderState.coercedValueAsFraction -
+ ThumbTrackGapSize.toPx()
+ val inactiveTrackStart = activeTrackEnd + ThumbTrackGapSize.toPx() * 2
+ val inactiveTrackEnd = size.width
+
+ val activeTrackWidth = activeTrackEnd - activeTrackStart
+ val inactiveTrackWidth = inactiveTrackEnd - inactiveTrackStart
+ if (
+ IconSize.toSize().width < activeTrackWidth - IconPadding.toPx() * 2
+ ) {
+ showIconActive = true
+ trackIcon(
+ Offset(activeTrackStart, yOffset),
+ activeIconColor,
+ iconActiveAlphaAnimatable.value,
+ )
+ } else if (
+ IconSize.toSize().width <
+ inactiveTrackWidth - IconPadding.toPx() * 2
+ ) {
+ showIconActive = false
+ trackIcon(
+ Offset(inactiveTrackStart, yOffset),
+ inactiveIconColor,
+ iconInactiveAlphaAnimatable.value,
+ )
+ }
+ },
+ trackCornerSize = SliderTrackRoundedCorner,
+ trackInsideCornerSize = 2.dp,
+ drawStopIndicator = null,
+ thumbTrackGapSize = ThumbTrackGapSize,
)
},
- interactionSource = interactionSource,
)
+
+ val currentShowToast by rememberUpdatedState(showToast)
// Showing the warning toast if the current running app window has controlled the
// brightness value.
if (Flags.showToastWhenAppControlBrightness()) {
- val context = LocalContext.current
LaunchedEffect(interactionSource) {
interactionSource.interactions.collect { interaction ->
if (interaction is DragInteraction.Start && overriddenByAppState) {
- viewModel.showToast(
- context,
- R.string.quick_settings_brightness_unable_adjust_msg,
- )
+ currentShowToast()
}
}
}
}
}
-private val sliderBackgroundFrameSize = 8.dp
-
private fun Modifier.sliderBackground(color: Color) = drawWithCache {
- val offsetAround = sliderBackgroundFrameSize.toPx()
- val newSize = Size(size.width + 2 * offsetAround, size.height + 2 * offsetAround)
- val offset = Offset(-offsetAround, -offsetAround)
- val cornerRadius = CornerRadius(offsetAround + size.height / 2)
+ val offsetAround = SliderBackgroundFrameSize.toSize()
+ val newSize = Size(size.width + 2 * offsetAround.width, size.height + 2 * offsetAround.height)
+ val offset = Offset(-offsetAround.width, -offsetAround.height)
+ val cornerRadius = CornerRadius(SliderBackgroundRoundedCorner.toPx())
onDrawBehind {
drawRoundRect(color = color, topLeft = offset, size = newSize, cornerRadius = cornerRadius)
}
@@ -192,21 +330,30 @@ fun BrightnessSliderContainer(
containerColor: Color = colorResource(R.color.shade_scrim_background_dark),
) {
val gamma = viewModel.currentBrightness.value
+ if (gamma == BrightnessSliderViewModel.initialValue.value) { // Ignore initial negative value.
+ return
+ }
+ val context = LocalContext.current
val coroutineScope = rememberCoroutineScope()
val restriction by
viewModel.policyRestriction.collectAsStateWithLifecycle(
initialValue = PolicyRestriction.NoRestriction
)
+ val overriddenByAppState by
+ if (Flags.showToastWhenAppControlBrightness()) {
+ viewModel.brightnessOverriddenByWindow.collectAsStateWithLifecycle()
+ } else {
+ remember { mutableStateOf(false) }
+ }
DisposableEffect(Unit) { onDispose { viewModel.setIsDragging(false) } }
Box(modifier = modifier.fillMaxWidth().sysuiResTag("brightness_slider")) {
BrightnessSlider(
- viewModel = viewModel,
gammaValue = gamma,
valueRange = viewModel.minBrightness.value..viewModel.maxBrightness.value,
- label = viewModel.label,
- icon = viewModel.icon,
+ iconResProvider = BrightnessSliderViewModel::getIconForPercentage,
+ imageLoader = viewModel::loadImage,
restriction = restriction,
onRestrictedClick = viewModel::showPolicyRestrictionDialog,
onDrag = {
@@ -220,7 +367,7 @@ fun BrightnessSliderContainer(
modifier =
Modifier.borderOnFocus(
color = MaterialTheme.colorScheme.secondary,
- cornerSize = CornerSize(32.dp),
+ cornerSize = CornerSize(SliderTrackRoundedCorner),
)
.then(if (viewModel.showMirror) Modifier.drawInOverlay() else Modifier)
.sliderBackground(containerColor)
@@ -234,8 +381,38 @@ fun BrightnessSliderContainer(
}
false
},
- formatter = viewModel::formatValue,
hapticsViewModelFactory = viewModel.hapticsViewModelFactory,
+ overriddenByAppState = overriddenByAppState,
+ showToast = {
+ viewModel.showToast(context, R.string.quick_settings_brightness_unable_adjust_msg)
+ },
)
}
}
+
+private object Dimensions {
+ val SliderBackgroundFrameSize = DpSize(10.dp, 6.dp)
+ val SliderBackgroundRoundedCorner = 24.dp
+ val SliderTrackRoundedCorner = 12.dp
+ val IconSize = DpSize(28.dp, 28.dp)
+ val IconPadding = 6.dp
+ val ThumbTrackGapSize = 6.dp
+}
+
+private object AnimationSpecs {
+ val IconAppearSpec = tween<Float>(durationMillis = 100, delayMillis = 33)
+ val IconDisappearSpec = tween<Float>(durationMillis = 50)
+}
+
+private suspend fun Animatable<Float, AnimationVector1D>.appear() =
+ animateTo(targetValue = 1f, animationSpec = IconAppearSpec)
+
+private suspend fun Animatable<Float, AnimationVector1D>.disappear() =
+ animateTo(targetValue = 0f, animationSpec = IconDisappearSpec)
+
+@VisibleForTesting
+object BrightnessSliderMotionTestKeys {
+ val AnimatingIcon = MotionTestValueKey<Boolean>("animatingIcon")
+ val ActiveIconAlpha = MotionTestValueKey<Float>("activeIconAlpha")
+ val InactiveIconAlpha = MotionTestValueKey<Float>("inactiveIconAlpha")
+}
diff --git a/packages/SystemUI/src/com/android/systemui/brightness/ui/viewmodel/BrightnessSliderViewModel.kt b/packages/SystemUI/src/com/android/systemui/brightness/ui/viewmodel/BrightnessSliderViewModel.kt
index 7df71550d43d..ed1cef3fccaf 100644
--- a/packages/SystemUI/src/com/android/systemui/brightness/ui/viewmodel/BrightnessSliderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/brightness/ui/viewmodel/BrightnessSliderViewModel.kt
@@ -17,6 +17,8 @@
package com.android.systemui.brightness.ui.viewmodel
import android.content.Context
+import androidx.annotation.DrawableRes
+import androidx.annotation.FloatRange
import androidx.annotation.StringRes
import androidx.compose.runtime.getValue
import com.android.systemui.brightness.domain.interactor.BrightnessPolicyEnforcementInteractor
@@ -24,9 +26,9 @@ import com.android.systemui.brightness.domain.interactor.ScreenBrightnessInterac
import com.android.systemui.brightness.shared.model.GammaBrightness
import com.android.systemui.classifier.Classifier
import com.android.systemui.classifier.domain.interactor.FalsingInteractor
-import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.Icon
-import com.android.systemui.common.shared.model.Text
+import com.android.systemui.common.shared.model.asIcon
+import com.android.systemui.graphics.ImageLoader
import com.android.systemui.haptics.slider.compose.ui.SliderHapticsViewModel
import com.android.systemui.lifecycle.ExclusiveActivatable
import com.android.systemui.lifecycle.Hydrator
@@ -57,6 +59,7 @@ constructor(
private val falsingInteractor: FalsingInteractor,
@Assisted private val supportsMirroring: Boolean,
private val brightnessWarningToast: BrightnessWarningToast,
+ private val imageLoader: ImageLoader,
) : ExclusiveActivatable() {
private val hydrator = Hydrator("BrightnessSliderViewModel.hydrator")
@@ -64,17 +67,13 @@ constructor(
val currentBrightness by
hydrator.hydratedStateOf(
"currentBrightness",
- GammaBrightness(0),
+ initialValue,
screenBrightnessInteractor.gammaBrightness,
)
val maxBrightness = screenBrightnessInteractor.maxGammaBrightness
val minBrightness = screenBrightnessInteractor.minGammaBrightness
- val label = Text.Resource(R.string.quick_settings_brightness_dialog_title)
-
- val icon = Icon.Resource(R.drawable.ic_brightness_full, ContentDescription.Resource(label.res))
-
val policyRestriction = brightnessPolicyEnforcementInteractor.brightnessPolicyRestriction
fun showPolicyRestrictionDialog(restriction: PolicyRestriction.Restricted) {
@@ -94,6 +93,16 @@ constructor(
falsingInteractor.isFalseTouch(Classifier.BRIGHTNESS_SLIDER)
}
+ suspend fun loadImage(@DrawableRes resId: Int, context: Context): Icon.Loaded {
+ return imageLoader
+ .loadDrawable(
+ android.graphics.drawable.Icon.createWithResource(context, resId),
+ maxHeight = 200,
+ maxWidth = 200,
+ )!!
+ .asIcon(null, resId)
+ }
+
/**
* As a brightness slider is dragged, the corresponding events should be sent using this method.
*/
@@ -104,18 +113,6 @@ constructor(
}
}
- /**
- * Format the current value of brightness as a percentage between the minimum and maximum gamma.
- */
- fun formatValue(value: Int): String {
- val min = minBrightness.value
- val max = maxBrightness.value
- val coercedValue = value.coerceIn(min, max)
- val percentage = (coercedValue - min) * 100 / (max - min)
- // This is not finalized UI so using fixed string
- return "$percentage%"
- }
-
fun setIsDragging(dragging: Boolean) {
brightnessMirrorShowingInteractor.setMirrorShowing(dragging && supportsMirroring)
}
@@ -131,6 +128,26 @@ constructor(
interface Factory {
fun create(supportsMirroring: Boolean): BrightnessSliderViewModel
}
+
+ companion object {
+ val initialValue = GammaBrightness(-1)
+
+ private val icons =
+ BrightnessIcons(
+ brightnessLow = R.drawable.ic_brightness_low,
+ brightnessMid = R.drawable.ic_brightness_medium,
+ brightnessHigh = R.drawable.ic_brightness_full,
+ )
+
+ @DrawableRes
+ fun getIconForPercentage(@FloatRange(0.0, 100.0) percentage: Float): Int {
+ return when {
+ percentage <= 20f -> icons.brightnessLow
+ percentage >= 80f -> icons.brightnessHigh
+ else -> icons.brightnessMid
+ }
+ }
+ }
}
fun BrightnessSliderViewModel.Factory.create() = create(supportsMirroring = true)
@@ -143,3 +160,9 @@ sealed interface Drag {
@JvmInline value class Stopped(override val brightness: GammaBrightness) : Drag
}
+
+private data class BrightnessIcons(
+ @DrawableRes val brightnessLow: Int,
+ @DrawableRes val brightnessMid: Int,
+ @DrawableRes val brightnessHigh: Int,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSceneRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSceneRepository.kt
index 643d185f1939..8b6322720118 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSceneRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSceneRepository.kt
@@ -16,7 +16,9 @@
package com.android.systemui.communal.data.repository
+import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.compose.animation.scene.ObservableTransitionState
+import com.android.compose.animation.scene.OverlayKey
import com.android.compose.animation.scene.SceneKey
import com.android.compose.animation.scene.TransitionKey
import com.android.systemui.communal.dagger.Communal
@@ -25,16 +27,17 @@ import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.scene.shared.model.SceneDataSource
+import com.android.systemui.scene.shared.model.SceneDataSourceDelegator
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.stateIn
-import com.android.app.tracing.coroutines.launchTraced as launch
/** Encapsulates the state of communal mode. */
interface CommunalSceneRepository {
@@ -52,6 +55,9 @@ interface CommunalSceneRepository {
/** Immediately snaps to the desired scene. */
fun snapToScene(toScene: SceneKey)
+ /** Shows the hub from a power button press. */
+ suspend fun showHubFromPowerButton()
+
/**
* Updates the transition state of the hub [SceneTransitionLayout].
*
@@ -67,6 +73,7 @@ constructor(
@Application private val applicationScope: CoroutineScope,
@Background backgroundScope: CoroutineScope,
@Communal private val sceneDataSource: SceneDataSource,
+ @Communal private val delegator: SceneDataSourceDelegator,
) : CommunalSceneRepository {
override val currentScene: StateFlow<SceneKey> = sceneDataSource.currentScene
@@ -98,6 +105,18 @@ constructor(
}
}
+ override suspend fun showHubFromPowerButton() {
+ // If keyguard is not showing yet, the hub view is not ready and the
+ // [SceneDataSourceDelegator] will still be using the default [NoOpSceneDataSource]
+ // and initial key, which is Blank. This means that when the hub container loads, it
+ // will default to not showing the hub. Attempting to set the scene in this state
+ // is simply ignored by the [NoOpSceneDataSource]. Instead, we temporarily override
+ // it with a new one that defaults to Communal. This delegate will be overwritten
+ // once the [CommunalContainer] loads.
+ // TODO(b/392969914): show the hub first instead of forcing the scene.
+ delegator.setDelegate(NoOpSceneDataSource(CommunalScenes.Communal))
+ }
+
/**
* Updates the transition state of the hub [SceneTransitionLayout].
*
@@ -106,4 +125,27 @@ constructor(
override fun setTransitionState(transitionState: Flow<ObservableTransitionState>?) {
_transitionState.value = transitionState
}
+
+ /** Noop implementation of a scene data source that always returns the initial [SceneKey]. */
+ private class NoOpSceneDataSource(initialSceneKey: SceneKey) : SceneDataSource {
+ override val currentScene: StateFlow<SceneKey> =
+ MutableStateFlow(initialSceneKey).asStateFlow()
+
+ override val currentOverlays: StateFlow<Set<OverlayKey>> =
+ MutableStateFlow(emptySet<OverlayKey>()).asStateFlow()
+
+ override fun changeScene(toScene: SceneKey, transitionKey: TransitionKey?) = Unit
+
+ override fun snapToScene(toScene: SceneKey) = Unit
+
+ override fun showOverlay(overlay: OverlayKey, transitionKey: TransitionKey?) = Unit
+
+ override fun hideOverlay(overlay: OverlayKey, transitionKey: TransitionKey?) = Unit
+
+ override fun replaceOverlay(
+ from: OverlayKey,
+ to: OverlayKey,
+ transitionKey: TransitionKey?,
+ ) = Unit
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractor.kt
index 476493225857..3d9e93036dbc 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractor.kt
@@ -148,6 +148,29 @@ constructor(
}
}
+ fun showHubFromPowerButton() {
+ val loggingReason = "showing hub from power button"
+ applicationScope.launch("$TAG#showHubFromPowerButton") {
+ if (SceneContainerFlag.isEnabled) {
+ sceneInteractor.changeScene(
+ toScene = CommunalScenes.Communal.toSceneContainerSceneKey(),
+ loggingReason = loggingReason,
+ )
+ return@launch
+ }
+
+ if (currentScene.value == CommunalScenes.Communal) return@launch
+ logger.logSceneChangeRequested(
+ from = currentScene.value,
+ to = CommunalScenes.Communal,
+ reason = loggingReason,
+ isInstant = true,
+ )
+ notifyListeners(CommunalScenes.Communal, null)
+ repository.showHubFromPowerButton()
+ }
+ }
+
private fun notifyListeners(newScene: SceneKey, keyguardState: KeyguardState?) {
onSceneAboutToChangeListener.forEach { it.onSceneAboutToChange(newScene, keyguardState) }
}
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 099a85926020..49003a735fbd 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
@@ -45,7 +45,7 @@ abstract class BaseCommunalViewModel(
val mediaHost: MediaHost,
val mediaCarouselController: MediaCarouselController,
) {
- val currentScene: Flow<SceneKey> = communalSceneInteractor.currentScene
+ val currentScene: StateFlow<SceneKey> = communalSceneInteractor.currentScene
/** Used to animate showing or hiding the communal content. */
open val isCommunalContentVisible: Flow<Boolean> = MutableStateFlow(false)
diff --git a/packages/SystemUI/src/com/android/systemui/complication/DreamClockTimeComplication.java b/packages/SystemUI/src/com/android/systemui/complication/DreamClockTimeComplication.java
index c709e3436cd6..8e6848a87c7d 100644
--- a/packages/SystemUI/src/com/android/systemui/complication/DreamClockTimeComplication.java
+++ b/packages/SystemUI/src/com/android/systemui/complication/DreamClockTimeComplication.java
@@ -16,10 +16,13 @@
package com.android.systemui.complication;
+import static android.text.format.DateFormat.getBestDateTimePattern;
+
import static com.android.systemui.complication.dagger.DreamClockTimeComplicationComponent.DreamClockTimeComplicationModule.DREAM_CLOCK_TIME_COMPLICATION_VIEW;
import static com.android.systemui.complication.dagger.RegisteredComplicationsModule.DREAM_CLOCK_TIME_COMPLICATION_LAYOUT_PARAMS;
import android.view.View;
+import android.widget.TextClock;
import com.android.internal.logging.UiEventLogger;
import com.android.systemui.CoreStartable;
@@ -92,18 +95,24 @@ public class DreamClockTimeComplication implements Complication {
* {@link ViewHolder} to contain value/logic associated with {@link DreamClockTimeComplication}.
*/
public static class DreamClockTimeViewHolder implements ViewHolder {
- private final View mView;
+ private final TextClock mView;
private final ComplicationLayoutParams mLayoutParams;
@Inject
DreamClockTimeViewHolder(
- @Named(DREAM_CLOCK_TIME_COMPLICATION_VIEW) View view,
+ @Named(DREAM_CLOCK_TIME_COMPLICATION_VIEW) TextClock view,
@Named(DREAM_CLOCK_TIME_COMPLICATION_LAYOUT_PARAMS)
ComplicationLayoutParams layoutParams,
DreamClockTimeViewController viewController) {
mView = view;
mLayoutParams = layoutParams;
viewController.init();
+
+ // Support localized AM/PM marker for 12h mode in content description.
+ String formatSkeleton = view.is24HourModeEnabled() ? "Hm" : "hm";
+ String pattern = getBestDateTimePattern(view.getTextLocale(), formatSkeleton);
+ view.setContentDescriptionFormat12Hour(pattern);
+ view.setContentDescriptionFormat24Hour(pattern);
}
@Override
@@ -122,7 +131,7 @@ public class DreamClockTimeComplication implements Complication {
@Inject
DreamClockTimeViewController(
- @Named(DREAM_CLOCK_TIME_COMPLICATION_VIEW) View view,
+ @Named(DREAM_CLOCK_TIME_COMPLICATION_VIEW) TextClock view,
UiEventLogger uiEventLogger) {
super(view);
diff --git a/packages/SystemUI/src/com/android/systemui/complication/dagger/DreamClockTimeComplicationComponent.kt b/packages/SystemUI/src/com/android/systemui/complication/dagger/DreamClockTimeComplicationComponent.kt
index 4b9ac1d58b57..9d367c91c2c3 100644
--- a/packages/SystemUI/src/com/android/systemui/complication/dagger/DreamClockTimeComplicationComponent.kt
+++ b/packages/SystemUI/src/com/android/systemui/complication/dagger/DreamClockTimeComplicationComponent.kt
@@ -18,7 +18,6 @@
package com.android.systemui.complication.dagger
import android.view.LayoutInflater
-import android.view.View
import android.widget.TextClock
import com.android.internal.util.Preconditions
import com.android.systemui.Flags
@@ -64,7 +63,7 @@ interface DreamClockTimeComplicationComponent {
@Provides
@DreamClockTimeComplicationScope
@Named(DREAM_CLOCK_TIME_COMPLICATION_VIEW)
- fun provideComplicationView(layoutInflater: LayoutInflater): View {
+ fun provideComplicationView(layoutInflater: LayoutInflater): TextClock {
val view =
Preconditions.checkNotNull(
layoutInflater.inflate(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
index df3633be4625..869edfa2b886 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
@@ -29,12 +29,14 @@ import com.android.systemui.deviceentry.domain.interactor.DeviceEntryHapticsInte
import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
import com.android.systemui.keyguard.domain.interactor.WallpaperFocalAreaInteractor
import com.android.systemui.keyguard.ui.binder.KeyguardBlueprintViewBinder
+import com.android.systemui.keyguard.ui.binder.KeyguardJankBinder
import com.android.systemui.keyguard.ui.binder.KeyguardRootViewBinder
import com.android.systemui.keyguard.ui.binder.LightRevealScrimViewBinder
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.KeyguardJankViewModel
import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel
import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel
import com.android.systemui.keyguard.ui.viewmodel.LightRevealScrimViewModel
@@ -65,6 +67,7 @@ class KeyguardViewConfigurator
constructor(
private val keyguardRootView: KeyguardRootView,
private val keyguardRootViewModel: KeyguardRootViewModel,
+ private val keyguardJankViewModel: KeyguardJankViewModel,
private val screenOffAnimationController: ScreenOffAnimationController,
private val occludingAppDeviceEntryMessageViewModel: OccludingAppDeviceEntryMessageViewModel,
private val chipbarCoordinator: ChipbarCoordinator,
@@ -93,9 +96,11 @@ constructor(
) : CoreStartable {
private var rootViewHandle: DisposableHandle? = null
+ private var jankHandle: DisposableHandle? = null
override fun start() {
bindKeyguardRootView()
+ bindJankViewModel()
initializeViews()
if (lightRevealMigration()) {
@@ -145,11 +150,9 @@ constructor(
clockInteractor,
wallpaperFocalAreaInteractor,
keyguardClockViewModel,
- interactionJankMonitor,
deviceEntryHapticsInteractor,
vibratorHelper,
falsingManager,
- keyguardViewMediator,
statusBarKeyguardViewManager,
mainDispatcher,
msdlPlayer,
@@ -157,5 +160,22 @@ constructor(
)
}
+ private fun bindJankViewModel() {
+ if (SceneContainerFlag.isEnabled) {
+ return
+ }
+
+ jankHandle?.dispose()
+ jankHandle =
+ KeyguardJankBinder.bind(
+ keyguardRootView,
+ keyguardJankViewModel,
+ interactionJankMonitor,
+ clockInteractor,
+ keyguardViewMediator,
+ mainDispatcher,
+ )
+ }
+
fun getKeyguardRootView() = keyguardRootView
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 7fed7d253efe..fd50485fc3a3 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -3249,7 +3249,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
Log.d(TAG, "handleStartKeyguardExitAnimation startTime=" + startTime
+ " fadeoutDuration=" + fadeoutDuration);
int currentUserId = mSelectedUserInteractor.getSelectedUserId();
- if (mGoingAwayRequestedForUserId != currentUserId) {
+ if (!KeyguardWmStateRefactor.isEnabled() && mGoingAwayRequestedForUserId != currentUserId) {
Log.e(TAG, "Not executing handleStartKeyguardExitAnimationInner() due to userId "
+ "mismatch. Requested: " + mGoingAwayRequestedForUserId + ", current: "
+ currentUserId);
@@ -3516,7 +3516,8 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
* app transition before finishing the current RemoteAnimation, or the keyguard being re-shown).
*/
private void handleCancelKeyguardExitAnimation() {
- if (mGoingAwayRequestedForUserId != mSelectedUserInteractor.getSelectedUserId()) {
+ if (!KeyguardWmStateRefactor.isEnabled()
+ && mGoingAwayRequestedForUserId != mSelectedUserInteractor.getSelectedUserId()) {
Log.e(TAG, "Setting pendingLock = true due to userId mismatch. Requested: "
+ mGoingAwayRequestedForUserId + ", current: "
+ mSelectedUserInteractor.getSelectedUserId());
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt
index dd2bec143292..0f5f31302670 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt
@@ -55,6 +55,7 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filter
@@ -289,7 +290,7 @@ constructor(
}
private val areBiometricsEnabledForDeviceEntryFromUserSetting: Flow<Triple<Int, Boolean, Int>> =
- conflatedCallbackFlow {
+ callbackFlow {
val callback =
object : IBiometricEnabledOnKeyguardCallback.Stub() {
override fun onChanged(enabled: Boolean, userId: Int, modality: Int) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardJankBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardJankBinder.kt
new file mode 100644
index 000000000000..0cb684a1aabe
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardJankBinder.kt
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.binder
+
+import android.view.ViewGroup
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.app.tracing.coroutines.launchTraced as launch
+import com.android.internal.jank.Cuj.CUJ_LOCKSCREEN_TRANSITION_FROM_AOD
+import com.android.internal.jank.Cuj.CUJ_LOCKSCREEN_TRANSITION_TO_AOD
+import com.android.internal.jank.Cuj.CUJ_SCREEN_OFF_SHOW_AOD
+import com.android.internal.jank.InteractionJankMonitor
+import com.android.systemui.keyguard.KeyguardViewMediator
+import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardJankViewModel
+import com.android.systemui.lifecycle.repeatWhenAttached
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.DisposableHandle
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+/** Jank monitoring related to keyguard and transitions. */
+@OptIn(ExperimentalCoroutinesApi::class)
+object KeyguardJankBinder {
+ @JvmStatic
+ fun bind(
+ view: ViewGroup,
+ viewModel: KeyguardJankViewModel,
+ jankMonitor: InteractionJankMonitor?,
+ clockInteractor: KeyguardClockInteractor,
+ keyguardViewMediator: KeyguardViewMediator?,
+ mainImmediateDispatcher: CoroutineDispatcher,
+ ): DisposableHandle? {
+ if (jankMonitor == null) {
+ return null
+ }
+
+ fun processStep(step: TransitionStep, cuj: Int) {
+ val clockId = clockInteractor.renderedClockId
+ when (step.transitionState) {
+ TransitionState.STARTED -> {
+ val builder =
+ InteractionJankMonitor.Configuration.Builder.withView(cuj, view)
+ .setTag(clockId)
+ jankMonitor.begin(builder)
+ }
+
+ TransitionState.CANCELED -> jankMonitor.cancel(cuj)
+
+ TransitionState.FINISHED -> jankMonitor.end(cuj)
+
+ TransitionState.RUNNING -> Unit
+ }
+ }
+
+ return view.repeatWhenAttached(mainImmediateDispatcher) {
+ repeatOnLifecycle(Lifecycle.State.CREATED) {
+ launch {
+ viewModel.goneToAodTransition.collect {
+ processStep(it, CUJ_SCREEN_OFF_SHOW_AOD)
+ if (it.transitionState == TransitionState.FINISHED) {
+ keyguardViewMediator?.maybeHandlePendingLock()
+ }
+ }
+ }
+
+ launch {
+ viewModel.lockscreenToAodTransition.collect {
+ processStep(it, CUJ_LOCKSCREEN_TRANSITION_TO_AOD)
+ }
+ }
+
+ launch {
+ viewModel.aodToLockscreenTransition.collect {
+ processStep(it, CUJ_LOCKSCREEN_TRANSITION_FROM_AOD)
+ }
+ }
+ }
+ }
+ }
+}
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 017fe169ca88..e48af773497a 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
@@ -37,8 +37,6 @@ import androidx.activity.setViewTreeOnBackPressedDispatcherOwner
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
import com.android.app.tracing.coroutines.launchTraced as launch
-import com.android.internal.jank.InteractionJankMonitor
-import com.android.internal.jank.InteractionJankMonitor.CUJ_SCREEN_OFF_SHOW_AOD
import com.android.keyguard.AuthInteractionProperties
import com.android.systemui.Flags
import com.android.systemui.Flags.msdlFeedback
@@ -51,11 +49,9 @@ 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.KeyguardViewMediator
import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
import com.android.systemui.keyguard.domain.interactor.WallpaperFocalAreaInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
-import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.ui.view.layout.sections.AodPromotedNotificationSection
import com.android.systemui.keyguard.ui.viewmodel.BurnInParameters
import com.android.systemui.keyguard.ui.viewmodel.KeyguardBlueprintViewModel
@@ -110,11 +106,9 @@ object KeyguardRootViewBinder {
clockInteractor: KeyguardClockInteractor,
wallpaperFocalAreaInteractor: WallpaperFocalAreaInteractor,
clockViewModel: KeyguardClockViewModel,
- interactionJankMonitor: InteractionJankMonitor?,
deviceEntryHapticsInteractor: DeviceEntryHapticsInteractor?,
vibratorHelper: VibratorHelper?,
falsingManager: FalsingManager?,
- keyguardViewMediator: KeyguardViewMediator?,
statusBarKeyguardViewManager: StatusBarKeyguardViewManager?,
mainImmediateDispatcher: CoroutineDispatcher,
msdlPlayer: MSDLPlayer?,
@@ -308,35 +302,6 @@ object KeyguardRootViewBinder {
}
}
- interactionJankMonitor?.let { jankMonitor ->
- launch {
- viewModel.goneToAodTransition.collect {
- when (it.transitionState) {
- TransitionState.STARTED -> {
- val clockId = clockInteractor.renderedClockId
- val builder =
- InteractionJankMonitor.Configuration.Builder.withView(
- CUJ_SCREEN_OFF_SHOW_AOD,
- view,
- )
- .setTag(clockId)
- jankMonitor.begin(builder)
- }
-
- TransitionState.CANCELED ->
- jankMonitor.cancel(CUJ_SCREEN_OFF_SHOW_AOD)
-
- TransitionState.FINISHED -> {
- keyguardViewMediator?.maybeHandlePendingLock()
- jankMonitor.end(CUJ_SCREEN_OFF_SHOW_AOD)
- }
-
- TransitionState.RUNNING -> Unit
- }
- }
- }
- }
-
launch {
shadeInteractor.isAnyFullyExpanded.collect { isFullyAnyExpanded ->
view.visibility =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodPromotedNotificationSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodPromotedNotificationSection.kt
index ea4acce037b8..6c9a755148e9 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodPromotedNotificationSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodPromotedNotificationSection.kt
@@ -42,6 +42,10 @@ constructor(
) : KeyguardSection() {
var view: ComposeView? = null
+ init {
+ logger.logSectionCreated(this)
+ }
+
override fun addViews(constraintLayout: ConstraintLayout) {
if (!PromotedNotificationUiAod.isEnabled) {
return
@@ -56,7 +60,7 @@ constructor(
constraintLayout.addView(this)
}
- logger.logSectionAddedViews()
+ logger.logSectionAddedViews(this)
}
override fun bindData(constraintLayout: ConstraintLayout) {
@@ -68,7 +72,7 @@ constructor(
// Do nothing; the binding happens in the AODPromotedNotification Composable.
- logger.logSectionBoundData()
+ logger.logSectionBoundData(this)
}
override fun applyConstraints(constraintSet: ConstraintSet) {
@@ -76,7 +80,8 @@ constructor(
return
}
- checkNotNull(view)
+ // view may have been created by a different instance of the section (!), and we don't
+ // actually *need* it to set constraints, so don't check for it here.
constraintSet.apply {
val isShadeLayoutWide = shadeInteractor.isShadeLayoutWide.value
@@ -90,7 +95,7 @@ constructor(
constrainHeight(viewId, ConstraintSet.WRAP_CONTENT)
}
- logger.logSectionAppliedConstraints()
+ logger.logSectionAppliedConstraints(this)
}
override fun removeViews(constraintLayout: ConstraintLayout) {
@@ -102,7 +107,7 @@ constructor(
view = null
- logger.logSectionRemovedViews()
+ logger.logSectionRemovedViews(this)
}
companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardJankViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardJankViewModel.kt
new file mode 100644
index 000000000000..2642505b32cc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardJankViewModel.kt
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.Edge
+import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
+import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
+import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
+import com.android.systemui.scene.shared.model.Scenes
+import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SysUISingleton
+class KeyguardJankViewModel
+@Inject
+constructor(
+ private val keyguardInteractor: KeyguardInteractor,
+ private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
+) {
+ val goneToAodTransition =
+ keyguardTransitionInteractor.transition(
+ edge = Edge.create(Scenes.Gone, AOD),
+ edgeWithoutSceneContainer = Edge.create(GONE, AOD),
+ )
+
+ val lockscreenToAodTransition =
+ keyguardTransitionInteractor.transition(
+ edge = Edge.create(Scenes.Lockscreen, AOD),
+ edgeWithoutSceneContainer = Edge.create(LOCKSCREEN, AOD),
+ )
+
+ val aodToLockscreenTransition =
+ keyguardTransitionInteractor.transition(
+ edge = Edge.create(AOD, Scenes.Lockscreen),
+ edgeWithoutSceneContainer = Edge.create(AOD, LOCKSCREEN),
+ )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/lowlightclock/AmbientLightModeMonitor.kt b/packages/SystemUI/src/com/android/systemui/lowlightclock/AmbientLightModeMonitor.kt
index ece97bd27df7..9e32dd8d74ae 100644
--- a/packages/SystemUI/src/com/android/systemui/lowlightclock/AmbientLightModeMonitor.kt
+++ b/packages/SystemUI/src/com/android/systemui/lowlightclock/AmbientLightModeMonitor.kt
@@ -29,6 +29,7 @@ import java.io.PrintWriter
import java.util.Optional
import javax.inject.Inject
import javax.inject.Named
+import javax.inject.Provider
/**
* Monitors ambient light signals, applies a debouncing algorithm, and produces the current ambient
@@ -43,7 +44,7 @@ class AmbientLightModeMonitor
constructor(
private val algorithm: Optional<DebounceAlgorithm>,
private val sensorManager: AsyncSensorManager,
- @Named(LIGHT_SENSOR) private val lightSensor: Optional<Sensor>,
+ @Named(LIGHT_SENSOR) private val lightSensor: Optional<Provider<Sensor>>,
) : Dumpable {
companion object {
private const val TAG = "AmbientLightModeMonitor"
@@ -67,7 +68,7 @@ constructor(
fun start(callback: Callback) {
if (DEBUG) Log.d(TAG, "start monitoring ambient light mode")
- if (lightSensor.isEmpty) {
+ if (lightSensor.isEmpty || lightSensor.get().get() == null) {
if (DEBUG) Log.w(TAG, "light sensor not available")
return
}
@@ -80,7 +81,7 @@ constructor(
algorithm.get().start(callback)
sensorManager.registerListener(
mSensorEventListener,
- lightSensor.get(),
+ lightSensor.get().get(),
SensorManager.SENSOR_DELAY_NORMAL,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/lowlightclock/dagger/LowLightModule.java b/packages/SystemUI/src/com/android/systemui/lowlightclock/dagger/LowLightModule.java
index c08be51c0699..8469cb4ab565 100644
--- a/packages/SystemUI/src/com/android/systemui/lowlightclock/dagger/LowLightModule.java
+++ b/packages/SystemUI/src/com/android/systemui/lowlightclock/dagger/LowLightModule.java
@@ -16,6 +16,7 @@
package com.android.systemui.lowlightclock.dagger;
+import android.annotation.Nullable;
import android.content.res.Resources;
import android.hardware.Sensor;
@@ -100,6 +101,7 @@ public abstract class LowLightModule {
abstract LowLightDisplayController bindsLowLightDisplayController();
@BindsOptionalOf
+ @Nullable
@Named(LIGHT_SENSOR)
abstract Sensor bindsLightSensor();
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt
index c00e14c5957e..6501c437caf9 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt
@@ -983,13 +983,20 @@ constructor(
val overrideSize = mediaHostStatesManager.carouselSizes[location]
var overridden = false
overrideSize?.let {
- // To be safe we're using a maximum here. The override size should always be set
- // properly though.
- if (
+ if (SceneContainerFlag.isEnabled) {
+ result.measureWidth = widthInSceneContainerPx
+ result.measureHeight = heightInSceneContainerPx
+ overridden = true
+ } else if (
result.measureHeight != it.measuredHeight || result.measureWidth != it.measuredWidth
) {
+ // To be safe we're using a maximum here. The override size should always be set
+ // properly though.
result.measureHeight = Math.max(it.measuredHeight, result.measureHeight)
result.measureWidth = Math.max(it.measuredWidth, result.measureWidth)
+ overridden = true
+ }
+ if (overridden) {
// The measureHeight and the shown height should both be set to the overridden
// height
result.height = result.measureHeight
@@ -1001,7 +1008,6 @@ constructor(
state.width = result.width
}
}
- overridden = true
}
}
if (overridden && state != null && state.squishFraction <= 1f) {
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
index 52b3c3ecacc6..b391cb079ec5 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
@@ -156,6 +156,34 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter {
boolean isMutingExpectedDeviceExist = mController.hasMutingExpectedDevice();
final boolean currentlyConnected = isCurrentlyConnected(device);
boolean isCurrentSeekbarInvisible = mSeekBar.getVisibility() == View.GONE;
+ boolean isSelected = isDeviceIncluded(mController.getSelectedMediaDevice(), device);
+ boolean isDeselectable =
+ isDeviceIncluded(mController.getDeselectableMediaDevice(), device);
+ boolean isSelectable = isDeviceIncluded(mController.getSelectableMediaDevice(), device);
+ boolean isTransferable =
+ isDeviceIncluded(mController.getTransferableMediaDevices(), device);
+ boolean hasRouteListingPreferenceItem = device.hasRouteListingPreferenceItem();
+
+ if (DEBUG) {
+ Log.d(
+ TAG,
+ "["
+ + position
+ + "] "
+ + device.getName()
+ + " ["
+ + (isDeselectable ? "deselectable" : "")
+ + "] ["
+ + (isSelected ? "selected" : "")
+ + "] ["
+ + (isSelectable ? "selectable" : "")
+ + "] ["
+ + (isTransferable ? "transferable" : "")
+ + "] ["
+ + (hasRouteListingPreferenceItem ? "hasListingPreference" : "")
+ + "]");
+ }
+
if (mCurrentActivePosition == position) {
mCurrentActivePosition = -1;
}
@@ -210,8 +238,7 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter {
}
} else if (device.hasSubtext()) {
boolean isActiveWithOngoingSession =
- (device.hasOngoingSession() && (currentlyConnected || isDeviceIncluded(
- mController.getSelectedMediaDevice(), device)));
+ device.hasOngoingSession() && (currentlyConnected || isSelected);
boolean isHost = device.isHostForOngoingSession()
&& isActiveWithOngoingSession;
if (isActiveWithOngoingSession) {
@@ -266,16 +293,13 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter {
setSingleLineLayout(device.getName(), false /* showSeekBar*/,
true /* showProgressBar */, false /* showCheckBox */,
false /* showEndTouchArea */);
- } else if (mController.getSelectedMediaDevice().size() > 1
- && isDeviceIncluded(mController.getSelectedMediaDevice(), device)) {
+ } else if (mController.getSelectedMediaDevice().size() > 1 && isSelected) {
// selected device in group
- boolean isDeviceDeselectable = isDeviceIncluded(
- mController.getDeselectableMediaDevice(), device);
- boolean showEndArea = !Flags.enableOutputSwitcherSessionGrouping()
- || isDeviceDeselectable;
+ boolean showEndArea =
+ !Flags.enableOutputSwitcherSessionGrouping() || isDeselectable;
updateUnmutedVolumeIcon(device);
- updateGroupableCheckBox(true, isDeviceDeselectable, device);
- updateEndClickArea(device, isDeviceDeselectable);
+ updateGroupableCheckBox(true, isDeselectable, device);
+ updateEndClickArea(device, isDeselectable);
disableFocusPropertyForView(mContainerLayout);
setUpContentDescriptionForView(mSeekBar, device);
setSingleLineLayout(device.getName(), true /* showSeekBar */,
@@ -307,10 +331,8 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter {
//If device is connected and there's other selectable devices, layout as
// one of selected devices.
updateUnmutedVolumeIcon(device);
- boolean isDeviceDeselectable = isDeviceIncluded(
- mController.getDeselectableMediaDevice(), device);
- updateGroupableCheckBox(true, isDeviceDeselectable, device);
- updateEndClickArea(device, isDeviceDeselectable);
+ updateGroupableCheckBox(true, isDeselectable, device);
+ updateEndClickArea(device, isDeselectable);
disableFocusPropertyForView(mContainerLayout);
setUpContentDescriptionForView(mSeekBar, device);
setSingleLineLayout(device.getName(), true /* showSeekBar */,
@@ -327,12 +349,16 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter {
false /* showEndTouchArea */);
initSeekbar(device, isCurrentSeekbarInvisible);
}
- } else if (isDeviceIncluded(mController.getSelectableMediaDevice(), device)) {
+ } else if (isSelectable) {
//groupable device
setUpDeviceIcon(device);
updateGroupableCheckBox(false, true, device);
updateEndClickArea(device, true);
- updateFullItemClickListener(v -> onItemClick(v, device));
+ if (!Flags.disableTransferWhenAppsDoNotSupport()
+ || isTransferable
+ || hasRouteListingPreferenceItem) {
+ updateFullItemClickListener(v -> onItemClick(v, device));
+ }
setSingleLineLayout(device.getName(), false /* showSeekBar */,
false /* showProgressBar */, true /* showCheckBox */,
true /* showEndTouchArea */);
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaSwitchingController.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaSwitchingController.java
index 35c872f8a203..02a2befe44e5 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaSwitchingController.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaSwitchingController.java
@@ -941,6 +941,10 @@ public class MediaSwitchingController
return mLocalMediaManager.getSelectableMediaDevice();
}
+ List<MediaDevice> getTransferableMediaDevices() {
+ return mLocalMediaManager.getTransferableMediaDevices();
+ }
+
public List<MediaDevice> getSelectedMediaDevice() {
if (!enableInputRouting()) {
return mLocalMediaManager.getSelectedMediaDevice();
diff --git a/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayContentViewModel.kt b/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayContentViewModel.kt
index 8aad61a8c7cb..c7b165415aea 100644
--- a/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayContentViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayContentViewModel.kt
@@ -52,18 +52,11 @@ constructor(
private val hydrator = Hydrator("NotificationsShadeOverlayContentViewModel.hydrator")
- val isShadeLayoutWide: Boolean by
+ val showClock: Boolean by
hydrator.hydratedStateOf(
- traceName = "isShadeLayoutWide",
- initialValue = shadeInteractor.isShadeLayoutWide.value,
- source = shadeInteractor.isShadeLayoutWide,
- )
-
- val showHeader: Boolean by
- hydrator.hydratedStateOf(
- traceName = "showHeader",
+ traceName = "showClock",
initialValue =
- shouldShowHeader(
+ shouldShowClock(
isShadeLayoutWide = shadeInteractor.isShadeLayoutWide.value,
areAnyNotificationsPresent =
activeNotificationsInteractor.areAnyNotificationsPresentValue,
@@ -72,7 +65,7 @@ constructor(
combine(
shadeInteractor.isShadeLayoutWide,
activeNotificationsInteractor.areAnyNotificationsPresent,
- this::shouldShowHeader,
+ this::shouldShowClock,
),
)
@@ -110,7 +103,7 @@ constructor(
shadeInteractor.collapseNotificationsShade(loggingReason = "shade scrim clicked")
}
- private fun shouldShowHeader(
+ private fun shouldShowClock(
isShadeLayoutWide: Boolean,
areAnyNotificationsPresent: Boolean,
): Boolean {
diff --git a/packages/SystemUI/src/com/android/systemui/power/domain/interactor/PowerInteractor.kt b/packages/SystemUI/src/com/android/systemui/power/domain/interactor/PowerInteractor.kt
index f8d442de0f55..25d53e6d1f1f 100644
--- a/packages/SystemUI/src/com/android/systemui/power/domain/interactor/PowerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/power/domain/interactor/PowerInteractor.kt
@@ -50,7 +50,7 @@ constructor(
@FalsingCollectorActual private val falsingCollector: FalsingCollector,
private val screenOffAnimationController: ScreenOffAnimationController,
private val statusBarStateController: StatusBarStateController,
- private val cameraGestureHelper: Provider<CameraGestureHelper>,
+ private val cameraGestureHelper: Provider<CameraGestureHelper?>,
) {
/** Whether the screen is on or off. */
val isInteractive: Flow<Boolean> = repository.isInteractive
@@ -154,8 +154,9 @@ constructor(
// or onFinishedGoingToSleep(), carry that state forward. It will be reset by the next
// onStartedGoingToSleep.
val powerButtonLaunchGestureTriggered =
- powerButtonLaunchGestureTriggeredOnWakeUp ||
- repository.wakefulness.value.powerButtonLaunchGestureTriggered
+ !isPowerButtonGestureSuppressed() &&
+ (powerButtonLaunchGestureTriggeredOnWakeUp ||
+ repository.wakefulness.value.powerButtonLaunchGestureTriggered)
repository.updateWakefulness(
rawState = WakefulnessState.STARTING_TO_WAKE,
@@ -204,8 +205,9 @@ constructor(
// If the launch gesture was previously detected via onCameraLaunchGestureDetected, carry
// that state forward. It will be reset by the next onStartedGoingToSleep.
val powerButtonLaunchGestureTriggered =
- powerButtonLaunchGestureTriggeredDuringSleep ||
- repository.wakefulness.value.powerButtonLaunchGestureTriggered
+ !isPowerButtonGestureSuppressed() &&
+ (powerButtonLaunchGestureTriggeredDuringSleep ||
+ repository.wakefulness.value.powerButtonLaunchGestureTriggered)
repository.updateWakefulness(
rawState = WakefulnessState.ASLEEP,
@@ -218,11 +220,7 @@ constructor(
}
fun onCameraLaunchGestureDetected() {
- if (
- cameraGestureHelper
- .get()
- .canCameraGestureBeLaunched(statusBarStateController.getState())
- ) {
+ if (!isPowerButtonGestureSuppressed()) {
repository.updateWakefulness(powerButtonLaunchGestureTriggered = true)
}
}
@@ -240,6 +238,16 @@ constructor(
.collect()
}
+ /**
+ * Whether the power button gesture isn't allowed to launch anything even if a double tap is
+ * detected.
+ */
+ private fun isPowerButtonGestureSuppressed(): Boolean {
+ return cameraGestureHelper
+ .get()
+ ?.canCameraGestureBeLaunched(statusBarStateController.state) == false
+ }
+
companion object {
private const val FSI_WAKE_WHY = "full_screen_intent"
diff --git a/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt b/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
index 85b677b65aeb..cf3b4969b07d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
@@ -573,8 +573,7 @@ constructor(
onDispose { qqsVisible.value = false }
}
val squishiness by
- viewModel.containerViewModel.quickQuickSettingsViewModel.squishinessViewModel
- .squishiness
+ viewModel.quickQuickSettingsViewModel.squishinessViewModel.squishiness
.collectAsStateWithLifecycle()
Column(modifier = modifier.sysuiResTag(ResIdTags.quickQsPanel)) {
@@ -607,9 +606,7 @@ constructor(
) {
val Tiles =
@Composable {
- QuickQuickSettings(
- viewModel = viewModel.containerViewModel.quickQuickSettingsViewModel
- )
+ QuickQuickSettings(viewModel = viewModel.quickQuickSettingsViewModel)
}
val Media =
@Composable {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt
index 219fc2fdc5ec..0dade7438720 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt
@@ -59,6 +59,7 @@ import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
import com.android.systemui.qs.panels.domain.interactor.TileSquishinessInteractor
import com.android.systemui.qs.panels.ui.viewmodel.InFirstPageViewModel
import com.android.systemui.qs.panels.ui.viewmodel.MediaInRowInLandscapeViewModel
+import com.android.systemui.qs.panels.ui.viewmodel.QuickQuickSettingsViewModel
import com.android.systemui.qs.ui.viewmodel.QuickSettingsContainerViewModel
import com.android.systemui.res.R
import com.android.systemui.scene.shared.model.Scenes
@@ -94,6 +95,7 @@ class QSFragmentComposeViewModel
@AssistedInject
constructor(
containerViewModelFactory: QuickSettingsContainerViewModel.Factory,
+ quickQuickSettingsViewModelFactory: QuickQuickSettingsViewModel.Factory,
@Main private val resources: Resources,
footerActionsViewModelFactory: FooterActionsViewModel.Factory,
private val footerActionsController: FooterActionsController,
@@ -102,7 +104,7 @@ constructor(
DisableFlagsInteractor: DisableFlagsInteractor,
keyguardTransitionInteractor: KeyguardTransitionInteractor,
private val largeScreenShadeInterpolator: LargeScreenShadeInterpolator,
- private val shadeInteractor: ShadeInteractor,
+ shadeInteractor: ShadeInteractor,
@ShadeDisplayAware configurationInteractor: ConfigurationInteractor,
private val largeScreenHeaderHelper: LargeScreenHeaderHelper,
private val squishinessInteractor: TileSquishinessInteractor,
@@ -118,6 +120,8 @@ constructor(
) : Dumpable, ExclusiveActivatable() {
val containerViewModel = containerViewModelFactory.create(true)
+ val quickQuickSettingsViewModel = quickQuickSettingsViewModelFactory.create()
+
private val qqsMediaInRowViewModel = mediaInRowInLandscapeViewModelFactory.create(LOCATION_QQS)
private val qsMediaInRowViewModel = mediaInRowInLandscapeViewModelFactory.create(LOCATION_QS)
@@ -475,6 +479,7 @@ constructor(
}
launch { hydrator.activate() }
launch { containerViewModel.activate() }
+ launch { quickQuickSettingsViewModel.activate() }
launch { qqsMediaInRowViewModel.activate() }
launch { qsMediaInRowViewModel.activate() }
awaitCancellation()
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/EditTile.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/EditTile.kt
index 1f4f9f98c5b2..7701b9087e23 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/EditTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/EditTile.kt
@@ -31,6 +31,7 @@ import androidx.compose.foundation.LocalOverscrollFactory
import androidx.compose.foundation.ScrollState
import androidx.compose.foundation.background
import androidx.compose.foundation.border
+import androidx.compose.foundation.clickable
import androidx.compose.foundation.clipScrollableContainer
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.layout.Arrangement.spacedBy
@@ -49,7 +50,6 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.requiredHeightIn
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.wrapContentHeight
-import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyGridScope
import androidx.compose.foundation.lazy.grid.rememberLazyGridState
@@ -101,7 +101,6 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.CustomAccessibilityAction
import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.semantics.customActions
-import androidx.compose.ui.semantics.onClick
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.semantics.stateDescription
import androidx.compose.ui.text.style.TextAlign
@@ -138,7 +137,6 @@ import com.android.systemui.qs.panels.ui.compose.selection.ResizingState
import com.android.systemui.qs.panels.ui.compose.selection.ResizingState.ResizeOperation
import com.android.systemui.qs.panels.ui.compose.selection.ResizingState.ResizeOperation.FinalResizeOperation
import com.android.systemui.qs.panels.ui.compose.selection.ResizingState.ResizeOperation.TemporaryResizeOperation
-import com.android.systemui.qs.panels.ui.compose.selection.clearSelectionTile
import com.android.systemui.qs.panels.ui.compose.selection.rememberResizingState
import com.android.systemui.qs.panels.ui.compose.selection.rememberSelectionState
import com.android.systemui.qs.panels.ui.compose.selection.selectableTile
@@ -190,6 +188,7 @@ fun DefaultEditTileGrid(
columns: Int,
largeTilesSpan: Int,
modifier: Modifier,
+ onAddTile: (TileSpec) -> Unit,
onRemoveTile: (TileSpec) -> Unit,
onSetTiles: (List<TileSpec>) -> Unit,
onResize: (TileSpec, toIcon: Boolean) -> Unit,
@@ -230,20 +229,26 @@ fun DefaultEditTileGrid(
modifier
.fillMaxSize()
// Apply top padding before the scroll so the scrollable doesn't show under
- // the
- // top bar
+ // the top bar
.padding(top = innerPadding.calculateTopPadding())
.clipScrollableContainer(Orientation.Vertical)
.verticalScroll(scrollState),
) {
AnimatedContent(
- targetState = listState.dragInProgress,
- modifier = Modifier.wrapContentSize(),
+ targetState = listState.dragInProgress || selectionState.selected,
label = "QSEditHeader",
- ) { dragIsInProgress ->
- EditGridHeader(Modifier.dragAndDropRemoveZone(listState, onRemoveTile)) {
- if (dragIsInProgress) {
- RemoveTileTarget()
+ ) { showRemoveTarget ->
+ EditGridHeader(
+ Modifier.dragAndDropRemoveZone(listState, onRemoveTile)
+ .padding(bottom = 26.dp)
+ ) {
+ if (showRemoveTarget) {
+ RemoveTileTarget {
+ selectionState.selection?.let {
+ selectionState.unSelect()
+ onRemoveTile(it.tileSpec)
+ }
+ }
} else {
Text(text = stringResource(id = R.string.drag_to_rearrange_tiles))
}
@@ -283,7 +288,13 @@ fun DefaultEditTileGrid(
Text(text = stringResource(id = R.string.drag_to_add_tiles))
}
- AvailableTileGrid(otherTiles, selectionState, columns, listState)
+ AvailableTileGrid(
+ otherTiles,
+ selectionState,
+ columns,
+ onAddTile,
+ listState,
+ )
}
}
}
@@ -347,22 +358,18 @@ private fun EditGridHeader(
CompositionLocalProvider(
LocalContentColor provides MaterialTheme.colorScheme.onBackground.copy(alpha = .5f)
) {
- Box(
- contentAlignment = Alignment.Center,
- modifier = modifier.fillMaxWidth().wrapContentHeight(),
- ) {
- content()
- }
+ Box(contentAlignment = Alignment.Center, modifier = modifier.fillMaxWidth()) { content() }
}
}
@Composable
-private fun RemoveTileTarget() {
+private fun RemoveTileTarget(onClick: () -> Unit) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = tileHorizontalArrangement(),
modifier =
Modifier.fillMaxHeight()
+ .clickable(onClick = onClick)
.border(1.dp, LocalContentColor.current, shape = CircleShape)
.padding(10.dp),
) {
@@ -441,6 +448,7 @@ private fun AvailableTileGrid(
tiles: List<SizedTile<EditTileViewModel>>,
selectionState: MutableSelectionState,
columns: Int,
+ onAddTile: (TileSpec) -> Unit,
dragAndDropState: DragAndDropState,
) {
// Available tiles aren't visible during drag and drop, so the row/col isn't needed
@@ -478,6 +486,7 @@ private fun AvailableTileGrid(
index = index,
dragAndDropState = dragAndDropState,
selectionState = selectionState,
+ onAddTile = onAddTile,
modifier = Modifier.weight(1f).fillMaxHeight(),
)
}
@@ -682,11 +691,16 @@ private fun AvailableTileGridCell(
index: Int,
dragAndDropState: DragAndDropState,
selectionState: MutableSelectionState,
+ onAddTile: (TileSpec) -> Unit,
modifier: Modifier = Modifier,
) {
val onClickActionName = stringResource(id = R.string.accessibility_qs_edit_tile_add_action)
val stateDescription = stringResource(id = R.string.accessibility_qs_edit_position, index + 1)
val colors = EditModeTileDefaults.editTileColors()
+ val onClick = {
+ onAddTile(cell.tile.tileSpec)
+ selectionState.select(cell.tile.tileSpec, manual = false)
+ }
// Displays the tile as an icon tile with the label underneath
Column(
@@ -697,11 +711,8 @@ private fun AvailableTileGridCell(
Box(
Modifier.fillMaxWidth()
.height(TileHeight)
- .clearSelectionTile(selectionState)
- .semantics(mergeDescendants = true) {
- onClick(onClickActionName) { false }
- this.stateDescription = stateDescription
- }
+ .clickable(onClick = onClick, onClickLabel = onClickActionName)
+ .semantics(mergeDescendants = true) { this.stateDescription = stateDescription }
.dragAndDropTileSource(
SizedTileImpl(cell.tile, cell.width),
dragAndDropState,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayout.kt
index cc4c3af1dc63..1c540eed8aa0 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayout.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayout.kt
@@ -42,6 +42,7 @@ import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel
import com.android.systemui.qs.panels.ui.viewmodel.IconTilesViewModel
import com.android.systemui.qs.panels.ui.viewmodel.InfiniteGridViewModel
import com.android.systemui.qs.panels.ui.viewmodel.TileViewModel
+import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractor.Companion.POSITION_AT_END
import com.android.systemui.qs.pipeline.shared.TileSpec
import com.android.systemui.qs.shared.ui.ElementKeys.toElementKey
import com.android.systemui.res.R
@@ -155,6 +156,7 @@ constructor(
otherTiles = otherTiles,
columns = columns,
modifier = modifier,
+ onAddTile = { onAddTile(it, POSITION_AT_END) },
onRemoveTile = onRemoveTile,
onSetTiles = onSetTiles,
onResize = iconTilesViewModel::resize,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/selection/MutableSelectionState.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/selection/MutableSelectionState.kt
index c6c6dcaa896c..26dfc7224ff9 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/selection/MutableSelectionState.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/selection/MutableSelectionState.kt
@@ -21,6 +21,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.pointer.pointerInput
import com.android.systemui.qs.pipeline.shared.TileSpec
@@ -39,17 +40,19 @@ data class Selection(val tileSpec: TileSpec, val manual: Boolean)
/** Holds the state of the current selection. */
class MutableSelectionState {
- private var _selection = mutableStateOf<Selection?>(null)
-
/** The [Selection] if a tile is selected, null if not. */
- val selection by _selection
+ var selection by mutableStateOf<Selection?>(null)
+ private set
+
+ val selected: Boolean
+ get() = selection != null
fun select(tileSpec: TileSpec, manual: Boolean) {
- _selection.value = Selection(tileSpec, manual)
+ selection = Selection(tileSpec, manual)
}
fun unSelect() {
- _selection.value = null
+ selection = null
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
index 0109e70a467e..1cfa6632a8b0 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
@@ -158,7 +158,7 @@ public class BluetoothTile extends QSTileImpl<BooleanState> {
private void handleClickEvent(@Nullable Expandable expandable) {
if (mFeatureFlags.isEnabled(Flags.BLUETOOTH_QS_TILE_DIALOG)) {
- mDialogViewModel.showDialog(expandable);
+ mDialogViewModel.showDetailsContent(expandable, /* view= */ null);
} else {
// Secondary clicks are header clicks, just toggle.
toggleBluetooth();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsContainerViewModel.kt
index c7db04a6b7b2..aa8e4242f64e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsContainerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsContainerViewModel.kt
@@ -19,43 +19,52 @@ package com.android.systemui.qs.ui.viewmodel
import androidx.compose.runtime.getValue
import com.android.systemui.brightness.ui.viewmodel.BrightnessSliderViewModel
import com.android.systemui.lifecycle.ExclusiveActivatable
+import com.android.systemui.lifecycle.Hydrator
import com.android.systemui.qs.panels.ui.viewmodel.DetailsViewModel
import com.android.systemui.qs.panels.ui.viewmodel.EditModeViewModel
-import com.android.systemui.qs.panels.ui.viewmodel.QuickQuickSettingsViewModel
import com.android.systemui.qs.panels.ui.viewmodel.TileGridViewModel
import com.android.systemui.qs.panels.ui.viewmodel.toolbar.ToolbarViewModel
+import com.android.systemui.shade.domain.interactor.ShadeModeInteractor
import com.android.systemui.shade.ui.viewmodel.ShadeHeaderViewModel
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import kotlinx.coroutines.awaitCancellation
import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
class QuickSettingsContainerViewModel
@AssistedInject
constructor(
brightnessSliderViewModelFactory: BrightnessSliderViewModel.Factory,
- quickQuickSettingsViewModelFactory: QuickQuickSettingsViewModel.Factory,
shadeHeaderViewModelFactory: ShadeHeaderViewModel.Factory,
@Assisted supportsBrightnessMirroring: Boolean,
val tileGridViewModel: TileGridViewModel,
val editModeViewModel: EditModeViewModel,
val detailsViewModel: DetailsViewModel,
val toolbarViewModelFactory: ToolbarViewModel.Factory,
+ shadeModeInteractor: ShadeModeInteractor,
) : ExclusiveActivatable() {
+ private val hydrator = Hydrator("QuickSettingsContainerViewModel.hydrator")
+
val brightnessSliderViewModel =
brightnessSliderViewModelFactory.create(supportsBrightnessMirroring)
- val quickQuickSettingsViewModel = quickQuickSettingsViewModelFactory.create()
-
val shadeHeaderViewModel = shadeHeaderViewModelFactory.create()
+ val showHeader: Boolean by
+ hydrator.hydratedStateOf(
+ traceName = "showHeader",
+ initialValue = !shadeModeInteractor.isShadeLayoutWide.value,
+ source = shadeModeInteractor.isShadeLayoutWide.map { !it },
+ )
+
override suspend fun onActivated(): Nothing {
coroutineScope {
+ launch { hydrator.activate() }
launch { brightnessSliderViewModel.activate() }
- launch { quickQuickSettingsViewModel.activate() }
launch { shadeHeaderViewModel.activate() }
awaitCancellation()
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModel.kt
index d9df1ef36847..0add3f515ebf 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModel.kt
@@ -16,13 +16,10 @@
package com.android.systemui.qs.ui.viewmodel
-import androidx.compose.runtime.getValue
import com.android.systemui.lifecycle.ExclusiveActivatable
-import com.android.systemui.lifecycle.Hydrator
import com.android.systemui.scene.domain.interactor.SceneInteractor
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.shade.domain.interactor.ShadeInteractor
-import com.android.systemui.shade.ui.viewmodel.ShadeHeaderViewModel
import com.android.systemui.statusbar.notification.stack.domain.interactor.NotificationStackAppearanceInteractor
import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimShape
import dagger.assisted.AssistedFactory
@@ -31,7 +28,6 @@ import kotlinx.coroutines.awaitCancellation
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filter
-import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
/**
@@ -46,39 +42,10 @@ constructor(
val shadeInteractor: ShadeInteractor,
val sceneInteractor: SceneInteractor,
val notificationStackAppearanceInteractor: NotificationStackAppearanceInteractor,
- val shadeHeaderViewModelFactory: ShadeHeaderViewModel.Factory,
- quickSettingsContainerViewModelFactory: QuickSettingsContainerViewModel.Factory,
) : ExclusiveActivatable() {
- private val hydrator = Hydrator("QuickSettingsContainerViewModel.hydrator")
-
- val isShadeLayoutWide: Boolean by
- hydrator.hydratedStateOf(
- traceName = "isShadeLayoutWide",
- initialValue = shadeInteractor.isShadeLayoutWide.value,
- source = shadeInteractor.isShadeLayoutWide,
- )
-
- val showHeader: Boolean by
- hydrator.hydratedStateOf(
- traceName = "showHeader",
- initialValue = !shadeInteractor.isShadeLayoutWide.value,
- source = shadeInteractor.isShadeLayoutWide.map { !it },
- )
-
- val quickSettingsContainerViewModel = quickSettingsContainerViewModelFactory.create(false)
-
- val showQuickSettingsOverlayHeader: Boolean by
- hydrator.hydratedStateOf(
- traceName = "showQuickSettingsOverlayHeader",
- initialValue = shadeInteractor.isShadeLayoutWide.value,
- source = shadeInteractor.isShadeLayoutWide,
- )
-
override suspend fun onActivated(): Nothing {
coroutineScope {
- launch { hydrator.activate() }
-
launch {
sceneInteractor.currentScene.collect { currentScene ->
when (currentScene) {
@@ -101,8 +68,6 @@ constructor(
)
}
}
-
- launch { quickSettingsContainerViewModel.activate() }
}
awaitCancellation()
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt
index 40e6d284cbc9..ba7979ca2120 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt
@@ -30,6 +30,7 @@ import com.android.compose.animation.scene.UserAction
import com.android.compose.animation.scene.UserActionResult
import com.android.systemui.classifier.Classifier
import com.android.systemui.classifier.domain.interactor.FalsingInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.ui.viewmodel.LightRevealScrimViewModel
import com.android.systemui.lifecycle.ExclusiveActivatable
import com.android.systemui.lifecycle.Hydrator
@@ -66,6 +67,7 @@ constructor(
hapticsViewModelFactory: SceneContainerHapticsViewModel.Factory,
val lightRevealScrim: LightRevealScrimViewModel,
val wallpaperViewModel: WallpaperViewModel,
+ keyguardInteractor: KeyguardInteractor,
@Assisted view: View,
@Assisted private val motionEventHandlerReceiver: (MotionEventHandler?) -> Unit,
) : ExclusiveActivatable() {
@@ -96,6 +98,14 @@ constructor(
},
)
+ /** Amount of color saturation for the Flexi🥃 ribbon. */
+ val ribbonColorSaturation: Float by
+ hydrator.hydratedStateOf(
+ traceName = "ribbonColorSaturation",
+ source = keyguardInteractor.dozeAmount.map { 1 - it },
+ initialValue = 1f,
+ )
+
override suspend fun onActivated(): Nothing {
try {
// Sends a MotionEventHandler to the owner of the view-model so they can report
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImpl.kt
index 661f2ae5132c..246177e0c46d 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImpl.kt
@@ -35,6 +35,7 @@ import com.android.systemui.shade.shared.model.ShadeMode
import com.android.systemui.utils.coroutines.flow.flatMapLatestConflated
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
@@ -45,6 +46,7 @@ import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
/** ShadeInteractor implementation for Scene Container. */
+@OptIn(ExperimentalCoroutinesApi::class)
@SysUISingleton
class ShadeInteractorSceneContainerImpl
@Inject
@@ -137,20 +139,18 @@ constructor(
override fun expandNotificationsShade(loggingReason: String, transitionKey: TransitionKey?) {
if (shadeModeInteractor.isDualShade) {
- if (Overlays.QuickSettingsShade in sceneInteractor.currentOverlays.value) {
- sceneInteractor.replaceOverlay(
- from = Overlays.QuickSettingsShade,
- to = Overlays.NotificationsShade,
- loggingReason = loggingReason,
- transitionKey = transitionKey,
- )
- } else {
- sceneInteractor.showOverlay(
- overlay = Overlays.NotificationsShade,
- loggingReason = loggingReason,
- transitionKey = transitionKey,
- )
- }
+ // Collapse the quick settings shade if it's expanded (no-op if it isn't).
+ sceneInteractor.hideOverlay(
+ overlay = Overlays.QuickSettingsShade,
+ loggingReason = loggingReason,
+ transitionKey = transitionKey,
+ )
+ // Expand the notifications shade.
+ sceneInteractor.showOverlay(
+ overlay = Overlays.NotificationsShade,
+ loggingReason = loggingReason,
+ transitionKey = transitionKey,
+ )
} else {
sceneInteractor.changeScene(
toScene = Scenes.Shade,
@@ -163,20 +163,18 @@ constructor(
override fun expandQuickSettingsShade(loggingReason: String, transitionKey: TransitionKey?) {
if (shadeModeInteractor.isDualShade) {
- if (Overlays.NotificationsShade in sceneInteractor.currentOverlays.value) {
- sceneInteractor.replaceOverlay(
- from = Overlays.NotificationsShade,
- to = Overlays.QuickSettingsShade,
- loggingReason = loggingReason,
- transitionKey = transitionKey,
- )
- } else {
- sceneInteractor.showOverlay(
- overlay = Overlays.QuickSettingsShade,
- loggingReason = loggingReason,
- transitionKey = transitionKey,
- )
- }
+ // Collapse the notifications shade if it's expanded (no-op if it isn't).
+ sceneInteractor.hideOverlay(
+ overlay = Overlays.NotificationsShade,
+ loggingReason = loggingReason,
+ transitionKey = transitionKey,
+ )
+ // Expand the quick settings shade.
+ sceneInteractor.showOverlay(
+ overlay = Overlays.QuickSettingsShade,
+ loggingReason = loggingReason,
+ transitionKey = transitionKey,
+ )
} else {
val isSplitShade = shadeModeInteractor.isSplitShade
sceneInteractor.changeScene(
@@ -199,12 +197,12 @@ constructor(
// TODO(b/356596436): Define instant transition instead of snapToScene().
sceneInteractor.snapToScene(
toScene = SceneFamilies.Home,
- loggingReason = loggingReason + " (collapseNotificationsShade)",
+ loggingReason = "$loggingReason (collapseNotificationsShade)",
)
} else {
sceneInteractor.changeScene(
toScene = SceneFamilies.Home,
- loggingReason = loggingReason + " (collapseNotificationsShade)",
+ loggingReason = "$loggingReason (collapseNotificationsShade)",
transitionKey =
transitionKey ?: ToSplitShade.takeIf { shadeModeInteractor.isSplitShade },
)
@@ -233,12 +231,12 @@ constructor(
// TODO(b/356596436): Define instant transition instead of snapToScene().
sceneInteractor.snapToScene(
toScene = targetScene,
- loggingReason = loggingReason + " (collapseQuickSettingsShade)",
+ loggingReason = "$loggingReason (collapseQuickSettingsShade)",
)
} else {
sceneInteractor.changeScene(
toScene = targetScene,
- loggingReason = loggingReason + " (collapseQuickSettingsShade)",
+ loggingReason = "$loggingReason (collapseQuickSettingsShade)",
transitionKey = transitionKey ?: ToSplitShade.takeIf { isSplitShade },
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModel.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModel.kt
index 96128df1b723..51fcf7da3c13 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModel.kt
@@ -23,8 +23,10 @@ import android.icu.text.DateFormat
import android.icu.text.DisplayContext
import android.os.UserHandle
import android.provider.Settings
+import android.view.ViewGroup
import androidx.compose.runtime.getValue
import com.android.app.tracing.coroutines.launchTraced as launch
+import com.android.systemui.battery.BatteryMeterViewController
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.lifecycle.ExclusiveActivatable
import com.android.systemui.lifecycle.Hydrator
@@ -40,6 +42,10 @@ import com.android.systemui.shade.domain.interactor.PrivacyChipInteractor
import com.android.systemui.shade.domain.interactor.ShadeHeaderClockInteractor
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.shade.shared.model.ShadeMode
+import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerStatusBarViewBinder
+import com.android.systemui.statusbar.phone.StatusBarLocation
+import com.android.systemui.statusbar.phone.ui.StatusBarIconController
+import com.android.systemui.statusbar.phone.ui.TintedIconManager
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor
import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel
import dagger.assisted.AssistedFactory
@@ -67,27 +73,46 @@ constructor(
val mobileIconsViewModel: MobileIconsViewModel,
private val privacyChipInteractor: PrivacyChipInteractor,
private val clockInteractor: ShadeHeaderClockInteractor,
+ private val tintedIconManagerFactory: TintedIconManager.Factory,
+ private val batteryMeterViewControllerFactory: BatteryMeterViewController.Factory,
+ val statusBarIconController: StatusBarIconController,
+ val notificationIconContainerStatusBarViewBinder: NotificationIconContainerStatusBarViewBinder,
private val broadcastDispatcher: BroadcastDispatcher,
) : ExclusiveActivatable() {
private val hydrator = Hydrator("ShadeHeaderViewModel.hydrator")
- val highlightNotificationIcons: Boolean by
+ val createTintedIconManager: (ViewGroup, StatusBarLocation) -> TintedIconManager =
+ tintedIconManagerFactory::create
+
+ val createBatteryMeterViewController:
+ (ViewGroup, StatusBarLocation) -> BatteryMeterViewController =
+ batteryMeterViewControllerFactory::create
+
+ val notificationsChipHighlight: HeaderChipHighlight by
hydrator.hydratedStateOf(
- traceName = "highlightNotificationIcons",
- initialValue = false,
+ traceName = "notificationsChipHighlight",
+ initialValue = HeaderChipHighlight.None,
source =
sceneInteractor.currentOverlays.map { overlays ->
- Overlays.NotificationsShade in overlays
+ when {
+ Overlays.NotificationsShade in overlays -> HeaderChipHighlight.Strong
+ Overlays.QuickSettingsShade in overlays -> HeaderChipHighlight.Weak
+ else -> HeaderChipHighlight.None
+ }
},
)
- val highlightQuickSettingsIcons: Boolean by
+ val quickSettingsChipHighlight: HeaderChipHighlight by
hydrator.hydratedStateOf(
- traceName = "highlightQuickSettingsIcons",
- initialValue = false,
+ traceName = "quickSettingsChipHighlight",
+ initialValue = HeaderChipHighlight.None,
source =
sceneInteractor.currentOverlays.map { overlays ->
- Overlays.QuickSettingsShade in overlays
+ when {
+ Overlays.QuickSettingsShade in overlays -> HeaderChipHighlight.Strong
+ Overlays.NotificationsShade in overlays -> HeaderChipHighlight.Weak
+ else -> HeaderChipHighlight.None
+ }
},
)
@@ -225,6 +250,15 @@ constructor(
)
}
+ /** Represents the background highlight of a header icons chip. */
+ sealed interface HeaderChipHighlight {
+ data object None : HeaderChipHighlight
+
+ data object Weak : HeaderChipHighlight
+
+ data object Strong : HeaderChipHighlight
+ }
+
private fun updateDateTexts(invalidateFormats: Boolean) {
if (invalidateFormats) {
longerDateFormat.value = getFormatFromPattern(longerPattern)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
index ead8f6a1123e..0dfc63ea8619 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
@@ -16,13 +16,9 @@
package com.android.systemui.statusbar;
-import static com.android.internal.jank.InteractionJankMonitor.CUJ_LOCKSCREEN_TRANSITION_FROM_AOD;
-import static com.android.internal.jank.InteractionJankMonitor.CUJ_LOCKSCREEN_TRANSITION_TO_AOD;
import static com.android.systemui.keyguard.shared.model.KeyguardState.GONE;
import static com.android.systemui.util.kotlin.JavaAdapterKt.combineFlows;
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.os.SystemProperties;
@@ -30,7 +26,6 @@ import android.os.Trace;
import android.text.format.DateFormat;
import android.util.FloatProperty;
import android.util.Log;
-import android.view.Choreographer;
import android.view.View;
import android.view.animation.Interpolator;
@@ -42,8 +37,6 @@ import com.android.compose.animation.scene.OverlayKey;
import com.android.compose.animation.scene.SceneKey;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.jank.InteractionJankMonitor;
-import com.android.internal.jank.InteractionJankMonitor.Configuration;
import com.android.internal.logging.UiEventLogger;
import com.android.systemui.DejankUtils;
import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor;
@@ -54,7 +47,6 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
-import com.android.systemui.res.R;
import com.android.systemui.scene.data.model.SceneStack;
import com.android.systemui.scene.data.model.SceneStackKt;
import com.android.systemui.scene.domain.interactor.SceneBackInteractor;
@@ -113,7 +105,6 @@ public class StatusBarStateControllerImpl implements
private final ArrayList<RankedListener> mListeners = new ArrayList<>();
private final UiEventLogger mUiEventLogger;
- private final Lazy<InteractionJankMonitor> mInteractionJankMonitorLazy;
private final JavaAdapter mJavaAdapter;
private final Lazy<KeyguardInteractor> mKeyguardInteractorLazy;
private final Lazy<KeyguardTransitionInteractor> mKeyguardTransitionInteractorLazy;
@@ -184,7 +175,6 @@ public class StatusBarStateControllerImpl implements
@Inject
public StatusBarStateControllerImpl(
UiEventLogger uiEventLogger,
- Lazy<InteractionJankMonitor> interactionJankMonitorLazy,
JavaAdapter javaAdapter,
Lazy<KeyguardInteractor> keyguardInteractor,
Lazy<KeyguardTransitionInteractor> keyguardTransitionInteractor,
@@ -196,7 +186,6 @@ public class StatusBarStateControllerImpl implements
Lazy<SceneBackInteractor> sceneBackInteractorLazy,
Lazy<AlternateBouncerInteractor> alternateBouncerInteractorLazy) {
mUiEventLogger = uiEventLogger;
- mInteractionJankMonitorLazy = interactionJankMonitorLazy;
mJavaAdapter = javaAdapter;
mKeyguardInteractorLazy = keyguardInteractor;
mKeyguardTransitionInteractorLazy = keyguardTransitionInteractor;
@@ -470,22 +459,6 @@ public class StatusBarStateControllerImpl implements
this, SET_DARK_AMOUNT_PROPERTY, mDozeAmountTarget);
darkAnimator.setInterpolator(Interpolators.LINEAR);
darkAnimator.setDuration(StackStateAnimator.ANIMATION_DURATION_WAKEUP);
- darkAnimator.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationCancel(Animator animation) {
- cancelInteractionJankMonitor();
- }
-
- @Override
- public void onAnimationEnd(Animator animation) {
- endInteractionJankMonitor();
- }
-
- @Override
- public void onAnimationStart(Animator animation) {
- beginInteractionJankMonitor();
- }
- });
darkAnimator.start();
return darkAnimator;
}
@@ -511,42 +484,6 @@ public class StatusBarStateControllerImpl implements
return mKeyguardClockInteractorLazy.get().getRenderedClockId();
}
- private void beginInteractionJankMonitor() {
- final boolean shouldPost =
- (mIsDozing && mDozeAmount == 0) || (!mIsDozing && mDozeAmount == 1);
- InteractionJankMonitor monitor = mInteractionJankMonitorLazy.get();
- if (monitor != null && mView != null && mView.isAttachedToWindow()) {
- if (shouldPost) {
- Choreographer.getInstance().postCallback(
- Choreographer.CALLBACK_ANIMATION, this::beginInteractionJankMonitor, null);
- } else {
- Configuration.Builder builder = Configuration.Builder.withView(getCujType(), mView)
- .setTag(getClockId())
- .setDeferMonitorForAnimationStart(false);
- monitor.begin(builder);
- }
- }
- }
-
- private void endInteractionJankMonitor() {
- InteractionJankMonitor monitor = mInteractionJankMonitorLazy.get();
- if (monitor == null) {
- return;
- }
- monitor.end(getCujType());
- }
-
- private void cancelInteractionJankMonitor() {
- InteractionJankMonitor monitor = mInteractionJankMonitorLazy.get();
- if (monitor == null) {
- return;
- }
- monitor.cancel(getCujType());
- }
-
- private int getCujType() {
- return mIsDozing ? CUJ_LOCKSCREEN_TRANSITION_TO_AOD : CUJ_LOCKSCREEN_TRANSITION_FROM_AOD;
- }
@Override
public boolean goingToFullShade() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndCastScreenToOtherDeviceDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndCastScreenToOtherDeviceDialogDelegate.kt
index 6ea72b97cb3a..521539866c9c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndCastScreenToOtherDeviceDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndCastScreenToOtherDeviceDialogDelegate.kt
@@ -18,6 +18,7 @@ package com.android.systemui.statusbar.chips.casttootherdevice.ui.view
import android.content.Context
import android.os.Bundle
+import android.view.View
import com.android.systemui.mediaprojection.data.model.MediaProjectionState
import com.android.systemui.res.R
import com.android.systemui.statusbar.chips.casttootherdevice.ui.viewmodel.CastToOtherDeviceChipViewModel.Companion.CAST_TO_OTHER_DEVICE_ICON
@@ -48,6 +49,11 @@ class EndCastScreenToOtherDeviceDialogDelegate(
R.string.cast_to_other_device_stop_dialog_button,
endMediaProjectionDialogHelper.wrapStopAction(stopAction),
)
+ if (com.android.media.projection.flags.Flags.showStopDialogPostCallEnd()) {
+ window
+ ?.decorView
+ ?.setAccessibilityDataSensitive(View.ACCESSIBILITY_DATA_SENSITIVE_YES)
+ }
}
}
@@ -82,9 +88,7 @@ class EndCastScreenToOtherDeviceDialogDelegate(
hostDeviceName,
)
} else {
- context.getString(
- R.string.cast_to_other_device_stop_dialog_message_entire_screen,
- )
+ context.getString(R.string.cast_to_other_device_stop_dialog_message_entire_screen)
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndGenericCastToOtherDeviceDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndGenericCastToOtherDeviceDialogDelegate.kt
index b0c832172776..8644c53f7849 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndGenericCastToOtherDeviceDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndGenericCastToOtherDeviceDialogDelegate.kt
@@ -18,6 +18,7 @@ package com.android.systemui.statusbar.chips.casttootherdevice.ui.view
import android.content.Context
import android.os.Bundle
+import android.view.View
import com.android.systemui.res.R
import com.android.systemui.statusbar.chips.casttootherdevice.ui.viewmodel.CastToOtherDeviceChipViewModel.Companion.CAST_TO_OTHER_DEVICE_ICON
import com.android.systemui.statusbar.chips.mediaprojection.ui.view.EndMediaProjectionDialogHelper
@@ -59,6 +60,11 @@ class EndGenericCastToOtherDeviceDialogDelegate(
R.string.cast_to_other_device_stop_dialog_button,
endMediaProjectionDialogHelper.wrapStopAction(stopAction),
)
+ if (com.android.media.projection.flags.Flags.showStopDialogPostCallEnd()) {
+ window
+ ?.decorView
+ ?.setAccessibilityDataSensitive(View.ACCESSIBILITY_DATA_SENSITIVE_YES)
+ }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/domain/model/MediaProjectionStopDialogModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/domain/model/MediaProjectionStopDialogModel.kt
index b37c76232f01..52f55fca55bb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/domain/model/MediaProjectionStopDialogModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/domain/model/MediaProjectionStopDialogModel.kt
@@ -34,6 +34,13 @@ sealed interface MediaProjectionStopDialogModel {
*/
fun createAndShowDialog() {
val dialog = dialogDelegate.createDialog()
+ // Prevents the dialog from being dismissed by tapping outside its boundary.
+ // This is specifically required for the stop dialog shown at call end (i.e.,
+ // PROJECTION_STARTED_DURING_CALL_AND_ACTIVE_POST_CALL event) to disallow remote
+ // dismissal by external devices. Other media projection stop dialogs do not require
+ // this since they are triggered explicitly by tapping the status bar chip, in which
+ // case the full screen containing the dialog is not remote dismissible.
+ dialog.setCanceledOnTouchOutside(/* cancel= */ false)
dialog.setOnCancelListener { onDismissAction.invoke() }
dialog.setOnDismissListener { onDismissAction.invoke() }
dialog.show()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/ui/view/EndScreenRecordingDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/ui/view/EndScreenRecordingDialogDelegate.kt
index 72656ca1934c..4e0117ea3709 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/ui/view/EndScreenRecordingDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/ui/view/EndScreenRecordingDialogDelegate.kt
@@ -19,6 +19,7 @@ package com.android.systemui.statusbar.chips.screenrecord.ui.view
import android.app.ActivityManager
import android.content.Context
import android.os.Bundle
+import android.view.View
import com.android.systemui.res.R
import com.android.systemui.statusbar.chips.mediaprojection.ui.view.EndMediaProjectionDialogHelper
import com.android.systemui.statusbar.chips.screenrecord.ui.viewmodel.ScreenRecordChipViewModel
@@ -56,6 +57,11 @@ class EndScreenRecordingDialogDelegate(
R.string.screenrecord_stop_dialog_button,
endMediaProjectionDialogHelper.wrapStopAction(stopAction),
)
+ if (com.android.media.projection.flags.Flags.showStopDialogPostCallEnd()) {
+ window
+ ?.decorView
+ ?.setAccessibilityDataSensitive(View.ACCESSIBILITY_DATA_SENSITIVE_YES)
+ }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/sharetoapp/ui/view/EndGenericShareToAppDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/sharetoapp/ui/view/EndGenericShareToAppDialogDelegate.kt
index 8ec05677107e..b8db6136e4c4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/sharetoapp/ui/view/EndGenericShareToAppDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/sharetoapp/ui/view/EndGenericShareToAppDialogDelegate.kt
@@ -18,6 +18,7 @@ package com.android.systemui.statusbar.chips.sharetoapp.ui.view
import android.content.Context
import android.os.Bundle
+import android.view.View
import com.android.systemui.res.R
import com.android.systemui.statusbar.chips.mediaprojection.ui.view.EndMediaProjectionDialogHelper
import com.android.systemui.statusbar.chips.sharetoapp.ui.viewmodel.ShareToAppChipViewModel.Companion.SHARE_TO_APP_ICON
@@ -49,6 +50,11 @@ class EndGenericShareToAppDialogDelegate(
R.string.share_to_app_stop_dialog_button,
endMediaProjectionDialogHelper.wrapStopAction(stopAction),
)
+ if (com.android.media.projection.flags.Flags.showStopDialogPostCallEnd()) {
+ window
+ ?.decorView
+ ?.setAccessibilityDataSensitive(View.ACCESSIBILITY_DATA_SENSITIVE_YES)
+ }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/sharetoapp/ui/view/EndShareScreenToAppDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/sharetoapp/ui/view/EndShareScreenToAppDialogDelegate.kt
index 053016e3109d..11a15555aef1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/sharetoapp/ui/view/EndShareScreenToAppDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/sharetoapp/ui/view/EndShareScreenToAppDialogDelegate.kt
@@ -18,6 +18,7 @@ package com.android.systemui.statusbar.chips.sharetoapp.ui.view
import android.content.Context
import android.os.Bundle
+import android.view.View
import com.android.systemui.mediaprojection.data.model.MediaProjectionState
import com.android.systemui.res.R
import com.android.systemui.statusbar.chips.mediaprojection.domain.model.ProjectionChipModel
@@ -48,6 +49,11 @@ class EndShareScreenToAppDialogDelegate(
R.string.share_to_app_stop_dialog_button,
endMediaProjectionDialogHelper.wrapStopAction(stopAction),
)
+ if (com.android.media.projection.flags.Flags.showStopDialogPostCallEnd()) {
+ window
+ ?.decorView
+ ?.setAccessibilityDataSensitive(View.ACCESSIBILITY_DATA_SENSITIVE_YES)
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/ChipContent.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/ChipContent.kt
index 375e02989a3d..cf2ec47a36d8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/ChipContent.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/ChipContent.kt
@@ -16,12 +16,20 @@
package com.android.systemui.statusbar.chips.ui.compose
-import androidx.compose.foundation.layout.padding
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.drawWithCache
+import androidx.compose.ui.graphics.BlendMode
+import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.CompositingStrategy
+import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.layout.Measurable
import androidx.compose.ui.layout.MeasureResult
import androidx.compose.ui.layout.MeasureScope
@@ -29,12 +37,15 @@ import androidx.compose.ui.node.LayoutModifierNode
import androidx.compose.ui.node.ModifierNodeElement
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.dimensionResource
+import androidx.compose.ui.text.rememberTextMeasurer
import androidx.compose.ui.unit.Constraints
+import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.constrain
import androidx.compose.ui.unit.dp
import com.android.systemui.res.R
import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
import com.android.systemui.statusbar.chips.ui.viewmodel.rememberChronometerState
+import kotlin.math.min
@Composable
fun ChipContent(viewModel: OngoingActivityChipModel.Shown, modifier: Modifier = Modifier) {
@@ -43,6 +54,9 @@ fun ChipContent(viewModel: OngoingActivityChipModel.Shown, modifier: Modifier =
val hasEmbeddedIcon =
viewModel.icon is OngoingActivityChipModel.ChipIcon.StatusBarView ||
viewModel.icon is OngoingActivityChipModel.ChipIcon.StatusBarNotificationIcon
+ val textStyle = MaterialTheme.typography.labelLarge
+ val textColor = Color(viewModel.colors.text(context))
+ val maxTextWidth = dimensionResource(id = R.dimen.ongoing_activity_chip_max_text_width)
val startPadding =
if (isTextOnly || hasEmbeddedIcon) {
0.dp
@@ -57,38 +71,69 @@ fun ChipContent(viewModel: OngoingActivityChipModel.Shown, modifier: Modifier =
} else {
0.dp
}
- val textStyle = MaterialTheme.typography.labelLarge
- val textColor = Color(viewModel.colors.text(context))
+ val textMeasurer = rememberTextMeasurer()
when (viewModel) {
is OngoingActivityChipModel.Shown.Timer -> {
val timerState = rememberChronometerState(startTimeMillis = viewModel.startTimeMs)
+ val text = timerState.currentTimeText
Text(
- text = timerState.currentTimeText,
+ text = text,
style = textStyle,
color = textColor,
+ softWrap = false,
modifier =
- modifier.padding(start = startPadding, end = endPadding).neverDecreaseWidth(),
+ modifier
+ .customTextContentLayout(
+ maxTextWidth = maxTextWidth,
+ startPadding = startPadding,
+ endPadding = endPadding,
+ ) { constraintWidth ->
+ val intrinsicWidth =
+ textMeasurer.measure(text, textStyle, softWrap = false).size.width
+ intrinsicWidth <= constraintWidth
+ }
+ .neverDecreaseWidth(),
)
}
is OngoingActivityChipModel.Shown.Countdown -> {
- ChipText(
- text = viewModel.secondsUntilStarted.toString(),
+ val text = viewModel.secondsUntilStarted.toString()
+ Text(
+ text = text,
style = textStyle,
color = textColor,
- modifier =
- modifier.padding(start = startPadding, end = endPadding).neverDecreaseWidth(),
- backgroundColor = Color(viewModel.colors.background(context).defaultColor),
+ softWrap = false,
+ modifier = modifier.neverDecreaseWidth(),
)
}
is OngoingActivityChipModel.Shown.Text -> {
- ChipText(
- text = viewModel.text,
- style = textStyle,
+ var hasOverflow by remember { mutableStateOf(false) }
+ val text = viewModel.text
+ Text(
+ text = text,
color = textColor,
- modifier = modifier.padding(start = startPadding, end = endPadding),
- backgroundColor = Color(viewModel.colors.background(context).defaultColor),
+ style = textStyle,
+ softWrap = false,
+ modifier =
+ modifier
+ .customTextContentLayout(
+ maxTextWidth = maxTextWidth,
+ startPadding = startPadding,
+ endPadding = endPadding,
+ ) { constraintWidth ->
+ val intrinsicWidth =
+ textMeasurer.measure(text, textStyle, softWrap = false).size.width
+ hasOverflow = intrinsicWidth > constraintWidth
+ constraintWidth.toFloat() / intrinsicWidth.toFloat() > 0.5f
+ }
+ .overflowFadeOut(
+ hasOverflow = { hasOverflow },
+ fadeLength =
+ dimensionResource(
+ id = R.dimen.ongoing_activity_chip_text_fading_edge_length
+ ),
+ ),
)
}
@@ -133,3 +178,83 @@ private class NeverDecreaseWidthNode : Modifier.Node(), LayoutModifierNode {
return layout(width, height) { placeable.place(0, 0) }
}
}
+
+/**
+ * A custom layout modifier for text that ensures its text is only visible if a provided
+ * [shouldShow] callback returns true. Imposes a provided [maxTextWidthPx]. Also, accounts for
+ * provided padding values if provided and ensures its text is placed with the provided padding
+ * included around it.
+ */
+private fun Modifier.customTextContentLayout(
+ maxTextWidth: Dp,
+ startPadding: Dp = 0.dp,
+ endPadding: Dp = 0.dp,
+ shouldShow: (constraintWidth: Int) -> Boolean,
+): Modifier {
+ return this.then(
+ CustomTextContentLayoutElement(maxTextWidth, startPadding, endPadding, shouldShow)
+ )
+}
+
+private data class CustomTextContentLayoutElement(
+ val maxTextWidth: Dp,
+ val startPadding: Dp,
+ val endPadding: Dp,
+ val shouldShow: (constrainedWidth: Int) -> Boolean,
+) : ModifierNodeElement<CustomTextContentLayoutNode>() {
+ override fun create(): CustomTextContentLayoutNode {
+ return CustomTextContentLayoutNode(maxTextWidth, startPadding, endPadding, shouldShow)
+ }
+
+ override fun update(node: CustomTextContentLayoutNode) {
+ node.shouldShow = shouldShow
+ node.maxTextWidth = maxTextWidth
+ node.startPadding = startPadding
+ node.endPadding = endPadding
+ }
+}
+
+private class CustomTextContentLayoutNode(
+ var maxTextWidth: Dp,
+ var startPadding: Dp,
+ var endPadding: Dp,
+ var shouldShow: (constrainedWidth: Int) -> Boolean,
+) : Modifier.Node(), LayoutModifierNode {
+ override fun MeasureScope.measure(
+ measurable: Measurable,
+ constraints: Constraints,
+ ): MeasureResult {
+ val horizontalPadding = startPadding + endPadding
+ val maxWidth =
+ min(maxTextWidth.roundToPx(), (constraints.maxWidth - horizontalPadding.roundToPx()))
+ .coerceAtLeast(constraints.minWidth)
+ val placeable = measurable.measure(constraints.copy(maxWidth = maxWidth))
+
+ val height = placeable.height
+ val width = placeable.width
+ return if (shouldShow(maxWidth)) {
+ layout(width + horizontalPadding.roundToPx(), height) {
+ placeable.place(startPadding.roundToPx(), 0)
+ }
+ } else {
+ layout(0, 0) {}
+ }
+ }
+}
+
+private fun Modifier.overflowFadeOut(hasOverflow: () -> Boolean, fadeLength: Dp): Modifier {
+ return graphicsLayer(compositingStrategy = CompositingStrategy.Offscreen).drawWithCache {
+ val width = size.width
+ val start = (width - fadeLength.toPx()).coerceAtLeast(0f)
+ val gradient =
+ Brush.horizontalGradient(
+ colors = listOf(Color.Black, Color.Transparent),
+ startX = start,
+ endX = width,
+ )
+ onDrawWithContent {
+ drawContent()
+ if (hasOverflow()) drawRect(brush = gradient, blendMode = BlendMode.DstIn)
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/ChipText.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/ChipText.kt
deleted file mode 100644
index 3d768d2d3e1e..000000000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/ChipText.kt
+++ /dev/null
@@ -1,114 +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.statusbar.chips.ui.compose
-
-import androidx.compose.foundation.layout.sizeIn
-import androidx.compose.material3.LocalTextStyle
-import androidx.compose.material3.Text
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.remember
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.draw.drawWithContent
-import androidx.compose.ui.geometry.Offset
-import androidx.compose.ui.geometry.Size
-import androidx.compose.ui.graphics.Brush
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.platform.LocalDensity
-import androidx.compose.ui.res.dimensionResource
-import androidx.compose.ui.text.TextLayoutResult
-import androidx.compose.ui.text.TextStyle
-import androidx.compose.ui.text.rememberTextMeasurer
-import com.android.systemui.res.R
-
-/**
- * Renders text within a status bar chip. The text is only displayed if more than 50% of its width
- * can fit inside the bounds of the chip. If there is any overflow,
- * [R.dimen.ongoing_activity_chip_text_fading_edge_length] is used to fade out the edge of the text.
- */
-@Composable
-fun ChipText(
- text: String,
- backgroundColor: Color,
- modifier: Modifier = Modifier,
- color: Color = Color.Unspecified,
- style: TextStyle = LocalTextStyle.current,
- minimumVisibleRatio: Float = 0.5f,
-) {
- val density = LocalDensity.current
- val textMeasurer = rememberTextMeasurer()
-
- val textFadeLength =
- dimensionResource(id = R.dimen.ongoing_activity_chip_text_fading_edge_length)
- val maxTextWidthDp = dimensionResource(id = R.dimen.ongoing_activity_chip_max_text_width)
- val maxTextWidthPx = with(density) { maxTextWidthDp.toPx() }
-
- val textLayoutResult = remember(text, style) { textMeasurer.measure(text, style) }
- val willOverflowWidth = textLayoutResult.size.width > maxTextWidthPx
-
- if (isSufficientlyVisible(maxTextWidthPx, minimumVisibleRatio, textLayoutResult)) {
- Text(
- text = text,
- style = style,
- softWrap = false,
- color = color,
- modifier =
- modifier
- .sizeIn(maxWidth = maxTextWidthDp)
- .then(
- if (willOverflowWidth) {
- Modifier.overflowFadeOut(
- with(density) { textFadeLength.roundToPx() },
- backgroundColor,
- )
- } else {
- Modifier
- }
- ),
- )
- }
-}
-
-private fun Modifier.overflowFadeOut(fadeLength: Int, color: Color): Modifier = drawWithContent {
- drawContent()
-
- val brush =
- Brush.horizontalGradient(
- colors = listOf(Color.Transparent, color),
- startX = size.width - fadeLength,
- endX = size.width,
- )
- drawRect(
- brush = brush,
- topLeft = Offset(size.width - fadeLength, 0f),
- size = Size(fadeLength.toFloat(), size.height),
- )
-}
-
-/**
- * Returns `true` if at least [minimumVisibleRatio] of the text width fits within the given
- * [maxAvailableWidthPx].
- */
-@Composable
-private fun isSufficientlyVisible(
- maxAvailableWidthPx: Float,
- minimumVisibleRatio: Float,
- textLayoutResult: TextLayoutResult,
-): Boolean {
- val widthPx = textLayoutResult.size.width
-
- return (maxAvailableWidthPx / widthPx) > minimumVisibleRatio
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/OngoingActivityChip.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/OngoingActivityChip.kt
index 816f291b9273..b49d46c4a05b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/OngoingActivityChip.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/OngoingActivityChip.kt
@@ -35,6 +35,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Shape
+import androidx.compose.ui.layout.layout
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.dimensionResource
import androidx.compose.ui.semantics.contentDescription
@@ -42,6 +43,7 @@ import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.unit.dp
import androidx.compose.ui.viewinterop.AndroidView
import com.android.compose.animation.Expandable
+import com.android.compose.modifiers.thenIf
import com.android.systemui.animation.Expandable
import com.android.systemui.common.ui.compose.Icon
import com.android.systemui.common.ui.compose.load
@@ -79,12 +81,11 @@ fun OngoingActivityChip(model: OngoingActivityChipModel.Shown, modifier: Modifie
private fun ChipBody(
model: OngoingActivityChipModel.Shown,
modifier: Modifier = Modifier,
- onClick: () -> Unit = {},
+ onClick: (() -> Unit)? = null,
) {
val context = LocalContext.current
- val isClickable = onClick != {}
+ val isClickable = onClick != null
val hasEmbeddedIcon = model.icon is OngoingActivityChipModel.ChipIcon.StatusBarView
-
val contentDescription =
when (val icon = model.icon) {
is OngoingActivityChipModel.ChipIcon.StatusBarView -> icon.contentDescription.load()
@@ -93,17 +94,28 @@ private fun ChipBody(
is OngoingActivityChipModel.ChipIcon.SingleColorIcon -> null
null -> null
}
-
+ val chipSidePadding = dimensionResource(id = R.dimen.ongoing_activity_chip_side_padding)
+ val minWidth =
+ if (isClickable) {
+ dimensionResource(id = R.dimen.min_clickable_item_size)
+ } else if (model.icon != null) {
+ dimensionResource(id = R.dimen.ongoing_activity_chip_icon_size) + chipSidePadding
+ } else {
+ dimensionResource(id = R.dimen.ongoing_activity_chip_min_text_width) + chipSidePadding
+ }
// Use a Box with `fillMaxHeight` to create a larger click surface for the chip. The visible
// height of the chip is determined by the height of the background of the Row below.
Box(
contentAlignment = Alignment.Center,
modifier =
- modifier.fillMaxHeight().clickable(enabled = isClickable, onClick = onClick).semantics {
- if (contentDescription != null) {
- this.contentDescription = contentDescription
- }
- },
+ modifier
+ .fillMaxHeight()
+ .clickable(enabled = isClickable, onClick = onClick ?: {})
+ .semantics {
+ if (contentDescription != null) {
+ this.contentDescription = contentDescription
+ }
+ },
) {
Row(
horizontalArrangement = Arrangement.Center,
@@ -115,14 +127,15 @@ private fun ChipBody(
)
)
.height(dimensionResource(R.dimen.ongoing_appops_chip_height))
- .widthIn(
- min =
- if (isClickable) {
- dimensionResource(id = R.dimen.min_clickable_item_size)
- } else {
- 0.dp
+ .thenIf(isClickable) { Modifier.widthIn(min = minWidth) }
+ .layout { measurable, constraints ->
+ val placeable = measurable.measure(constraints)
+ layout(placeable.width, placeable.height) {
+ if (constraints.maxWidth >= minWidth.roundToPx()) {
+ placeable.place(0, 0)
}
- )
+ }
+ }
.background(Color(model.colors.background(context).defaultColor))
.padding(
horizontal =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/AODPromotedNotification.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/AODPromotedNotification.kt
index 33c71d4a9c5a..098d537fc225 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/AODPromotedNotification.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/AODPromotedNotification.kt
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.notification.promoted
import android.app.Flags
+import android.app.Notification
import android.view.LayoutInflater
import android.view.View
import android.view.View.GONE
@@ -49,6 +50,7 @@ import com.android.internal.widget.CachingIconView
import com.android.internal.widget.ImageFloatingTextView
import com.android.internal.widget.NotificationExpandButton
import com.android.internal.widget.NotificationProgressBar
+import com.android.internal.widget.NotificationProgressModel
import com.android.internal.widget.NotificationRowIconView
import com.android.systemui.lifecycle.rememberViewModel
import com.android.systemui.res.R as systemuiR
@@ -176,17 +178,17 @@ private class AODPromotedNotificationViewUpdater(root: View) {
private val verificationIcon: ImageView? = root.findViewById(R.id.verification_icon)
private val verificationText: TextView? = root.findViewById(R.id.verification_text)
- private var oldProgressStub = root.findViewById<View>(R.id.progress) as? ViewStub
- private var oldProgress: ProgressBar? = null
- private val newProgress = root.findViewById<View>(R.id.progress) as? NotificationProgressBar
+ private var oldProgressBarStub = root.findViewById<View>(R.id.progress) as? ViewStub
+ private var oldProgressBar: ProgressBar? = null
+ private val newProgressBar = root.findViewById<View>(R.id.progress) as? NotificationProgressBar
fun update(content: PromotedNotificationContentModel) {
when (content.style) {
Style.Base -> updateBase(content)
- Style.BigPicture -> updateBigPicture(content)
- Style.BigText -> updateBigText(content)
- Style.Call -> updateCall(content)
- Style.Progress -> updateProgress(content)
+ Style.BigPicture -> updateBigPictureStyle(content)
+ Style.BigText -> updateBigTextStyle(content)
+ Style.Call -> updateCallStyle(content)
+ Style.Progress -> updateProgressStyle(content)
Style.Ineligible -> {}
}
}
@@ -194,42 +196,72 @@ private class AODPromotedNotificationViewUpdater(root: View) {
private fun updateBase(
content: PromotedNotificationContentModel,
textView: ImageFloatingTextView? = null,
+ showOldProgress: Boolean = true,
) {
updateHeader(content)
updateTitle(title, content)
updateText(textView ?: text, content)
+
+ if (showOldProgress) {
+ updateOldProgressBar(content)
+ }
}
- private fun updateBigPicture(content: PromotedNotificationContentModel) {
+ private fun updateBigPictureStyle(content: PromotedNotificationContentModel) {
updateBase(content)
bigPicture?.visibility = GONE
}
- private fun updateBigText(content: PromotedNotificationContentModel) {
+ private fun updateBigTextStyle(content: PromotedNotificationContentModel) {
updateBase(content, textView = bigText)
}
- private fun updateCall(content: PromotedNotificationContentModel) {
+ private fun updateCallStyle(content: PromotedNotificationContentModel) {
updateConversationHeader(content)
updateText(text, content)
}
- private fun updateProgress(content: PromotedNotificationContentModel) {
- updateBase(content)
+ private fun updateProgressStyle(content: PromotedNotificationContentModel) {
+ updateBase(content, showOldProgress = false)
updateNewProgressBar(content)
}
+ private fun updateOldProgressBar(content: PromotedNotificationContentModel) {
+ if (
+ content.oldProgress == null ||
+ content.oldProgress.max == 0 ||
+ content.oldProgress.isIndeterminate
+ ) {
+ oldProgressBar?.visibility = GONE
+ return
+ }
+
+ inflateOldProgressBar()
+
+ val oldProgressBar = oldProgressBar ?: return
+
+ oldProgressBar.progress = content.oldProgress.progress
+ oldProgressBar.max = content.oldProgress.max
+ oldProgressBar.isIndeterminate = content.oldProgress.isIndeterminate
+ oldProgressBar.visibility = VISIBLE
+ }
+
private fun updateNewProgressBar(content: PromotedNotificationContentModel) {
notificationProgressStartIcon?.visibility = GONE
notificationProgressEndIcon?.visibility = GONE
- content.progress?.let {
- newProgress?.setProgressModel(it.toBundle())
- newProgress?.visibility = VISIBLE
- } ?: run { newProgress?.visibility = GONE }
+
+ val newProgressBar = newProgressBar ?: return
+
+ if (content.newProgress != null && !content.newProgress.isIndeterminate) {
+ newProgressBar.setProgressModel(content.newProgress.toSkeleton().toBundle())
+ newProgressBar.visibility = VISIBLE
+ } else {
+ newProgressBar.visibility = GONE
+ }
}
private fun updateHeader(content: PromotedNotificationContentModel) {
@@ -335,6 +367,15 @@ private class AODPromotedNotificationViewUpdater(root: View) {
chronometerStub = null
}
+ private fun inflateOldProgressBar() {
+ if (oldProgressBar != null) {
+ return
+ }
+
+ oldProgressBar = oldProgressBarStub?.inflate() as ProgressBar
+ oldProgressBarStub = null
+ }
+
private fun updateText(
view: ImageFloatingTextView?,
content: PromotedNotificationContentModel,
@@ -351,7 +392,7 @@ private class AODPromotedNotificationViewUpdater(root: View) {
setTextViewColor(view, color)
if (text != null && text.isNotEmpty()) {
- view?.text = text
+ view?.text = text.toSkeleton()
view?.visibility = VISIBLE
} else {
view?.text = ""
@@ -364,6 +405,38 @@ private class AODPromotedNotificationViewUpdater(root: View) {
}
}
+private fun CharSequence.toSkeleton(): CharSequence {
+ return this.toString()
+}
+
+private fun NotificationProgressModel.toSkeleton(): NotificationProgressModel {
+ if (isIndeterminate) {
+ return NotificationProgressModel(/* indeterminateColor= */ SecondaryText.colorInt)
+ }
+
+ return NotificationProgressModel(
+ listOf(Notification.ProgressStyle.Segment(progressMax).toSkeleton()),
+ points.map { it.toSkeleton() }.toList(),
+ progress,
+ /* isStyledByProgress = */ true,
+ /* segmentsFallbackColor = */ SecondaryText.colorInt,
+ )
+}
+
+private fun Notification.ProgressStyle.Segment.toSkeleton(): Notification.ProgressStyle.Segment {
+ return Notification.ProgressStyle.Segment(length).also {
+ it.id = id
+ it.color = SecondaryText.colorInt
+ }
+}
+
+private fun Notification.ProgressStyle.Point.toSkeleton(): Notification.ProgressStyle.Point {
+ return Notification.ProgressStyle.Point(position).also {
+ it.id = id
+ it.color = SecondaryText.colorInt
+ }
+}
+
private enum class AodPromotedNotificationColor(colorUInt: UInt) {
Background(0x00000000u),
PrimaryText(0xFFFFFFFFu),
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 24d071c83a5e..035edd9711bd 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
@@ -21,6 +21,9 @@ import android.app.Notification.BigPictureStyle
import android.app.Notification.BigTextStyle
import android.app.Notification.CallStyle
import android.app.Notification.EXTRA_CHRONOMETER_COUNT_DOWN
+import android.app.Notification.EXTRA_PROGRESS
+import android.app.Notification.EXTRA_PROGRESS_INDETERMINATE
+import android.app.Notification.EXTRA_PROGRESS_MAX
import android.app.Notification.EXTRA_SUB_TEXT
import android.app.Notification.EXTRA_TEXT
import android.app.Notification.EXTRA_TITLE
@@ -34,6 +37,7 @@ import com.android.systemui.statusbar.notification.promoted.AutomaticPromotionCo
import com.android.systemui.statusbar.notification.promoted.AutomaticPromotionCoordinator.Companion.EXTRA_WAS_AUTOMATICALLY_PROMOTED
import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel
import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel.Companion.isPromotedForStatusBarChip
+import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel.OldProgress
import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel.Style
import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel.When
import javax.inject.Inject
@@ -90,6 +94,7 @@ constructor(
contentBuilder.title = notification.title()
contentBuilder.text = notification.text()
contentBuilder.skeletonLargeIcon = null // TODO
+ contentBuilder.oldProgress = notification.oldProgress()
val colorsFromNotif = recoveredBuilder.getColors(/* header= */ false)
contentBuilder.colors =
@@ -126,6 +131,21 @@ private fun Notification.shortCriticalText(): String? {
private fun Notification.chronometerCountDown(): Boolean =
extras?.getBoolean(EXTRA_CHRONOMETER_COUNT_DOWN, /* defaultValue= */ false) ?: false
+private fun Notification.oldProgress(): OldProgress? {
+ val progress = progress() ?: return null
+ val max = progressMax() ?: return null
+ val isIndeterminate = progressIndeterminate() ?: return null
+
+ return OldProgress(progress = progress, max = max, isIndeterminate = isIndeterminate)
+}
+
+private fun Notification.progress(): Int? = extras?.getInt(EXTRA_PROGRESS)
+
+private fun Notification.progressMax(): Int? = extras?.getInt(EXTRA_PROGRESS_MAX)
+
+private fun Notification.progressIndeterminate(): Boolean? =
+ extras?.getBoolean(EXTRA_PROGRESS_INDETERMINATE)
+
private fun Notification.extractWhen(): When? {
val time = `when`
val showsTime = showsTime()
@@ -191,5 +211,5 @@ private fun CallStyle.extractContent(contentBuilder: PromotedNotificationContent
private fun ProgressStyle.extractContent(contentBuilder: PromotedNotificationContentModel.Builder) {
// TODO: Create NotificationProgressModel.toSkeleton, or something similar.
- contentBuilder.progress = createProgressModel(0xffffffff.toInt(), 0xff000000.toInt())
+ contentBuilder.newProgress = createProgressModel(0xffffffff.toInt(), 0xff000000.toInt())
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationLogger.kt
index 4ccdc65ba91a..468934791525 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationLogger.kt
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.notification.promoted
import androidx.constraintlayout.widget.ConstraintSet
+import com.android.systemui.keyguard.ui.view.layout.sections.AodPromotedNotificationSection
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.core.LogLevel.ERROR
import com.android.systemui.log.core.LogLevel.INFO
@@ -25,6 +26,7 @@ import com.android.systemui.statusbar.notification.logKey
import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel
import javax.inject.Inject
+@OptIn(ExperimentalStdlibApi::class)
class PromotedNotificationLogger
@Inject
constructor(@PromotedNotificationLog private val buffer: LogBuffer) {
@@ -92,20 +94,44 @@ constructor(@PromotedNotificationLog private val buffer: LogBuffer) {
buffer.log(AOD_VIEW_BINDER_TAG, INFO, "binder unbound notification")
}
- fun logSectionAddedViews() {
- buffer.log(AOD_SECTION_TAG, INFO, "section added views")
+ fun logSectionCreated(section: AodPromotedNotificationSection) {
+ buffer.log(
+ AOD_SECTION_TAG,
+ INFO,
+ "section ${System.identityHashCode(section).toHexString()} created",
+ )
}
- fun logSectionBoundData() {
- buffer.log(AOD_SECTION_TAG, INFO, "section bound data")
+ fun logSectionAddedViews(section: AodPromotedNotificationSection) {
+ buffer.log(
+ AOD_SECTION_TAG,
+ INFO,
+ "section ${System.identityHashCode(section).toHexString()} added views",
+ )
}
- fun logSectionAppliedConstraints() {
- buffer.log(AOD_SECTION_TAG, INFO, "section applied constraints")
+ fun logSectionBoundData(section: AodPromotedNotificationSection) {
+ buffer.log(
+ AOD_SECTION_TAG,
+ INFO,
+ "section ${System.identityHashCode(section).toHexString()} bound data",
+ )
}
- fun logSectionRemovedViews() {
- buffer.log(AOD_SECTION_TAG, INFO, "section removed views")
+ fun logSectionAppliedConstraints(section: AodPromotedNotificationSection) {
+ buffer.log(
+ AOD_SECTION_TAG,
+ INFO,
+ "section ${System.identityHashCode(section).toHexString()} applied constraints",
+ )
+ }
+
+ fun logSectionRemovedViews(section: AodPromotedNotificationSection) {
+ buffer.log(
+ AOD_SECTION_TAG,
+ INFO,
+ "section ${System.identityHashCode(section).toHexString()} removed views",
+ )
}
}
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 3dacae2114b0..58da5286dd71 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
@@ -51,6 +51,7 @@ data class PromotedNotificationContentModel(
val title: CharSequence?,
val text: CharSequence?,
val skeletonLargeIcon: Icon?, // TODO(b/377568176): Make into an IconModel.
+ val oldProgress: OldProgress?,
val colors: Colors,
val style: Style,
@@ -61,7 +62,7 @@ data class PromotedNotificationContentModel(
val verificationText: CharSequence?,
// for ProgressStyle:
- val progress: NotificationProgressModel?,
+ val newProgress: NotificationProgressModel?,
) {
class Builder(val key: String) {
var wasPromotedAutomatically: Boolean = false
@@ -75,6 +76,7 @@ data class PromotedNotificationContentModel(
var title: CharSequence? = null
var text: CharSequence? = null
var skeletonLargeIcon: Icon? = null
+ var oldProgress: OldProgress? = null
var style: Style = Style.Ineligible
var colors: Colors = Colors(backgroundColor = 0, primaryTextColor = 0)
@@ -85,7 +87,7 @@ data class PromotedNotificationContentModel(
var verificationText: CharSequence? = null
// for ProgressStyle:
- var progress: NotificationProgressModel? = null
+ var newProgress: NotificationProgressModel? = null
fun build() =
PromotedNotificationContentModel(
@@ -101,13 +103,14 @@ data class PromotedNotificationContentModel(
title = title,
text = text,
skeletonLargeIcon = skeletonLargeIcon,
+ oldProgress = oldProgress,
colors = colors,
style = style,
personIcon = personIcon,
personName = personName,
verificationIcon = verificationIcon,
verificationText = verificationText,
- progress = progress,
+ newProgress = newProgress,
)
}
@@ -129,6 +132,9 @@ data class PromotedNotificationContentModel(
/** The colors used to display the notification. */
data class Colors(@ColorInt val backgroundColor: Int, @ColorInt val primaryTextColor: Int)
+ /** The fields needed to render the old-style progress bar. */
+ data class OldProgress(val progress: Int, val max: Int, val isIndeterminate: Boolean)
+
/** The promotion-eligible style of a notification, or [Style.Ineligible] if not. */
enum class Style {
Base, // style == null
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/ui/viewmodel/PromotedNotificationViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/ui/viewmodel/PromotedNotificationViewModel.kt
index f265e0ff33f8..56057fb00e45 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/ui/viewmodel/PromotedNotificationViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/ui/viewmodel/PromotedNotificationViewModel.kt
@@ -54,5 +54,5 @@ class PromotedNotificationViewModel(
val verificationText: Flow<CharSequence?> = content.map { it.verificationText }
// for ProgressStyle:
- val progress: Flow<NotificationProgressModel?> = content.map { it.progress }
+ val progress: Flow<NotificationProgressModel?> = content.map { it.newProgress }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt
index 5a63c0cd84e6..bd1d7f755a74 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt
@@ -30,6 +30,7 @@ import com.android.systemui.statusbar.SysuiStatusBarStateController
import com.android.systemui.util.concurrency.DelayableExecutor
import dagger.Lazy
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
/** Handles start activity logic in SystemUI. */
@SysUISingleton
@@ -52,9 +53,10 @@ constructor(
override fun registerTransition(
cookie: ActivityTransitionAnimator.TransitionCookie,
controllerFactory: ActivityTransitionAnimator.ControllerFactory,
+ scope: CoroutineScope,
) {
if (!TransitionAnimator.longLivedReturnAnimationsEnabled()) return
- activityStarterInternal.registerTransition(cookie, controllerFactory)
+ activityStarterInternal.registerTransition(cookie, controllerFactory, scope)
}
override fun unregisterTransition(cookie: ActivityTransitionAnimator.TransitionCookie) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterInternal.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterInternal.kt
index 5e427fbf1f7e..015ec3052134 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterInternal.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterInternal.kt
@@ -25,15 +25,17 @@ import android.view.View
import com.android.systemui.ActivityIntentHelper
import com.android.systemui.animation.ActivityTransitionAnimator
import com.android.systemui.plugins.ActivityStarter
+import kotlinx.coroutines.CoroutineScope
interface ActivityStarterInternal {
/**
* Registers the given [controllerFactory] for launching and closing transitions matching the
- * [cookie] and the [ComponentName] that it contains.
+ * [cookie] and the [ComponentName] that it contains, within the given [scope].
*/
fun registerTransition(
cookie: ActivityTransitionAnimator.TransitionCookie,
controllerFactory: ActivityTransitionAnimator.ControllerFactory,
+ scope: CoroutineScope,
)
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterInternalImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterInternalImpl.kt
index 7289c2ed5897..6e82d7f7401a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterInternalImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterInternalImpl.kt
@@ -66,6 +66,7 @@ import com.android.systemui.util.kotlin.getOrNull
import dagger.Lazy
import java.util.Optional
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
/**
* Encapsulates the activity logic for activity starter when the SceneContainerFlag is enabled.
@@ -105,6 +106,7 @@ constructor(
override fun registerTransition(
cookie: ActivityTransitionAnimator.TransitionCookie,
controllerFactory: ActivityTransitionAnimator.ControllerFactory,
+ scope: CoroutineScope,
) {
check(TransitionAnimator.longLivedReturnAnimationsEnabled())
@@ -116,7 +118,7 @@ constructor(
controllerFactory.launchCujType,
controllerFactory.returnCujType,
) {
- override fun createController(
+ override suspend fun createController(
forLaunch: Boolean
): ActivityTransitionAnimator.Controller {
val baseController = controllerFactory.createController(forLaunch)
@@ -132,7 +134,7 @@ constructor(
}
}
- activityTransitionAnimator.register(cookie, factory)
+ activityTransitionAnimator.register(cookie, factory, scope)
}
override fun unregisterTransition(cookie: ActivityTransitionAnimator.TransitionCookie) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImpl.kt
index d7a29c36f2ce..76f67dc6c146 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImpl.kt
@@ -64,6 +64,7 @@ import com.android.systemui.util.kotlin.getOrNull
import dagger.Lazy
import java.util.Optional
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
/** Encapsulates the activity logic for activity starter. */
@SysUISingleton
@@ -102,6 +103,7 @@ constructor(
override fun registerTransition(
cookie: ActivityTransitionAnimator.TransitionCookie,
controllerFactory: ActivityTransitionAnimator.ControllerFactory,
+ scope: CoroutineScope,
) {
check(TransitionAnimator.longLivedReturnAnimationsEnabled())
@@ -113,7 +115,7 @@ constructor(
controllerFactory.launchCujType,
controllerFactory.returnCujType,
) {
- override fun createController(
+ override suspend fun createController(
forLaunch: Boolean
): ActivityTransitionAnimator.Controller {
val baseController = controllerFactory.createController(forLaunch)
@@ -129,7 +131,7 @@ constructor(
}
}
- activityTransitionAnimator.register(cookie, factory)
+ activityTransitionAnimator.register(cookie, factory, scope)
}
override fun unregisterTransition(cookie: ActivityTransitionAnimator.TransitionCookie) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModel.kt
index 6258a55c374f..34ba767c227e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModel.kt
@@ -22,7 +22,7 @@ import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.ContentDescription.Companion.loadContentDescription
import com.android.systemui.common.shared.model.Text
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.qs.tileimpl.QSTileImpl
import com.android.systemui.qs.tileimpl.QSTileImpl.ResourceIcon
import com.android.systemui.res.R
@@ -61,7 +61,7 @@ constructor(
mobileIconsInteractor: MobileIconsInteractor,
wifiInteractor: WifiInteractor,
private val context: Context,
- @Application scope: CoroutineScope,
+ @Background scope: CoroutineScope,
) {
private val internetLabel: String = context.getString(R.string.quick_settings_internet_label)
@@ -111,17 +111,16 @@ constructor(
if (it == null) {
notConnectedFlow
} else {
- combine(
- it.networkName,
- it.signalLevelIcon,
- mobileDataContentName,
- ) { networkNameModel, signalIcon, dataContentDescription ->
+ combine(it.networkName, it.signalLevelIcon, mobileDataContentName) {
+ networkNameModel,
+ signalIcon,
+ dataContentDescription ->
when (signalIcon) {
is SignalIconModel.Cellular -> {
val secondary =
mobileDataContentConcat(
networkNameModel.name,
- dataContentDescription
+ dataContentDescription,
)
InternetTileModel.Active(
secondaryTitle = secondary,
@@ -147,7 +146,7 @@ constructor(
private fun mobileDataContentConcat(
networkName: String?,
- dataContentDescription: CharSequence?
+ dataContentDescription: CharSequence?,
): CharSequence {
if (dataContentDescription == null) {
return networkName ?: ""
@@ -160,9 +159,9 @@ constructor(
context.getString(
R.string.mobile_carrier_text_format,
networkName,
- dataContentDescription
+ dataContentDescription,
),
- 0
+ 0,
)
}
@@ -191,10 +190,9 @@ constructor(
}
private val notConnectedFlow: StateFlow<InternetTileModel> =
- combine(
- wifiInteractor.areNetworksAvailable,
- airplaneModeRepository.isAirplaneMode,
- ) { networksAvailable, isAirplaneMode ->
+ combine(wifiInteractor.areNetworksAvailable, airplaneModeRepository.isAirplaneMode) {
+ networksAvailable,
+ isAirplaneMode ->
when {
isAirplaneMode -> {
val secondary = context.getString(R.string.status_bar_airplane)
@@ -213,7 +211,7 @@ constructor(
iconId = R.drawable.ic_qs_no_internet_available,
stateDescription = null,
contentDescription =
- ContentDescription.Loaded("$internetLabel,$secondary")
+ ContentDescription.Loaded("$internetLabel,$secondary"),
)
}
else -> {
diff --git a/packages/SystemUI/tests/goldens/brightnessSlider_iconAlphaChanges.json b/packages/SystemUI/tests/goldens/brightnessSlider_iconAlphaChanges.json
new file mode 100644
index 000000000000..cefada71686c
--- /dev/null
+++ b/packages/SystemUI/tests/goldens/brightnessSlider_iconAlphaChanges.json
@@ -0,0 +1,168 @@
+{
+ "frame_ids": [
+ "before",
+ 0,
+ 16,
+ 32,
+ 48,
+ 64,
+ 80,
+ 96,
+ 112,
+ 128,
+ 144,
+ 160,
+ 176,
+ 192,
+ 208,
+ 224,
+ 240,
+ 256,
+ 272,
+ 288,
+ 304,
+ 320,
+ 336,
+ 352,
+ 368,
+ 384,
+ 400,
+ 416,
+ 432,
+ 448,
+ 464,
+ 480,
+ 496,
+ 512,
+ 528,
+ 544,
+ 560,
+ 576,
+ 592,
+ 608,
+ 624,
+ 640,
+ 656,
+ 672,
+ 688,
+ 704,
+ 720,
+ 736,
+ 752,
+ "after"
+ ],
+ "features": [
+ {
+ "name": "activeIconAlpha_activeIconAlpha",
+ "type": "float",
+ "data_points": [
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 0.5782508,
+ 0.09543866,
+ 8.595586E-4,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0
+ ]
+ },
+ {
+ "name": "inactiveIconAlpha_inactiveIconAlpha",
+ "type": "float",
+ "data_points": [
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0.065971695,
+ 0.3946195,
+ 0.7348632,
+ 0.8979182,
+ 0.97246534,
+ 0.9986459,
+ 1
+ ]
+ }
+ ]
+}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index 312d2ffd74e4..4110a05170b3 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -18,7 +18,6 @@ package com.android.keyguard;
import static android.app.StatusBarManager.SESSION_KEYGUARD;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
-import static android.hardware.biometrics.BiometricAuthenticator.TYPE_ANY_BIOMETRIC;
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE;
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
import static android.hardware.biometrics.BiometricFaceConstants.FACE_ERROR_LOCKOUT_PERMANENT;
@@ -2717,7 +2716,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
() -> mAlternateBouncerInteractor,
() -> mJavaAdapter,
() -> mSceneInteractor,
- mCommunalSceneInteractor);
+ () -> mCommunalSceneInteractor);
setAlternateBouncerVisibility(false);
setPrimaryBouncerVisibility(false);
setStrongAuthTracker(KeyguardUpdateMonitorTest.this.mStrongAuthTracker);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java
index ad5f96044c4c..a0f5b2214f80 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java
@@ -242,8 +242,8 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase {
mWindowMagnificationAnimationController.enableWindowMagnification(targetScale,
targetCenterX, targetCenterY, mAnimationCallback2);
mCurrentScale.set(mController.getScale());
- mCurrentCenterX.set(mController.getCenterX());
- mCurrentCenterY.set(mController.getCenterY());
+ mCurrentCenterX.set(mController.getMagnificationFrameCenterX());
+ mCurrentCenterY.set(mController.getMagnificationFrameCenterY());
advanceTimeBy(mWaitAnimationDuration);
});
@@ -297,8 +297,8 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase {
mWindowMagnificationAnimationController.enableWindowMagnification(targetScale,
targetCenterX, targetCenterY, mAnimationCallback);
mCurrentScale.set(mController.getScale());
- mCurrentCenterX.set(mController.getCenterX());
- mCurrentCenterY.set(mController.getCenterY());
+ mCurrentCenterX.set(mController.getMagnificationFrameCenterX());
+ mCurrentCenterY.set(mController.getMagnificationFrameCenterY());
advanceTimeBy(mWaitAnimationDuration);
});
@@ -339,8 +339,8 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase {
mWindowMagnificationAnimationController.enableWindowMagnification(targetScale,
targetCenterX, targetCenterY, mAnimationCallback);
mCurrentScale.set(mController.getScale());
- mCurrentCenterX.set(mController.getCenterX());
- mCurrentCenterY.set(mController.getCenterY());
+ mCurrentCenterX.set(mController.getMagnificationFrameCenterX());
+ mCurrentCenterY.set(mController.getMagnificationFrameCenterY());
advanceTimeBy(mWaitAnimationDuration);
});
@@ -375,8 +375,8 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase {
mWindowMagnificationAnimationController.enableWindowMagnification(targetScale,
targetCenterX, targetCenterY, mAnimationCallback);
mCurrentScale.set(mController.getScale());
- mCurrentCenterX.set(mController.getCenterX());
- mCurrentCenterY.set(mController.getCenterY());
+ mCurrentCenterX.set(mController.getMagnificationFrameCenterX());
+ mCurrentCenterY.set(mController.getMagnificationFrameCenterY());
advanceTimeBy(mWaitAnimationDuration);
});
@@ -463,8 +463,8 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase {
mWindowMagnificationAnimationController.enableWindowMagnification(targetScale,
targetCenterX, targetCenterY, mAnimationCallback2);
mCurrentScale.set(mController.getScale());
- mCurrentCenterX.set(mController.getCenterX());
- mCurrentCenterY.set(mController.getCenterY());
+ mCurrentCenterX.set(mController.getMagnificationFrameCenterX());
+ mCurrentCenterY.set(mController.getMagnificationFrameCenterY());
});
// Current spec shouldn't match given spec.
@@ -548,8 +548,8 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase {
mWindowMagnificationAnimationController.enableWindowMagnification(targetScale,
targetCenterX, targetCenterY, mAnimationCallback2);
mCurrentScale.set(mController.getScale());
- mCurrentCenterX.set(mController.getCenterX());
- mCurrentCenterY.set(mController.getCenterY());
+ mCurrentCenterX.set(mController.getMagnificationFrameCenterX());
+ mCurrentCenterY.set(mController.getMagnificationFrameCenterY());
advanceTimeBy(mWaitAnimationDuration);
});
@@ -777,8 +777,8 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase {
mWindowMagnificationAnimationController.deleteWindowMagnification(
mAnimationCallback2);
mCurrentScale.set(mController.getScale());
- mCurrentCenterX.set(mController.getCenterX());
- mCurrentCenterY.set(mController.getCenterY());
+ mCurrentCenterX.set(mController.getMagnificationFrameCenterX());
+ mCurrentCenterY.set(mController.getMagnificationFrameCenterY());
// ValueAnimator.reverse() could not work correctly with the AnimatorTestRule since it
// is using SystemClock in reverse() (b/305731398). Therefore, we call end() on the
// animator directly to verify the result of animation is correct instead of querying
@@ -940,8 +940,8 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase {
private void verifyFinalSpec(float expectedScale, float expectedCenterX,
float expectedCenterY) {
assertEquals(expectedScale, mController.getScale(), 0f);
- assertEquals(expectedCenterX, mController.getCenterX(), 0f);
- assertEquals(expectedCenterY, mController.getCenterY(), 0f);
+ assertEquals(expectedCenterX, mController.getMagnificationFrameCenterX(), 0f);
+ assertEquals(expectedCenterY, mController.getMagnificationFrameCenterY(), 0f);
}
private void enableWindowMagnificationWithoutAnimation() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
index 8552e48a2024..7cf93277bb5b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
@@ -63,6 +63,8 @@ import android.graphics.Rect;
import android.os.Handler;
import android.os.RemoteException;
import android.os.SystemClock;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
import android.provider.Settings;
import android.testing.TestableLooper;
import android.testing.TestableResources;
@@ -88,6 +90,7 @@ import androidx.test.InstrumentationRegistry;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.LargeTest;
+import com.android.systemui.Flags;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.animation.AnimatorTestRule;
import com.android.systemui.kosmos.KosmosJavaAdapter;
@@ -128,6 +131,7 @@ public class WindowMagnificationControllerTest extends SysuiTestCase {
public final AnimatorTestRule mAnimatorTestRule = new AnimatorTestRule(/* test= */ null);
private static final int LAYOUT_CHANGE_TIMEOUT_MS = 5000;
+ private static final int INSET_BOTTOM = 10;
@Mock
private MirrorWindowControl mMirrorWindowControl;
@Mock
@@ -329,9 +333,9 @@ public class WindowMagnificationControllerTest extends SysuiTestCase {
final ArgumentCaptor<Rect> sourceBoundsCaptor = ArgumentCaptor.forClass(Rect.class);
verify(mWindowMagnifierCallback, atLeast(2)).onSourceBoundsChanged(
(eq(mContext.getDisplayId())), sourceBoundsCaptor.capture());
- assertThat(mWindowMagnificationController.getCenterX())
+ assertThat(mWindowMagnificationController.getMagnificationFrameCenterX())
.isEqualTo(sourceBoundsCaptor.getValue().exactCenterX());
- assertThat(mWindowMagnificationController.getCenterY())
+ assertThat(mWindowMagnificationController.getMagnificationFrameCenterY())
.isEqualTo(sourceBoundsCaptor.getValue().exactCenterY());
}
@@ -382,6 +386,7 @@ public class WindowMagnificationControllerTest extends SysuiTestCase {
}
@Test
+ @DisableFlags(Flags.FLAG_UPDATE_WINDOW_MAGNIFIER_BOTTOM_BOUNDARY)
public void deleteWindowMagnification_enableAtTheBottom_overlapFlagIsFalse() {
final WindowManager wm = mContext.getSystemService(WindowManager.class);
final Rect bounds = wm.getCurrentWindowMetrics().getBounds();
@@ -457,12 +462,14 @@ public class WindowMagnificationControllerTest extends SysuiTestCase {
verify(mAnimationCallback, never()).onResult(eq(false));
verify(mWindowMagnifierCallback, timeout(LAYOUT_CHANGE_TIMEOUT_MS))
.onSourceBoundsChanged((eq(mContext.getDisplayId())), sourceBoundsCaptor.capture());
- assertThat(mWindowMagnificationController.getCenterX())
+ assertThat(mWindowMagnificationController.getMagnificationFrameCenterX())
.isEqualTo(sourceBoundsCaptor.getValue().exactCenterX());
- assertThat(mWindowMagnificationController.getCenterY())
+ assertThat(mWindowMagnificationController.getMagnificationFrameCenterY())
.isEqualTo(sourceBoundsCaptor.getValue().exactCenterY());
- assertThat(mWindowMagnificationController.getCenterX()).isEqualTo(targetCenterX);
- assertThat(mWindowMagnificationController.getCenterY()).isEqualTo(targetCenterY);
+ assertThat(mWindowMagnificationController.getMagnificationFrameCenterX())
+ .isEqualTo(targetCenterX);
+ assertThat(mWindowMagnificationController.getMagnificationFrameCenterY())
+ .isEqualTo(targetCenterY);
}
@Test
@@ -498,12 +505,14 @@ public class WindowMagnificationControllerTest extends SysuiTestCase {
verify(mAnimationCallback, times(3)).onResult(eq(false));
verify(mWindowMagnifierCallback, timeout(LAYOUT_CHANGE_TIMEOUT_MS))
.onSourceBoundsChanged((eq(mContext.getDisplayId())), sourceBoundsCaptor.capture());
- assertThat(mWindowMagnificationController.getCenterX())
+ assertThat(mWindowMagnificationController.getMagnificationFrameCenterX())
.isEqualTo(sourceBoundsCaptor.getValue().exactCenterX());
- assertThat(mWindowMagnificationController.getCenterY())
+ assertThat(mWindowMagnificationController.getMagnificationFrameCenterY())
.isEqualTo(sourceBoundsCaptor.getValue().exactCenterY());
- assertThat(mWindowMagnificationController.getCenterX()).isEqualTo(centerX + 40);
- assertThat(mWindowMagnificationController.getCenterY()).isEqualTo(centerY + 40);
+ assertThat(mWindowMagnificationController.getMagnificationFrameCenterX())
+ .isEqualTo(centerX + 40);
+ assertThat(mWindowMagnificationController.getMagnificationFrameCenterY())
+ .isEqualTo(centerY + 40);
}
@Test
@@ -545,8 +554,8 @@ public class WindowMagnificationControllerTest extends SysuiTestCase {
mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN,
magnifiedCenter.x, magnifiedCenter.y);
// Get the center again in case the center we set is out of screen.
- magnifiedCenter.set(mWindowMagnificationController.getCenterX(),
- mWindowMagnificationController.getCenterY());
+ magnifiedCenter.set(mWindowMagnificationController.getMagnificationFrameCenterX(),
+ mWindowMagnificationController.getMagnificationFrameCenterY());
});
// Rotate the window clockwise 90 degree.
windowBounds.set(windowBounds.top, windowBounds.left, windowBounds.bottom,
@@ -559,8 +568,9 @@ public class WindowMagnificationControllerTest extends SysuiTestCase {
assertThat(mWindowMagnificationController.mRotation).isEqualTo(newRotation);
final PointF expectedCenter = new PointF(magnifiedCenter.y,
displayWidth - magnifiedCenter.x);
- final PointF actualCenter = new PointF(mWindowMagnificationController.getCenterX(),
- mWindowMagnificationController.getCenterY());
+ final PointF actualCenter =
+ new PointF(mWindowMagnificationController.getMagnificationFrameCenterX(),
+ mWindowMagnificationController.getMagnificationFrameCenterY());
assertThat(actualCenter).isEqualTo(expectedCenter);
}
@@ -603,10 +613,10 @@ public class WindowMagnificationControllerTest extends SysuiTestCase {
});
// The ratio of center to window size should be the same.
- assertThat(mWindowMagnificationController.getCenterX() / testWindowBounds.width())
- .isEqualTo(expectedRatio);
- assertThat(mWindowMagnificationController.getCenterY() / testWindowBounds.height())
- .isEqualTo(expectedRatio);
+ assertThat(mWindowMagnificationController.getMagnificationFrameCenterX()
+ / testWindowBounds.width()).isEqualTo(expectedRatio);
+ assertThat(mWindowMagnificationController.getMagnificationFrameCenterY()
+ / testWindowBounds.height()).isEqualTo(expectedRatio);
}
@Test
@@ -1175,6 +1185,7 @@ public class WindowMagnificationControllerTest extends SysuiTestCase {
}
@Test
+ @DisableFlags(Flags.FLAG_UPDATE_WINDOW_MAGNIFIER_BOTTOM_BOUNDARY)
public void moveWindowMagnificationToTheBottom_enabledWithGestureInset_overlapFlagIsTrue() {
final Rect bounds = mWindowManager.getCurrentWindowMetrics().getBounds();
setSystemGestureInsets();
@@ -1191,6 +1202,30 @@ public class WindowMagnificationControllerTest extends SysuiTestCase {
}
@Test
+ @EnableFlags(Flags.FLAG_UPDATE_WINDOW_MAGNIFIER_BOTTOM_BOUNDARY)
+ public void moveWindowMagnificationToTheBottom_stopsAtSystemGestureTop() {
+ final Rect bounds = mWindowManager.getCurrentWindowMetrics().getBounds();
+ setSystemGestureInsets();
+ mInstrumentation.runOnMainSync(() -> {
+ mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
+ Float.NaN);
+ });
+
+ ViewGroup.LayoutParams params = mSurfaceControlViewHost.getView().getLayoutParams();
+ final int mOuterBorderSize = mResources.getDimensionPixelSize(
+ R.dimen.magnification_outer_border_margin);
+
+ final float expectedY =
+ (float) (bounds.bottom - INSET_BOTTOM - params.height + mOuterBorderSize);
+
+ mInstrumentation.runOnMainSync(() -> {
+ mWindowMagnificationController.moveWindowMagnifier(0, bounds.height());
+ });
+
+ assertThat(mWindowMagnificationController.getMagnifierWindowY()).isEqualTo(expectedY);
+ }
+
+ @Test
public void moveWindowMagnificationToRightEdge_dragHandleMovesToLeftAndUpdatesTapExcludeRegion()
throws RemoteException {
final Rect bounds = mWindowManager.getCurrentWindowMetrics().getBounds();
@@ -1445,8 +1480,10 @@ public class WindowMagnificationControllerTest extends SysuiTestCase {
mInstrumentation.runOnMainSync(() -> {
mWindowMagnificationController.setWindowSizeAndCenter(minimumWindowSize,
minimumWindowSize, bounds.right, bounds.bottom);
- magnificationCenterX.set((int) mWindowMagnificationController.getCenterX());
- magnificationCenterY.set((int) mWindowMagnificationController.getCenterY());
+ magnificationCenterX.set(
+ (int) mWindowMagnificationController.getMagnificationFrameCenterX());
+ magnificationCenterY.set(
+ (int) mWindowMagnificationController.getMagnificationFrameCenterY());
});
assertThat(magnificationCenterX.get()).isLessThan(bounds.right);
@@ -1501,7 +1538,7 @@ public class WindowMagnificationControllerTest extends SysuiTestCase {
private void setSystemGestureInsets() {
final WindowInsets testInsets = new WindowInsets.Builder()
- .setInsets(systemGestures(), Insets.of(0, 0, 0, 10))
+ .setInsets(systemGestures(), Insets.of(0, 0, 0, INSET_BOTTOM))
.build();
mWindowManager.setWindowInsets(testInsets);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityTransitionAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityTransitionAnimatorTest.kt
index fd751d9cc7c3..845be0252581 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityTransitionAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityTransitionAnimatorTest.kt
@@ -27,7 +27,10 @@ import android.window.WindowAnimationState
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.kosmos.runTest
+import com.android.systemui.kosmos.testScope
import com.android.systemui.shared.Flags
+import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.any
import com.android.wm.shell.shared.ShellTransitions
import com.google.common.truth.Truth.assertThat
@@ -38,6 +41,9 @@ import junit.framework.Assert.assertTrue
import junit.framework.AssertionFailedError
import kotlin.concurrent.thread
import kotlin.test.assertEquals
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.advanceUntilIdle
+import kotlinx.coroutines.test.runTest
import org.junit.After
import org.junit.Assert.assertThrows
import org.junit.Before
@@ -54,10 +60,12 @@ import org.mockito.Mockito.`when`
import org.mockito.Spy
import org.mockito.junit.MockitoJUnit
+@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
@RunWithLooper
class ActivityTransitionAnimatorTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
private val transitionContainer = LinearLayout(mContext)
private val mainExecutor = context.mainExecutor
private val testTransitionAnimator = fakeTransitionAnimator(mainExecutor)
@@ -67,12 +75,12 @@ class ActivityTransitionAnimatorTest : SysuiTestCase() {
@Spy private val controller = TestTransitionAnimatorController(transitionContainer)
@Mock lateinit var iCallback: IRemoteAnimationFinishedCallback
- private lateinit var activityTransitionAnimator: ActivityTransitionAnimator
+ private lateinit var underTest: ActivityTransitionAnimator
@get:Rule val rule = MockitoJUnit.rule()
@Before
fun setup() {
- activityTransitionAnimator =
+ underTest =
ActivityTransitionAnimator(
mainExecutor,
ActivityTransitionAnimator.TransitionRegister.fromShellTransitions(
@@ -82,17 +90,17 @@ class ActivityTransitionAnimatorTest : SysuiTestCase() {
testTransitionAnimator,
disableWmTimeout = true,
)
- activityTransitionAnimator.callback = callback
- activityTransitionAnimator.addListener(listener)
+ underTest.callback = callback
+ underTest.addListener(listener)
}
@After
fun tearDown() {
- activityTransitionAnimator.removeListener(listener)
+ underTest.removeListener(listener)
}
private fun startIntentWithAnimation(
- animator: ActivityTransitionAnimator = this.activityTransitionAnimator,
+ animator: ActivityTransitionAnimator = underTest,
controller: ActivityTransitionAnimator.Controller? = this.controller,
animate: Boolean = true,
intentStarter: (RemoteAnimationAdapter?) -> Int,
@@ -157,7 +165,7 @@ class ActivityTransitionAnimatorTest : SysuiTestCase() {
val willAnimateCaptor = ArgumentCaptor.forClass(Boolean::class.java)
var animationAdapter: RemoteAnimationAdapter? = null
- startIntentWithAnimation(activityTransitionAnimator) { adapter ->
+ startIntentWithAnimation(underTest) { adapter ->
animationAdapter = adapter
ActivityManager.START_DELIVERED_TO_TOP
}
@@ -185,9 +193,7 @@ class ActivityTransitionAnimatorTest : SysuiTestCase() {
fun registersReturnIffCookieIsPresent() {
`when`(callback.isOnKeyguard()).thenReturn(false)
- startIntentWithAnimation(activityTransitionAnimator, controller) { _ ->
- ActivityManager.START_DELIVERED_TO_TOP
- }
+ startIntentWithAnimation(underTest, controller) { ActivityManager.START_DELIVERED_TO_TOP }
waitForIdleSync()
assertTrue(testShellTransitions.remotes.isEmpty())
@@ -199,9 +205,7 @@ class ActivityTransitionAnimatorTest : SysuiTestCase() {
get() = ActivityTransitionAnimator.TransitionCookie("testCookie")
}
- startIntentWithAnimation(activityTransitionAnimator, controller) { _ ->
- ActivityManager.START_DELIVERED_TO_TOP
- }
+ startIntentWithAnimation(underTest, controller) { ActivityManager.START_DELIVERED_TO_TOP }
waitForIdleSync()
assertEquals(1, testShellTransitions.remotes.size)
@@ -214,13 +218,15 @@ class ActivityTransitionAnimatorTest : SysuiTestCase() {
)
@Test
fun registersLongLivedTransition() {
- var factory = controllerFactory()
- activityTransitionAnimator.register(factory.cookie, factory)
- assertEquals(2, testShellTransitions.remotes.size)
-
- factory = controllerFactory()
- activityTransitionAnimator.register(factory.cookie, factory)
- assertEquals(4, testShellTransitions.remotes.size)
+ kosmos.runTest {
+ var factory = controllerFactory()
+ underTest.register(factory.cookie, factory, testScope)
+ assertEquals(2, testShellTransitions.remotes.size)
+
+ factory = controllerFactory()
+ underTest.register(factory.cookie, factory, testScope)
+ assertEquals(4, testShellTransitions.remotes.size)
+ }
}
@EnableFlags(
@@ -229,49 +235,55 @@ class ActivityTransitionAnimatorTest : SysuiTestCase() {
)
@Test
fun registersLongLivedTransitionOverridingPreviousRegistration() {
- val cookie = ActivityTransitionAnimator.TransitionCookie("test_cookie")
- var factory = controllerFactory(cookie)
- activityTransitionAnimator.register(cookie, factory)
- val transitions = testShellTransitions.remotes.values.toList()
-
- factory = controllerFactory(cookie)
- activityTransitionAnimator.register(cookie, factory)
- assertEquals(2, testShellTransitions.remotes.size)
- for (transition in transitions) {
- assertThat(testShellTransitions.remotes.values).doesNotContain(transition)
+ kosmos.runTest {
+ val cookie = ActivityTransitionAnimator.TransitionCookie("test_cookie")
+ var factory = controllerFactory(cookie)
+ underTest.register(cookie, factory, testScope)
+ val transitions = testShellTransitions.remotes.values.toList()
+
+ factory = controllerFactory(cookie)
+ underTest.register(cookie, factory, testScope)
+ assertEquals(2, testShellTransitions.remotes.size)
+ for (transition in transitions) {
+ assertThat(testShellTransitions.remotes.values).doesNotContain(transition)
+ }
}
}
@DisableFlags(Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LONG_LIVED)
@Test
fun doesNotRegisterLongLivedTransitionIfFlagIsDisabled() {
- val factory = controllerFactory(component = null)
- assertThrows(IllegalStateException::class.java) {
- activityTransitionAnimator.register(factory.cookie, factory)
+ kosmos.runTest {
+ val factory = controllerFactory(component = null)
+ assertThrows(IllegalStateException::class.java) {
+ underTest.register(factory.cookie, factory, testScope)
+ }
}
}
@EnableFlags(Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LONG_LIVED)
@Test
fun doesNotRegisterLongLivedTransitionIfMissingRequiredProperties() {
- // No ComponentName
- var factory = controllerFactory(component = null)
- assertThrows(IllegalStateException::class.java) {
- activityTransitionAnimator.register(factory.cookie, factory)
- }
+ kosmos.runTest {
+ // No ComponentName
+ var factory = controllerFactory(component = null)
+ assertThrows(IllegalStateException::class.java) {
+ underTest.register(factory.cookie, factory, testScope)
+ }
- // No TransitionRegister
- activityTransitionAnimator =
- ActivityTransitionAnimator(
- mainExecutor,
- transitionRegister = null,
- testTransitionAnimator,
- testTransitionAnimator,
- disableWmTimeout = true,
- )
- factory = controllerFactory()
- assertThrows(IllegalStateException::class.java) {
- activityTransitionAnimator.register(factory.cookie, factory)
+ // No TransitionRegister
+ val activityTransitionAnimator =
+ ActivityTransitionAnimator(
+ mainExecutor,
+ transitionRegister = null,
+ testTransitionAnimator,
+ testTransitionAnimator,
+ disableWmTimeout = true,
+ )
+ factory = controllerFactory()
+ assertThrows(IllegalStateException::class.java) {
+ activityTransitionAnimator.register(factory.cookie, factory, testScope)
+ }
}
}
@@ -281,27 +293,29 @@ class ActivityTransitionAnimatorTest : SysuiTestCase() {
)
@Test
fun unregistersLongLivedTransition() {
- val cookies = arrayOfNulls<ActivityTransitionAnimator.TransitionCookie>(3)
+ kosmos.runTest {
+ val cookies = arrayOfNulls<ActivityTransitionAnimator.TransitionCookie>(3)
- for (index in 0 until 3) {
- cookies[index] = mock(ActivityTransitionAnimator.TransitionCookie::class.java)
- val factory = controllerFactory(cookies[index]!!)
- activityTransitionAnimator.register(factory.cookie, factory)
- }
+ for (index in 0 until 3) {
+ cookies[index] = mock(ActivityTransitionAnimator.TransitionCookie::class.java)
+ val factory = controllerFactory(cookies[index]!!)
+ underTest.register(factory.cookie, factory, testScope)
+ }
- activityTransitionAnimator.unregister(cookies[0]!!)
- assertEquals(4, testShellTransitions.remotes.size)
+ underTest.unregister(cookies[0]!!)
+ assertEquals(4, testShellTransitions.remotes.size)
- activityTransitionAnimator.unregister(cookies[2]!!)
- assertEquals(2, testShellTransitions.remotes.size)
+ underTest.unregister(cookies[2]!!)
+ assertEquals(2, testShellTransitions.remotes.size)
- activityTransitionAnimator.unregister(cookies[1]!!)
- assertThat(testShellTransitions.remotes).isEmpty()
+ underTest.unregister(cookies[1]!!)
+ assertThat(testShellTransitions.remotes).isEmpty()
+ }
}
@Test
fun doesNotStartIfAnimationIsCancelled() {
- val runner = activityTransitionAnimator.createEphemeralRunner(controller)
+ val runner = underTest.createEphemeralRunner(controller)
runner.onAnimationCancelled()
runner.onAnimationStart(TRANSIT_NONE, emptyArray(), emptyArray(), emptyArray(), iCallback)
@@ -315,7 +329,7 @@ class ActivityTransitionAnimatorTest : SysuiTestCase() {
@Test
fun cancelsIfNoOpeningWindowIsFound() {
- val runner = activityTransitionAnimator.createEphemeralRunner(controller)
+ val runner = underTest.createEphemeralRunner(controller)
runner.onAnimationStart(TRANSIT_NONE, emptyArray(), emptyArray(), emptyArray(), iCallback)
waitForIdleSync()
@@ -328,7 +342,7 @@ class ActivityTransitionAnimatorTest : SysuiTestCase() {
@Test
fun startsAnimationIfWindowIsOpening() {
- val runner = activityTransitionAnimator.createEphemeralRunner(controller)
+ val runner = underTest.createEphemeralRunner(controller)
runner.onAnimationStart(
TRANSIT_NONE,
arrayOf(fakeWindow()),
@@ -354,9 +368,11 @@ class ActivityTransitionAnimatorTest : SysuiTestCase() {
)
@Test
fun creatingRunnerWithLazyInitializationThrows_whenTheFlagsAreDisabled() {
- assertThrows(IllegalStateException::class.java) {
- val factory = controllerFactory()
- activityTransitionAnimator.createLongLivedRunner(factory, forLaunch = true)
+ kosmos.runTest {
+ assertThrows(IllegalStateException::class.java) {
+ val factory = controllerFactory()
+ underTest.createLongLivedRunner(factory, testScope, forLaunch = true)
+ }
}
}
@@ -365,44 +381,34 @@ class ActivityTransitionAnimatorTest : SysuiTestCase() {
Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LONG_LIVED,
)
@Test
- fun runnerCreatesDelegateLazily_whenPostingTimeouts() {
- val factory = controllerFactory()
- val runner = activityTransitionAnimator.createLongLivedRunner(factory, forLaunch = true)
- assertNull(runner.delegate)
- runner.postTimeouts()
- assertNotNull(runner.delegate)
- }
-
- @EnableFlags(
- Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LIBRARY,
- Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LONG_LIVED,
- )
- @Test
fun runnerCreatesDelegateLazily_onAnimationStart() {
- val factory = controllerFactory()
- val runner = activityTransitionAnimator.createLongLivedRunner(factory, forLaunch = true)
- assertNull(runner.delegate)
-
- var delegateInitialized = false
- activityTransitionAnimator.addListener(
- object : ActivityTransitionAnimator.Listener {
- override fun onTransitionAnimationStart() {
- // This is called iff the delegate was initialized, so it's a good proxy for
- // checking the initialization.
- delegateInitialized = true
+ kosmos.runTest {
+ val factory = controllerFactory()
+ val runner = underTest.createLongLivedRunner(factory, testScope, forLaunch = true)
+ assertNull(runner.delegate)
+
+ var delegateInitialized = false
+ underTest.addListener(
+ object : ActivityTransitionAnimator.Listener {
+ override fun onTransitionAnimationStart() {
+ // This is called iff the delegate was initialized, so it's a good proxy for
+ // checking the initialization.
+ delegateInitialized = true
+ }
}
- }
- )
- runner.onAnimationStart(
- TRANSIT_NONE,
- arrayOf(fakeWindow()),
- emptyArray(),
- emptyArray(),
- iCallback,
- )
+ )
+ runner.onAnimationStart(
+ TRANSIT_NONE,
+ arrayOf(fakeWindow()),
+ emptyArray(),
+ emptyArray(),
+ iCallback,
+ )
+ testScope.advanceUntilIdle()
+ waitForIdleSync()
- waitForIdleSync()
- assertTrue(delegateInitialized)
+ assertTrue(delegateInitialized)
+ }
}
@EnableFlags(
@@ -411,29 +417,32 @@ class ActivityTransitionAnimatorTest : SysuiTestCase() {
)
@Test
fun runnerCreatesDelegateLazily_onAnimationTakeover() {
- val factory = controllerFactory()
- val runner = activityTransitionAnimator.createLongLivedRunner(factory, forLaunch = false)
- assertNull(runner.delegate)
-
- var delegateInitialized = false
- activityTransitionAnimator.addListener(
- object : ActivityTransitionAnimator.Listener {
- override fun onTransitionAnimationStart() {
- // This is called iff the delegate was initialized, so it's a good proxy for
- // checking the initialization.
- delegateInitialized = true
+ kosmos.runTest {
+ val factory = controllerFactory()
+ val runner = underTest.createLongLivedRunner(factory, testScope, forLaunch = false)
+ assertNull(runner.delegate)
+
+ var delegateInitialized = false
+ underTest.addListener(
+ object : ActivityTransitionAnimator.Listener {
+ override fun onTransitionAnimationStart() {
+ // This is called iff the delegate was initialized, so it's a good proxy for
+ // checking the initialization.
+ delegateInitialized = true
+ }
}
- }
- )
- runner.takeOverAnimation(
- arrayOf(fakeWindow(MODE_CLOSING)),
- arrayOf(WindowAnimationState()),
- SurfaceControl.Transaction(),
- iCallback,
- )
+ )
+ runner.takeOverAnimation(
+ arrayOf(fakeWindow(MODE_CLOSING)),
+ arrayOf(WindowAnimationState()),
+ SurfaceControl.Transaction(),
+ iCallback,
+ )
+ testScope.advanceUntilIdle()
+ waitForIdleSync()
- waitForIdleSync()
- assertTrue(delegateInitialized)
+ assertTrue(delegateInitialized)
+ }
}
@DisableFlags(
@@ -442,7 +451,7 @@ class ActivityTransitionAnimatorTest : SysuiTestCase() {
)
@Test
fun animationTakeoverThrows_whenTheFlagsAreDisabled() {
- val runner = activityTransitionAnimator.createEphemeralRunner(controller)
+ val runner = underTest.createEphemeralRunner(controller)
assertThrows(IllegalStateException::class.java) {
runner.takeOverAnimation(
arrayOf(fakeWindow()),
@@ -459,7 +468,7 @@ class ActivityTransitionAnimatorTest : SysuiTestCase() {
)
@Test
fun disposeRunner_delegateDereferenced() {
- val runner = activityTransitionAnimator.createEphemeralRunner(controller)
+ val runner = underTest.createEphemeralRunner(controller)
assertNotNull(runner.delegate)
runner.dispose()
waitForIdleSync()
@@ -469,13 +478,13 @@ class ActivityTransitionAnimatorTest : SysuiTestCase() {
@Test
fun concurrentListenerModification_doesNotThrow() {
// Need a second listener to trigger the concurrent modification.
- activityTransitionAnimator.addListener(object : ActivityTransitionAnimator.Listener {})
+ underTest.addListener(object : ActivityTransitionAnimator.Listener {})
`when`(listener.onTransitionAnimationStart()).thenAnswer {
- activityTransitionAnimator.removeListener(listener)
+ underTest.removeListener(listener)
listener
}
- val runner = activityTransitionAnimator.createEphemeralRunner(controller)
+ val runner = underTest.createEphemeralRunner(controller)
runner.onAnimationStart(
TRANSIT_NONE,
arrayOf(fakeWindow()),
@@ -494,7 +503,7 @@ class ActivityTransitionAnimatorTest : SysuiTestCase() {
component: ComponentName? = mock(ComponentName::class.java),
): ActivityTransitionAnimator.ControllerFactory {
return object : ActivityTransitionAnimator.ControllerFactory(cookie, component) {
- override fun createController(forLaunch: Boolean) =
+ override suspend fun createController(forLaunch: Boolean) =
object : DelegateTransitionAnimatorController(controller) {
override val isLaunching: Boolean
get() = forLaunch
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothDetailsContentManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothDetailsContentManagerTest.kt
new file mode 100644
index 000000000000..6ed990d513cb
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothDetailsContentManagerTest.kt
@@ -0,0 +1,461 @@
+/*
+ * 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.bluetooth.qsdialog
+
+import android.graphics.drawable.Drawable
+import android.testing.TestableLooper
+import android.view.LayoutInflater
+import android.view.View
+import android.view.View.GONE
+import android.view.View.VISIBLE
+import android.view.ViewGroup.LayoutParams.MATCH_PARENT
+import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
+import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.recyclerview.widget.RecyclerView
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.internal.logging.UiEventLogger
+import com.android.settingslib.bluetooth.CachedBluetoothDevice
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.animation.DialogTransitionAnimator
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.model.SysUiState
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.phone.SystemUIDialog
+import com.android.systemui.statusbar.phone.SystemUIDialogManager
+import com.android.systemui.testKosmos
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyBoolean
+import org.mockito.ArgumentMatchers.anyLong
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoRule
+import org.mockito.kotlin.any
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+class BluetoothDetailsContentManagerTest : SysuiTestCase() {
+ companion object {
+ const val DEVICE_NAME = "device"
+ const val DEVICE_CONNECTION_SUMMARY = "active"
+ const val ENABLED = true
+ const val CONTENT_HEIGHT = WRAP_CONTENT
+ }
+
+ @get:Rule val mockitoRule: MockitoRule = MockitoJUnit.rule()
+
+ private val cachedBluetoothDevice = mock<CachedBluetoothDevice>()
+
+ private val bluetoothTileDialogCallback = mock<BluetoothTileDialogCallback>()
+
+ private val drawable = mock<Drawable>()
+
+ private val uiEventLogger = mock<UiEventLogger>()
+
+ private val logger = mock<BluetoothTileDialogLogger>()
+
+ private val sysuiDialogFactory = mock<SystemUIDialog.Factory>()
+ private val dialogManager = mock<SystemUIDialogManager>()
+ private val sysuiState = mock<SysUiState>()
+ private val dialogTransitionAnimator = mock<DialogTransitionAnimator>()
+
+ private val fakeSystemClock = FakeSystemClock()
+
+ private val uiProperties =
+ BluetoothTileDialogViewModel.UiProperties.build(
+ isBluetoothEnabled = ENABLED,
+ isAutoOnToggleFeatureAvailable = ENABLED,
+ )
+
+ private lateinit var icon: Pair<Drawable, String>
+ private lateinit var mBluetoothDetailsContentManager: BluetoothDetailsContentManager
+ private lateinit var deviceItem: DeviceItem
+ private lateinit var contentView: View
+
+ private val kosmos = testKosmos()
+
+ @Before
+ fun setUp() {
+ with(kosmos) {
+ contentView =
+ LayoutInflater.from(mContext).inflate(R.layout.bluetooth_tile_dialog, null)
+
+ whenever(sysuiState.setFlag(anyLong(), anyBoolean())).thenReturn(sysuiState)
+
+ mBluetoothDetailsContentManager =
+ BluetoothDetailsContentManager(
+ uiProperties,
+ CONTENT_HEIGHT,
+ bluetoothTileDialogCallback,
+ /* isInDialog= */ true,
+ {},
+ testDispatcher,
+ fakeSystemClock,
+ uiEventLogger,
+ logger,
+ )
+
+ whenever(sysuiDialogFactory.create(any<SystemUIDialog.Delegate>(), any())).thenAnswer {
+ SystemUIDialog(
+ mContext,
+ 0,
+ SystemUIDialog.DEFAULT_DISMISS_ON_DEVICE_LOCK,
+ dialogManager,
+ sysuiState,
+ fakeBroadcastDispatcher,
+ dialogTransitionAnimator,
+ it.getArgument(0),
+ )
+ }
+
+ icon = Pair(drawable, DEVICE_NAME)
+ deviceItem =
+ DeviceItem(
+ type = DeviceItemType.AVAILABLE_MEDIA_BLUETOOTH_DEVICE,
+ cachedBluetoothDevice = cachedBluetoothDevice,
+ deviceName = DEVICE_NAME,
+ connectionSummary = DEVICE_CONNECTION_SUMMARY,
+ iconWithDescription = icon,
+ background = null,
+ )
+ whenever(cachedBluetoothDevice.isBusy).thenReturn(false)
+ }
+ }
+
+ @Test
+ fun testShowDialog_createRecyclerViewWithAdapter() {
+ mBluetoothDetailsContentManager.bind(contentView)
+ mBluetoothDetailsContentManager.start()
+
+ val recyclerView = contentView.requireViewById<RecyclerView>(R.id.device_list)
+
+ assertThat(recyclerView).isNotNull()
+ assertThat(recyclerView.visibility).isEqualTo(VISIBLE)
+ assertThat(recyclerView.adapter).isNotNull()
+ assertThat(recyclerView.layoutManager is LinearLayoutManager).isTrue()
+ mBluetoothDetailsContentManager.releaseView()
+ }
+
+ @Test
+ fun testShowDialog_displayBluetoothDevice() {
+ with(kosmos) {
+ testScope.runTest {
+ mBluetoothDetailsContentManager.bind(contentView)
+ mBluetoothDetailsContentManager.start()
+ fakeSystemClock.setElapsedRealtime(Long.MAX_VALUE)
+ mBluetoothDetailsContentManager.onDeviceItemUpdated(
+ listOf(deviceItem),
+ showSeeAll = false,
+ showPairNewDevice = false,
+ )
+
+ val recyclerView = contentView.requireViewById<RecyclerView>(R.id.device_list)
+ val adapter = recyclerView?.adapter as BluetoothDetailsContentManager.Adapter
+ assertThat(adapter.itemCount).isEqualTo(1)
+ assertThat(adapter.getItem(0).deviceName).isEqualTo(DEVICE_NAME)
+ assertThat(adapter.getItem(0).connectionSummary)
+ .isEqualTo(DEVICE_CONNECTION_SUMMARY)
+ assertThat(adapter.getItem(0).iconWithDescription).isEqualTo(icon)
+ mBluetoothDetailsContentManager.releaseView()
+ }
+ }
+ }
+
+ @Test
+ fun testDeviceItemViewHolder_cachedDeviceNotBusy() {
+ with(kosmos) {
+ testScope.runTest {
+ deviceItem.isEnabled = true
+
+ val view =
+ LayoutInflater.from(mContext)
+ .inflate(R.layout.bluetooth_device_item, null, false)
+ val viewHolder =
+ mBluetoothDetailsContentManager.Adapter().DeviceItemViewHolder(view)
+ viewHolder.bind(deviceItem)
+ val container = view.requireViewById<View>(R.id.bluetooth_device_row)
+
+ assertThat(container).isNotNull()
+ assertThat(container.isEnabled).isTrue()
+ assertThat(container.hasOnClickListeners()).isTrue()
+ val value by collectLastValue(mBluetoothDetailsContentManager.deviceItemClick)
+ runCurrent()
+ container.performClick()
+ runCurrent()
+ assertThat(value).isNotNull()
+ value?.let {
+ assertThat(it.target).isEqualTo(DeviceItemClick.Target.ENTIRE_ROW)
+ assertThat(it.clickedView).isEqualTo(container)
+ assertThat(it.deviceItem).isEqualTo(deviceItem)
+ }
+ }
+ }
+ }
+
+ @Test
+ fun testDeviceItemViewHolder_cachedDeviceBusy() {
+ with(kosmos) {
+ deviceItem.isEnabled = false
+
+ val view =
+ LayoutInflater.from(mContext).inflate(R.layout.bluetooth_device_item, null, false)
+ val viewHolder =
+ BluetoothDetailsContentManager(
+ uiProperties,
+ CONTENT_HEIGHT,
+ bluetoothTileDialogCallback,
+ /* isInDialog= */ true,
+ {},
+ testDispatcher,
+ fakeSystemClock,
+ uiEventLogger,
+ logger,
+ )
+ .Adapter()
+ .DeviceItemViewHolder(view)
+ viewHolder.bind(deviceItem)
+ val container = view.requireViewById<View>(R.id.bluetooth_device_row)
+
+ assertThat(container).isNotNull()
+ assertThat(container.isEnabled).isFalse()
+ assertThat(container.hasOnClickListeners()).isTrue()
+ }
+ }
+
+ @Test
+ fun testDeviceItemViewHolder_clickActionIcon() {
+ with(kosmos) {
+ testScope.runTest {
+ deviceItem.isEnabled = true
+
+ val view =
+ LayoutInflater.from(mContext)
+ .inflate(R.layout.bluetooth_device_item, null, false)
+ val viewHolder =
+ mBluetoothDetailsContentManager.Adapter().DeviceItemViewHolder(view)
+ viewHolder.bind(deviceItem)
+ val actionIconView = view.requireViewById<View>(R.id.gear_icon)
+
+ assertThat(actionIconView).isNotNull()
+ assertThat(actionIconView.hasOnClickListeners()).isTrue()
+ val value by collectLastValue(mBluetoothDetailsContentManager.deviceItemClick)
+ runCurrent()
+ actionIconView.performClick()
+ runCurrent()
+ assertThat(value).isNotNull()
+ value?.let {
+ assertThat(it.target).isEqualTo(DeviceItemClick.Target.ACTION_ICON)
+ assertThat(it.clickedView).isEqualTo(actionIconView)
+ assertThat(it.deviceItem).isEqualTo(deviceItem)
+ }
+ }
+ }
+ }
+
+ @Test
+ fun testOnDeviceUpdated_hideSeeAll_showPairNew() {
+ with(kosmos) {
+ testScope.runTest {
+ mBluetoothDetailsContentManager.bind(contentView)
+ mBluetoothDetailsContentManager.start()
+ fakeSystemClock.setElapsedRealtime(Long.MAX_VALUE)
+ mBluetoothDetailsContentManager.onDeviceItemUpdated(
+ listOf(deviceItem),
+ showSeeAll = false,
+ showPairNewDevice = true,
+ )
+
+ val seeAllButton = contentView.requireViewById<View>(R.id.see_all_button)
+ val pairNewButton = contentView.requireViewById<View>(R.id.pair_new_device_button)
+ val recyclerView = contentView.requireViewById<RecyclerView>(R.id.device_list)
+ val adapter = recyclerView?.adapter as BluetoothDetailsContentManager.Adapter
+ val scrollViewContent = contentView.requireViewById<View>(R.id.scroll_view)
+
+ assertThat(seeAllButton).isNotNull()
+ assertThat(seeAllButton.visibility).isEqualTo(GONE)
+ assertThat(pairNewButton).isNotNull()
+ assertThat(pairNewButton.visibility).isEqualTo(VISIBLE)
+ assertThat(adapter.itemCount).isEqualTo(1)
+ assertThat(scrollViewContent.layoutParams.height).isEqualTo(WRAP_CONTENT)
+ mBluetoothDetailsContentManager.releaseView()
+ }
+ }
+ }
+
+ @Test
+ fun testShowDialog_cachedHeightLargerThanMinHeight_displayFromCachedHeight() {
+ with(kosmos) {
+ testScope.runTest {
+ val cachedHeight = Int.MAX_VALUE
+ val contentManager =
+ BluetoothDetailsContentManager(
+ BluetoothTileDialogViewModel.UiProperties.build(ENABLED, ENABLED),
+ cachedHeight,
+ bluetoothTileDialogCallback,
+ /* isInDialog= */ true,
+ {},
+ testDispatcher,
+ fakeSystemClock,
+ uiEventLogger,
+ logger,
+ )
+ contentManager.bind(contentView)
+ contentManager.start()
+ assertThat(contentView.requireViewById<View>(R.id.scroll_view).layoutParams.height)
+ .isEqualTo(cachedHeight)
+ contentManager.releaseView()
+ }
+ }
+ }
+
+ @Test
+ fun testShowDialog_cachedHeightLessThanMinHeight_displayFromUiProperties() {
+ with(kosmos) {
+ testScope.runTest {
+ val contentManager =
+ BluetoothDetailsContentManager(
+ BluetoothTileDialogViewModel.UiProperties.build(ENABLED, ENABLED),
+ MATCH_PARENT,
+ bluetoothTileDialogCallback,
+ /* isInDialog= */ true,
+ {},
+ testDispatcher,
+ fakeSystemClock,
+ uiEventLogger,
+ logger,
+ )
+ contentManager.bind(contentView)
+ contentManager.start()
+ assertThat(contentView.requireViewById<View>(R.id.scroll_view).layoutParams.height)
+ .isGreaterThan(MATCH_PARENT)
+ contentManager.releaseView()
+ }
+ }
+ }
+
+ @Test
+ fun testShowDialog_bluetoothEnabled_autoOnToggleGone() {
+ with(kosmos) {
+ testScope.runTest {
+ val contentManager =
+ BluetoothDetailsContentManager(
+ BluetoothTileDialogViewModel.UiProperties.build(ENABLED, ENABLED),
+ MATCH_PARENT,
+ bluetoothTileDialogCallback,
+ /* isInDialog= */ true,
+ {},
+ testDispatcher,
+ fakeSystemClock,
+ uiEventLogger,
+ logger,
+ )
+ contentManager.bind(contentView)
+ contentManager.start()
+ assertThat(
+ contentView
+ .requireViewById<View>(R.id.bluetooth_auto_on_toggle_layout)
+ .visibility
+ )
+ .isEqualTo(GONE)
+ contentManager.releaseView()
+ }
+ }
+ }
+
+ @Test
+ fun testOnAudioSharingButtonUpdated_visibleActive_activateButton() {
+ with(kosmos) {
+ testScope.runTest {
+ mBluetoothDetailsContentManager.bind(contentView)
+ mBluetoothDetailsContentManager.start()
+ fakeSystemClock.setElapsedRealtime(Long.MAX_VALUE)
+ mBluetoothDetailsContentManager.onAudioSharingButtonUpdated(
+ visibility = VISIBLE,
+ label = null,
+ isActive = true,
+ )
+
+ val audioSharingButton =
+ contentView.requireViewById<View>(R.id.audio_sharing_button)
+
+ assertThat(audioSharingButton).isNotNull()
+ assertThat(audioSharingButton.visibility).isEqualTo(VISIBLE)
+ assertThat(audioSharingButton.isActivated).isTrue()
+ mBluetoothDetailsContentManager.releaseView()
+ }
+ }
+ }
+
+ @Test
+ fun testOnAudioSharingButtonUpdated_visibleNotActive_inactivateButton() {
+ with(kosmos) {
+ testScope.runTest {
+ mBluetoothDetailsContentManager.bind(contentView)
+ mBluetoothDetailsContentManager.start()
+ fakeSystemClock.setElapsedRealtime(Long.MAX_VALUE)
+ mBluetoothDetailsContentManager.onAudioSharingButtonUpdated(
+ visibility = VISIBLE,
+ label = null,
+ isActive = false,
+ )
+
+ val audioSharingButton =
+ contentView.requireViewById<View>(R.id.audio_sharing_button)
+
+ assertThat(audioSharingButton).isNotNull()
+ assertThat(audioSharingButton.visibility).isEqualTo(VISIBLE)
+ assertThat(audioSharingButton.isActivated).isFalse()
+ mBluetoothDetailsContentManager.releaseView()
+ }
+ }
+ }
+
+ @Test
+ fun testOnAudioSharingButtonUpdated_gone_inactivateButton() {
+ with(kosmos) {
+ testScope.runTest {
+ mBluetoothDetailsContentManager.bind(contentView)
+ mBluetoothDetailsContentManager.start()
+ fakeSystemClock.setElapsedRealtime(Long.MAX_VALUE)
+ mBluetoothDetailsContentManager.onAudioSharingButtonUpdated(
+ visibility = GONE,
+ label = null,
+ isActive = false,
+ )
+
+ val audioSharingButton =
+ contentView.requireViewById<View>(R.id.audio_sharing_button)
+
+ assertThat(audioSharingButton).isNotNull()
+ assertThat(audioSharingButton.visibility).isEqualTo(GONE)
+ assertThat(audioSharingButton.isActivated).isFalse()
+ mBluetoothDetailsContentManager.releaseView()
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegateTest.kt
index 4396b0a42ae6..ffc75188ffa1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegateTest.kt
@@ -16,47 +16,34 @@
package com.android.systemui.bluetooth.qsdialog
-import android.graphics.drawable.Drawable
import android.testing.TestableLooper
-import android.view.LayoutInflater
-import android.view.View
-import android.view.View.GONE
-import android.view.View.VISIBLE
-import android.view.ViewGroup.LayoutParams.MATCH_PARENT
import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
-import androidx.recyclerview.widget.LinearLayoutManager
-import androidx.recyclerview.widget.RecyclerView
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.internal.logging.UiEventLogger
-import com.android.settingslib.bluetooth.CachedBluetoothDevice
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.DialogTransitionAnimator
-import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
import com.android.systemui.model.SysUiState
-import com.android.systemui.res.R
import com.android.systemui.shade.data.repository.shadeDialogContextInteractor
import com.android.systemui.statusbar.phone.SystemUIDialog
import com.android.systemui.statusbar.phone.SystemUIDialogManager
import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.whenever
-import com.android.systemui.util.time.FakeSystemClock
-import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.test.TestCoroutineScheduler
import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.runCurrent
-import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.anyBoolean
+import org.mockito.ArgumentMatchers.anyInt
import org.mockito.ArgumentMatchers.anyLong
import org.mockito.Mock
-import org.mockito.Mockito.`when`
+import org.mockito.Mockito.verify
import org.mockito.junit.MockitoJUnit
import org.mockito.junit.MockitoRule
@@ -73,33 +60,31 @@ class BluetoothTileDialogDelegateTest : SysuiTestCase() {
@get:Rule val mockitoRule: MockitoRule = MockitoJUnit.rule()
- @Mock private lateinit var cachedBluetoothDevice: CachedBluetoothDevice
+ @Mock
+ private lateinit var bluetoothDetailsContentManagerFactory:
+ BluetoothDetailsContentManager.Factory
- @Mock private lateinit var bluetoothTileDialogCallback: BluetoothTileDialogCallback
+ @Mock private lateinit var bluetoothDetailsContentManager: BluetoothDetailsContentManager
- @Mock private lateinit var drawable: Drawable
+ @Mock private lateinit var bluetoothTileDialogCallback: BluetoothTileDialogCallback
@Mock private lateinit var uiEventLogger: UiEventLogger
- @Mock private lateinit var logger: BluetoothTileDialogLogger
+ @Mock private lateinit var sysuiDialogFactory: SystemUIDialog.Factory
+ @Mock private lateinit var dialogManager: SystemUIDialogManager
+ @Mock private lateinit var sysuiState: SysUiState
+ @Mock private lateinit var dialogTransitionAnimator: DialogTransitionAnimator
private val uiProperties =
BluetoothTileDialogViewModel.UiProperties.build(
isBluetoothEnabled = ENABLED,
isAutoOnToggleFeatureAvailable = ENABLED,
)
- @Mock private lateinit var sysuiDialogFactory: SystemUIDialog.Factory
- @Mock private lateinit var dialogManager: SystemUIDialogManager
- @Mock private lateinit var sysuiState: SysUiState
- @Mock private lateinit var dialogTransitionAnimator: DialogTransitionAnimator
-
- private val fakeSystemClock = FakeSystemClock()
+ private lateinit var scheduler: TestCoroutineScheduler
private lateinit var dispatcher: CoroutineDispatcher
private lateinit var testScope: TestScope
- private lateinit var icon: Pair<Drawable, String>
private lateinit var mBluetoothTileDialogDelegate: BluetoothTileDialogDelegate
- private lateinit var deviceItem: DeviceItem
private val kosmos = testKosmos()
@@ -116,12 +101,10 @@ class BluetoothTileDialogDelegateTest : SysuiTestCase() {
CONTENT_HEIGHT,
bluetoothTileDialogCallback,
{},
- dispatcher,
- fakeSystemClock,
uiEventLogger,
- logger,
sysuiDialogFactory,
kosmos.shadeDialogContextInteractor,
+ bluetoothDetailsContentManagerFactory,
)
whenever(sysuiDialogFactory.create(any(SystemUIDialog.Delegate::class.java), any()))
@@ -138,17 +121,16 @@ class BluetoothTileDialogDelegateTest : SysuiTestCase() {
)
}
- icon = Pair(drawable, DEVICE_NAME)
- deviceItem =
- DeviceItem(
- type = DeviceItemType.AVAILABLE_MEDIA_BLUETOOTH_DEVICE,
- cachedBluetoothDevice = cachedBluetoothDevice,
- deviceName = DEVICE_NAME,
- connectionSummary = DEVICE_CONNECTION_SUMMARY,
- iconWithDescription = icon,
- background = null,
+ whenever(
+ bluetoothDetailsContentManagerFactory.create(
+ any(),
+ anyInt(),
+ any(),
+ anyBoolean(),
+ any(),
+ )
)
- `when`(cachedBluetoothDevice.isBusy).thenReturn(false)
+ .thenReturn(bluetoothDetailsContentManager)
}
@Test
@@ -156,287 +138,9 @@ class BluetoothTileDialogDelegateTest : SysuiTestCase() {
val dialog = mBluetoothTileDialogDelegate.createDialog()
dialog.show()
- val recyclerView = dialog.requireViewById<RecyclerView>(R.id.device_list)
-
- assertThat(recyclerView).isNotNull()
- assertThat(recyclerView.visibility).isEqualTo(VISIBLE)
- assertThat(recyclerView.adapter).isNotNull()
- assertThat(recyclerView.layoutManager is LinearLayoutManager).isTrue()
+ verify(bluetoothDetailsContentManager).bind(any())
+ verify(bluetoothDetailsContentManager).start()
dialog.dismiss()
- }
-
- @Test
- fun testShowDialog_displayBluetoothDevice() {
- testScope.runTest {
- val dialog = mBluetoothTileDialogDelegate.createDialog()
- dialog.show()
- fakeSystemClock.setElapsedRealtime(Long.MAX_VALUE)
- mBluetoothTileDialogDelegate.onDeviceItemUpdated(
- dialog,
- listOf(deviceItem),
- showSeeAll = false,
- showPairNewDevice = false,
- )
-
- val recyclerView = dialog.requireViewById<RecyclerView>(R.id.device_list)
- val adapter = recyclerView?.adapter as BluetoothTileDialogDelegate.Adapter
- assertThat(adapter.itemCount).isEqualTo(1)
- assertThat(adapter.getItem(0).deviceName).isEqualTo(DEVICE_NAME)
- assertThat(adapter.getItem(0).connectionSummary).isEqualTo(DEVICE_CONNECTION_SUMMARY)
- assertThat(adapter.getItem(0).iconWithDescription).isEqualTo(icon)
- dialog.dismiss()
- }
- }
-
- @Test
- fun testDeviceItemViewHolder_cachedDeviceNotBusy() {
- testScope.runTest {
- deviceItem.isEnabled = true
-
- val view =
- LayoutInflater.from(mContext).inflate(R.layout.bluetooth_device_item, null, false)
- val viewHolder = mBluetoothTileDialogDelegate.Adapter().DeviceItemViewHolder(view)
- viewHolder.bind(deviceItem)
- val container = view.requireViewById<View>(R.id.bluetooth_device_row)
-
- assertThat(container).isNotNull()
- assertThat(container.isEnabled).isTrue()
- assertThat(container.hasOnClickListeners()).isTrue()
- val value by collectLastValue(mBluetoothTileDialogDelegate.deviceItemClick)
- runCurrent()
- container.performClick()
- runCurrent()
- assertThat(value).isNotNull()
- value?.let {
- assertThat(it.target).isEqualTo(DeviceItemClick.Target.ENTIRE_ROW)
- assertThat(it.clickedView).isEqualTo(container)
- assertThat(it.deviceItem).isEqualTo(deviceItem)
- }
- }
- }
-
- @Test
- fun testDeviceItemViewHolder_cachedDeviceBusy() {
- deviceItem.isEnabled = false
-
- val view =
- LayoutInflater.from(mContext).inflate(R.layout.bluetooth_device_item, null, false)
- val viewHolder =
- BluetoothTileDialogDelegate(
- uiProperties,
- CONTENT_HEIGHT,
- bluetoothTileDialogCallback,
- {},
- dispatcher,
- fakeSystemClock,
- uiEventLogger,
- logger,
- sysuiDialogFactory,
- kosmos.shadeDialogContextInteractor,
- )
- .Adapter()
- .DeviceItemViewHolder(view)
- viewHolder.bind(deviceItem)
- val container = view.requireViewById<View>(R.id.bluetooth_device_row)
-
- assertThat(container).isNotNull()
- assertThat(container.isEnabled).isFalse()
- assertThat(container.hasOnClickListeners()).isTrue()
- }
-
- @Test
- fun testDeviceItemViewHolder_clickActionIcon() {
- testScope.runTest {
- deviceItem.isEnabled = true
-
- val view =
- LayoutInflater.from(mContext).inflate(R.layout.bluetooth_device_item, null, false)
- val viewHolder = mBluetoothTileDialogDelegate.Adapter().DeviceItemViewHolder(view)
- viewHolder.bind(deviceItem)
- val actionIconView = view.requireViewById<View>(R.id.gear_icon)
-
- assertThat(actionIconView).isNotNull()
- assertThat(actionIconView.hasOnClickListeners()).isTrue()
- val value by collectLastValue(mBluetoothTileDialogDelegate.deviceItemClick)
- runCurrent()
- actionIconView.performClick()
- runCurrent()
- assertThat(value).isNotNull()
- value?.let {
- assertThat(it.target).isEqualTo(DeviceItemClick.Target.ACTION_ICON)
- assertThat(it.clickedView).isEqualTo(actionIconView)
- assertThat(it.deviceItem).isEqualTo(deviceItem)
- }
- }
- }
-
- @Test
- fun testOnDeviceUpdated_hideSeeAll_showPairNew() {
- testScope.runTest {
- val dialog = mBluetoothTileDialogDelegate.createDialog()
- dialog.show()
- fakeSystemClock.setElapsedRealtime(Long.MAX_VALUE)
- mBluetoothTileDialogDelegate.onDeviceItemUpdated(
- dialog,
- listOf(deviceItem),
- showSeeAll = false,
- showPairNewDevice = true,
- )
-
- val seeAllButton = dialog.requireViewById<View>(R.id.see_all_button)
- val pairNewButton = dialog.requireViewById<View>(R.id.pair_new_device_button)
- val recyclerView = dialog.requireViewById<RecyclerView>(R.id.device_list)
- val adapter = recyclerView?.adapter as BluetoothTileDialogDelegate.Adapter
- val scrollViewContent = dialog.requireViewById<View>(R.id.scroll_view)
-
- assertThat(seeAllButton).isNotNull()
- assertThat(seeAllButton.visibility).isEqualTo(GONE)
- assertThat(pairNewButton).isNotNull()
- assertThat(pairNewButton.visibility).isEqualTo(VISIBLE)
- assertThat(adapter.itemCount).isEqualTo(1)
- assertThat(scrollViewContent.layoutParams.height).isEqualTo(WRAP_CONTENT)
- dialog.dismiss()
- }
- }
-
- @Test
- fun testShowDialog_cachedHeightLargerThanMinHeight_displayFromCachedHeight() {
- testScope.runTest {
- val cachedHeight = Int.MAX_VALUE
- val dialog =
- BluetoothTileDialogDelegate(
- BluetoothTileDialogViewModel.UiProperties.build(ENABLED, ENABLED),
- cachedHeight,
- bluetoothTileDialogCallback,
- {},
- dispatcher,
- fakeSystemClock,
- uiEventLogger,
- logger,
- sysuiDialogFactory,
- kosmos.shadeDialogContextInteractor,
- )
- .createDialog()
- dialog.show()
- assertThat(dialog.requireViewById<View>(R.id.scroll_view).layoutParams.height)
- .isEqualTo(cachedHeight)
- dialog.dismiss()
- }
- }
-
- @Test
- fun testShowDialog_cachedHeightLessThanMinHeight_displayFromUiProperties() {
- testScope.runTest {
- val dialog =
- BluetoothTileDialogDelegate(
- BluetoothTileDialogViewModel.UiProperties.build(ENABLED, ENABLED),
- MATCH_PARENT,
- bluetoothTileDialogCallback,
- {},
- dispatcher,
- fakeSystemClock,
- uiEventLogger,
- logger,
- sysuiDialogFactory,
- kosmos.shadeDialogContextInteractor,
- )
- .createDialog()
- dialog.show()
- assertThat(dialog.requireViewById<View>(R.id.scroll_view).layoutParams.height)
- .isGreaterThan(MATCH_PARENT)
- dialog.dismiss()
- }
- }
-
- @Test
- fun testShowDialog_bluetoothEnabled_autoOnToggleGone() {
- testScope.runTest {
- val dialog =
- BluetoothTileDialogDelegate(
- BluetoothTileDialogViewModel.UiProperties.build(ENABLED, ENABLED),
- MATCH_PARENT,
- bluetoothTileDialogCallback,
- {},
- dispatcher,
- fakeSystemClock,
- uiEventLogger,
- logger,
- sysuiDialogFactory,
- kosmos.shadeDialogContextInteractor,
- )
- .createDialog()
- dialog.show()
- assertThat(
- dialog.requireViewById<View>(R.id.bluetooth_auto_on_toggle_layout).visibility
- )
- .isEqualTo(GONE)
- dialog.dismiss()
- }
- }
-
- @Test
- fun testOnAudioSharingButtonUpdated_visibleActive_activateButton() {
- testScope.runTest {
- val dialog = mBluetoothTileDialogDelegate.createDialog()
- dialog.show()
- fakeSystemClock.setElapsedRealtime(Long.MAX_VALUE)
- mBluetoothTileDialogDelegate.onAudioSharingButtonUpdated(
- dialog,
- visibility = VISIBLE,
- label = null,
- isActive = true,
- )
-
- val audioSharingButton = dialog.requireViewById<View>(R.id.audio_sharing_button)
-
- assertThat(audioSharingButton).isNotNull()
- assertThat(audioSharingButton.visibility).isEqualTo(VISIBLE)
- assertThat(audioSharingButton.isActivated).isTrue()
- dialog.dismiss()
- }
- }
-
- @Test
- fun testOnAudioSharingButtonUpdated_visibleNotActive_inactivateButton() {
- testScope.runTest {
- val dialog = mBluetoothTileDialogDelegate.createDialog()
- dialog.show()
- fakeSystemClock.setElapsedRealtime(Long.MAX_VALUE)
- mBluetoothTileDialogDelegate.onAudioSharingButtonUpdated(
- dialog,
- visibility = VISIBLE,
- label = null,
- isActive = false,
- )
-
- val audioSharingButton = dialog.requireViewById<View>(R.id.audio_sharing_button)
-
- assertThat(audioSharingButton).isNotNull()
- assertThat(audioSharingButton.visibility).isEqualTo(VISIBLE)
- assertThat(audioSharingButton.isActivated).isFalse()
- dialog.dismiss()
- }
- }
-
- @Test
- fun testOnAudioSharingButtonUpdated_gone_inactivateButton() {
- testScope.runTest {
- val dialog = mBluetoothTileDialogDelegate.createDialog()
- dialog.show()
- fakeSystemClock.setElapsedRealtime(Long.MAX_VALUE)
- mBluetoothTileDialogDelegate.onAudioSharingButtonUpdated(
- dialog,
- visibility = GONE,
- label = null,
- isActive = false,
- )
-
- val audioSharingButton = dialog.requireViewById<View>(R.id.audio_sharing_button)
-
- assertThat(audioSharingButton).isNotNull()
- assertThat(audioSharingButton.visibility).isEqualTo(GONE)
- assertThat(audioSharingButton.isActivated).isFalse()
- dialog.dismiss()
- }
+ verify(bluetoothDetailsContentManager).releaseView()
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModelTest.kt
index 242d31a26b13..47a834be9b9c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModelTest.kt
@@ -76,8 +76,6 @@ class BluetoothTileDialogViewModelTest : SysuiTestCase() {
private lateinit var bluetoothTileDialogViewModel: BluetoothTileDialogViewModel
- @Mock private lateinit var bluetoothStateInteractor: BluetoothStateInteractor
-
@Mock private lateinit var bluetoothDeviceMetadataInteractor: BluetoothDeviceMetadataInteractor
@Mock private lateinit var deviceItemInteractor: DeviceItemInteractor
@@ -106,9 +104,16 @@ class BluetoothTileDialogViewModelTest : SysuiTestCase() {
@Mock private lateinit var bluetoothTileDialogDelegate: BluetoothTileDialogDelegate
+ @Mock
+ private lateinit var bluetoothDetailsContentManagerFactory:
+ BluetoothDetailsContentManager.Factory
+
+ @Mock private lateinit var bluetoothDetailsContentManager: BluetoothDetailsContentManager
+
@Mock private lateinit var sysuiDialog: SystemUIDialog
@Mock private lateinit var expandable: Expandable
@Mock private lateinit var controller: DialogTransitionAnimator.Controller
+ @Mock private lateinit var mockView: View
private val sharedPreferences = FakeSharedPreferences()
@@ -129,7 +134,7 @@ class BluetoothTileDialogViewModelTest : SysuiTestCase() {
localBluetoothManager,
bluetoothTileDialogLogger,
testScope.backgroundScope,
- dispatcher
+ dispatcher,
),
// TODO(b/316822488): Create FakeBluetoothAutoOnInteractor.
BluetoothAutoOnInteractor(
@@ -137,7 +142,7 @@ class BluetoothTileDialogViewModelTest : SysuiTestCase() {
localBluetoothManager,
bluetoothAdapter,
testScope.backgroundScope,
- dispatcher
+ dispatcher,
)
),
kosmos.audioSharingInteractor,
@@ -151,7 +156,8 @@ class BluetoothTileDialogViewModelTest : SysuiTestCase() {
dispatcher,
dispatcher,
sharedPreferences,
- mBluetoothTileDialogDelegateDelegateFactory
+ mBluetoothTileDialogDelegateDelegateFactory,
+ bluetoothDetailsContentManagerFactory,
)
whenever(deviceItemInteractor.deviceItemUpdate).thenReturn(MutableSharedFlow())
whenever(deviceItemInteractor.deviceItemUpdateRequest)
@@ -161,20 +167,34 @@ class BluetoothTileDialogViewModelTest : SysuiTestCase() {
whenever(mBluetoothTileDialogDelegateDelegateFactory.create(any(), anyInt(), any(), any()))
.thenReturn(bluetoothTileDialogDelegate)
whenever(bluetoothTileDialogDelegate.createDialog()).thenReturn(sysuiDialog)
+ whenever(bluetoothTileDialogDelegate.contentManager)
+ .thenReturn(bluetoothDetailsContentManager)
+ whenever(
+ bluetoothDetailsContentManagerFactory.create(
+ any(),
+ anyInt(),
+ any(),
+ anyBoolean(),
+ any(),
+ )
+ )
+ .thenReturn(bluetoothDetailsContentManager)
whenever(sysuiDialog.context).thenReturn(mContext)
- whenever(bluetoothTileDialogDelegate.bluetoothStateToggle)
+ whenever(bluetoothDetailsContentManager.bluetoothStateToggle)
.thenReturn(getMutableStateFlow(false))
- whenever(bluetoothTileDialogDelegate.deviceItemClick).thenReturn(MutableSharedFlow())
- whenever(bluetoothTileDialogDelegate.contentHeight).thenReturn(getMutableStateFlow(0))
- whenever(bluetoothTileDialogDelegate.bluetoothAutoOnToggle)
+ whenever(bluetoothDetailsContentManager.deviceItemClick)
+ .thenReturn(getMutableStateFlow(null))
+ whenever(bluetoothDetailsContentManager.contentHeight).thenReturn(getMutableStateFlow(0))
+ whenever(bluetoothDetailsContentManager.bluetoothAutoOnToggle)
.thenReturn(getMutableStateFlow(false))
whenever(expandable.dialogTransitionController(any())).thenReturn(controller)
+ whenever(mockView.context).thenReturn(mContext)
}
@Test
- fun testShowDialog_noAnimation() {
+ fun testShowDetailsContent_noAnimation() {
testScope.runTest {
- bluetoothTileDialogViewModel.showDialog(null)
+ bluetoothTileDialogViewModel.showDetailsContent(null, null)
runCurrent()
verify(mDialogTransitionAnimator, never()).show(any(), any(), any())
@@ -182,9 +202,9 @@ class BluetoothTileDialogViewModelTest : SysuiTestCase() {
}
@Test
- fun testShowDialog_animated() {
+ fun testShowDetailsContent_animated() {
testScope.runTest {
- bluetoothTileDialogViewModel.showDialog(expandable)
+ bluetoothTileDialogViewModel.showDetailsContent(expandable, null)
runCurrent()
verify(mDialogTransitionAnimator).show(any(), any(), anyBoolean())
@@ -192,10 +212,21 @@ class BluetoothTileDialogViewModelTest : SysuiTestCase() {
}
@Test
- fun testShowDialog_animated_callInBackgroundThread() {
+ fun testShowDetailsContent_animated_inDetailsView() {
+ testScope.runTest {
+ bluetoothTileDialogViewModel.showDetailsContent(expandable, mockView)
+ runCurrent()
+
+ verify(bluetoothDetailsContentManager).bind(mockView)
+ verify(bluetoothDetailsContentManager).start()
+ }
+ }
+
+ @Test
+ fun testShowDetailsContent_animated_callInBackgroundThread() {
testScope.runTest {
backgroundExecutor.execute {
- bluetoothTileDialogViewModel.showDialog(expandable)
+ bluetoothTileDialogViewModel.showDetailsContent(expandable, null)
runCurrent()
verify(mDialogTransitionAnimator).show(any(), any(), anyBoolean())
@@ -204,9 +235,22 @@ class BluetoothTileDialogViewModelTest : SysuiTestCase() {
}
@Test
- fun testShowDialog_fetchDeviceItem() {
+ fun testShowDetailsContent_animated_callInBackgroundThread_inDetailsView() {
+ testScope.runTest {
+ backgroundExecutor.execute {
+ bluetoothTileDialogViewModel.showDetailsContent(expandable, mockView)
+ runCurrent()
+
+ verify(bluetoothDetailsContentManager).bind(mockView)
+ verify(bluetoothDetailsContentManager).start()
+ }
+ }
+ }
+
+ @Test
+ fun testShowDetailsContent_fetchDeviceItem() {
testScope.runTest {
- bluetoothTileDialogViewModel.showDialog(null)
+ bluetoothTileDialogViewModel.showDetailsContent(null, null)
runCurrent()
verify(deviceItemInteractor).deviceItemUpdate
@@ -217,7 +261,7 @@ class BluetoothTileDialogViewModelTest : SysuiTestCase() {
fun testStartSettingsActivity_activityLaunched_dialogDismissed() {
testScope.runTest {
whenever(deviceItem.cachedBluetoothDevice).thenReturn(cachedBluetoothDevice)
- bluetoothTileDialogViewModel.showDialog(null)
+ bluetoothTileDialogViewModel.showDetailsContent(null, null)
runCurrent()
val clickedView = View(context)
@@ -234,7 +278,7 @@ class BluetoothTileDialogViewModelTest : SysuiTestCase() {
val actual =
BluetoothTileDialogViewModel.UiProperties.build(
isBluetoothEnabled = true,
- isAutoOnToggleFeatureAvailable = true
+ isAutoOnToggleFeatureAvailable = true,
)
assertThat(actual.autoOnToggleVisibility).isEqualTo(GONE)
}
@@ -246,7 +290,7 @@ class BluetoothTileDialogViewModelTest : SysuiTestCase() {
val actual =
BluetoothTileDialogViewModel.UiProperties.build(
isBluetoothEnabled = false,
- isAutoOnToggleFeatureAvailable = true
+ isAutoOnToggleFeatureAvailable = true,
)
assertThat(actual.autoOnToggleVisibility).isEqualTo(VISIBLE)
}
@@ -258,7 +302,7 @@ class BluetoothTileDialogViewModelTest : SysuiTestCase() {
val actual =
BluetoothTileDialogViewModel.UiProperties.build(
isBluetoothEnabled = false,
- isAutoOnToggleFeatureAvailable = false
+ isAutoOnToggleFeatureAvailable = false,
)
assertThat(actual.autoOnToggleVisibility).isEqualTo(GONE)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/brightness/ui/compose/BrightnessSliderMotionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/brightness/ui/compose/BrightnessSliderMotionTest.kt
new file mode 100644
index 000000000000..9dab9d735603
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/brightness/ui/compose/BrightnessSliderMotionTest.kt
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.brightness.ui.compose
+
+import android.platform.test.annotations.MotionTest
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.wrapContentHeight
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
+import androidx.compose.ui.test.hasTestTag
+import androidx.compose.ui.test.swipeLeft
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.LargeTest
+import com.android.compose.theme.PlatformTheme
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.brightness.ui.viewmodel.BrightnessSliderViewModel
+import com.android.systemui.common.shared.model.asIcon
+import com.android.systemui.haptics.slider.sliderHapticsViewModelFactory
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.motion.createSysUiComposeMotionTestRule
+import com.android.systemui.utils.PolicyRestriction
+import kotlin.test.Test
+import kotlin.time.Duration.Companion.seconds
+import kotlinx.coroutines.async
+import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.joinAll
+import org.junit.Rule
+import org.junit.runner.RunWith
+import platform.test.motion.compose.ComposeRecordingSpec
+import platform.test.motion.compose.MotionControl
+import platform.test.motion.compose.MotionControlScope
+import platform.test.motion.compose.feature
+import platform.test.motion.compose.motionTestValueOfNode
+import platform.test.motion.compose.recordMotion
+import platform.test.motion.compose.runTest
+import platform.test.motion.compose.values.MotionTestValueKey
+import platform.test.motion.golden.FeatureCapture
+import platform.test.motion.golden.TimeSeriesCaptureScope
+import platform.test.motion.golden.asDataPoint
+import platform.test.screenshot.DeviceEmulationSpec
+import platform.test.screenshot.Displays.Phone
+
+@RunWith(AndroidJUnit4::class)
+@LargeTest
+@MotionTest
+class BrightnessSliderMotionTest : SysuiTestCase() {
+
+ private val deviceSpec = DeviceEmulationSpec(Phone)
+ private val kosmos = Kosmos()
+
+ @get:Rule val motionTestRule = createSysUiComposeMotionTestRule(kosmos, deviceSpec)
+
+ @Composable
+ private fun BrightnessSliderUnderTest(startingValue: Int) {
+ PlatformTheme {
+ BrightnessSlider(
+ gammaValue = startingValue,
+ modifier = Modifier.wrapContentHeight().fillMaxWidth(),
+ valueRange = 0..100,
+ iconResProvider = BrightnessSliderViewModel::getIconForPercentage,
+ imageLoader = { resId, context -> context.getDrawable(resId)!!.asIcon(null) },
+ restriction = PolicyRestriction.NoRestriction,
+ onRestrictedClick = {},
+ onDrag = {},
+ onStop = {},
+ overriddenByAppState = false,
+ hapticsViewModelFactory = kosmos.sliderHapticsViewModelFactory,
+ )
+ }
+ }
+
+ @Test
+ fun iconAlphaChanges() {
+ motionTestRule.runTest(timeout = 30.seconds) {
+ val motion =
+ recordMotion(
+ content = { BrightnessSliderUnderTest(100) },
+ ComposeRecordingSpec(
+ MotionControl(delayReadyToPlay = { awaitCondition { !isAnimating } }) {
+ coroutineScope {
+ val gesture = async {
+ performTouchInputAsync(
+ onNode(hasTestTag("com.android.systemui:id/slider"))
+ ) {
+ swipeLeft(startX = right, endX = left, durationMillis = 500)
+ }
+ }
+ val animationEnd = async {
+ awaitCondition { isAnimating }
+ awaitCondition { !isAnimating }
+ }
+ joinAll(gesture, animationEnd)
+ }
+ }
+ ) {
+ featureFloat(BrightnessSliderMotionTestKeys.ActiveIconAlpha)
+ featureFloat(BrightnessSliderMotionTestKeys.InactiveIconAlpha)
+ },
+ )
+ assertThat(motion).timeSeriesMatchesGolden("brightnessSlider_iconAlphaChanges")
+ }
+ }
+
+ private companion object {
+
+ val MotionControlScope.isAnimating: Boolean
+ get() = motionTestValueOfNode(BrightnessSliderMotionTestKeys.AnimatingIcon)
+
+ fun TimeSeriesCaptureScope<SemanticsNodeInteractionsProvider>.featureFloat(
+ motionTestValueKey: MotionTestValueKey<Float>
+ ) {
+ feature(
+ motionTestValueKey = motionTestValueKey,
+ capture =
+ FeatureCapture(motionTestValueKey.semanticsPropertyKey.name) {
+ it.asDataPoint()
+ },
+ )
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/lowlightclock/AmbientLightModeMonitorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/lowlightclock/AmbientLightModeMonitorTest.kt
index 43ee388e44a7..8a2dc15d7545 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/lowlightclock/AmbientLightModeMonitorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/lowlightclock/AmbientLightModeMonitorTest.kt
@@ -23,6 +23,7 @@ import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.util.sensors.AsyncSensorManager
import java.util.Optional
+import javax.inject.Provider
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -50,7 +51,11 @@ class AmbientLightModeMonitorTest : SysuiTestCase() {
MockitoAnnotations.initMocks(this)
ambientLightModeMonitor =
- AmbientLightModeMonitor(Optional.of(algorithm), sensorManager, Optional.of(sensor))
+ AmbientLightModeMonitor(
+ Optional.of(algorithm),
+ sensorManager,
+ Optional.of(Provider { sensor }),
+ )
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaSwitchingControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaSwitchingControllerTest.java
index 2715cb31ca8b..86094d1a0fef 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaSwitchingControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaSwitchingControllerTest.java
@@ -891,6 +891,13 @@ public class MediaSwitchingControllerTest extends SysuiTestCase {
}
@Test
+ public void getTransferableMediaDevice_triggersFromLocalMediaManager() {
+ mMediaSwitchingController.getTransferableMediaDevices();
+
+ verify(mLocalMediaManager).getTransferableMediaDevices();
+ }
+
+ @Test
public void getDeselectableMediaDevice_triggersFromLocalMediaManager() {
mMediaSwitchingController.getDeselectableMediaDevice();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/DragAndDropTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/DragAndDropTest.kt
index fc720b836f72..26cf4a261289 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/DragAndDropTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/DragAndDropTest.kt
@@ -68,6 +68,7 @@ class DragAndDropTest : SysuiTestCase() {
columns = 4,
largeTilesSpan = 4,
modifier = Modifier.fillMaxSize(),
+ onAddTile = {},
onRemoveTile = {},
onSetTiles = onSetTiles,
onResize = { _, _ -> },
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/EditModeTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/EditModeTest.kt
new file mode 100644
index 000000000000..4e8b0bcd374c
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/EditModeTest.kt
@@ -0,0 +1,199 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.panels.ui.compose
+
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.semantics.SemanticsProperties
+import androidx.compose.ui.test.SemanticsMatcher
+import androidx.compose.ui.test.assert
+import androidx.compose.ui.test.filter
+import androidx.compose.ui.test.hasContentDescription
+import androidx.compose.ui.test.junit4.ComposeContentTestRule
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onChildren
+import androidx.compose.ui.test.onNodeWithContentDescription
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.test.performClick
+import androidx.compose.ui.text.AnnotatedString
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.shared.model.ContentDescription
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.qs.panels.shared.model.SizedTile
+import com.android.systemui.qs.panels.shared.model.SizedTileImpl
+import com.android.systemui.qs.panels.ui.compose.infinitegrid.DefaultEditTileGrid
+import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.shared.model.TileCategory
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class EditModeTest : SysuiTestCase() {
+ @get:Rule val composeRule = createComposeRule()
+
+ @Composable
+ private fun EditTileGridUnderTest() {
+ var tiles by remember { mutableStateOf(TestEditTiles) }
+ val (currentTiles, otherTiles) = tiles.partition { it.tile.isCurrent }
+ val listState = EditTileListState(currentTiles, columns = 4, largeTilesSpan = 2)
+ DefaultEditTileGrid(
+ listState = listState,
+ otherTiles = otherTiles,
+ columns = 4,
+ largeTilesSpan = 4,
+ modifier = Modifier.fillMaxSize(),
+ onAddTile = { tiles = tiles.add(it) },
+ onRemoveTile = { tiles = tiles.remove(it) },
+ onSetTiles = {},
+ onResize = { _, _ -> },
+ onStopEditing = {},
+ onReset = null,
+ )
+ }
+
+ @Test
+ fun clickAvailableTile_shouldAdd() {
+ composeRule.setContent { EditTileGridUnderTest() }
+ composeRule.waitForIdle()
+
+ composeRule.onNodeWithContentDescription("tileF").performClick() // Tap to add
+ composeRule.waitForIdle()
+
+ composeRule.assertCurrentTilesGridContainsExactly(
+ listOf("tileA", "tileB", "tileC", "tileD_large", "tileE", "tileF")
+ )
+ composeRule.assertAvailableTilesGridContainsExactly(listOf("tileG_large"))
+ }
+
+ @Test
+ fun clickRemoveTarget_shouldRemoveSelection() {
+ composeRule.setContent { EditTileGridUnderTest() }
+ composeRule.waitForIdle()
+
+ composeRule.onNodeWithContentDescription("tileA").performClick() // Selects
+ composeRule.onNodeWithText("Remove").performClick() // Removes
+
+ composeRule.waitForIdle()
+
+ composeRule.assertCurrentTilesGridContainsExactly(
+ listOf("tileB", "tileC", "tileD_large", "tileE")
+ )
+ composeRule.assertAvailableTilesGridContainsExactly(listOf("tileA", "tileF", "tileG_large"))
+ }
+
+ private fun ComposeContentTestRule.assertCurrentTilesGridContainsExactly(specs: List<String>) =
+ assertGridContainsExactly(CURRENT_TILES_GRID_TEST_TAG, specs)
+
+ private fun ComposeContentTestRule.assertAvailableTilesGridContainsExactly(
+ specs: List<String>
+ ) = assertGridContainsExactly(AVAILABLE_TILES_GRID_TEST_TAG, specs)
+
+ private fun ComposeContentTestRule.assertGridContainsExactly(
+ testTag: String,
+ specs: List<String>,
+ ) {
+ onNodeWithTag(testTag)
+ .onChildren()
+ .filter(SemanticsMatcher.keyIsDefined(SemanticsProperties.ContentDescription))
+ .apply {
+ fetchSemanticsNodes().forEachIndexed { index, _ ->
+ get(index).assert(hasContentDescription(specs[index]))
+ }
+ }
+ }
+
+ companion object {
+ private const val CURRENT_TILES_GRID_TEST_TAG = "CurrentTilesGrid"
+ private const val AVAILABLE_TILES_GRID_TEST_TAG = "AvailableTilesGrid"
+
+ private fun List<SizedTile<EditTileViewModel>>.add(
+ spec: TileSpec
+ ): List<SizedTile<EditTileViewModel>> {
+ return map {
+ if (it.tile.tileSpec == spec) {
+ createEditTile(it.tile.tileSpec.spec)
+ } else {
+ it
+ }
+ }
+ }
+
+ private fun List<SizedTile<EditTileViewModel>>.remove(
+ spec: TileSpec
+ ): List<SizedTile<EditTileViewModel>> {
+ return map {
+ if (it.tile.tileSpec == spec) {
+ createEditTile(it.tile.tileSpec.spec, isCurrent = false)
+ } else {
+ it
+ }
+ }
+ }
+
+ private fun createEditTile(
+ tileSpec: String,
+ isCurrent: Boolean = true,
+ ): SizedTile<EditTileViewModel> {
+ return SizedTileImpl(
+ EditTileViewModel(
+ tileSpec = TileSpec.create(tileSpec),
+ icon =
+ Icon.Resource(
+ android.R.drawable.star_on,
+ ContentDescription.Loaded(tileSpec),
+ ),
+ label = AnnotatedString(tileSpec),
+ appName = null,
+ isCurrent = isCurrent,
+ availableEditActions = emptySet(),
+ category = TileCategory.UNKNOWN,
+ ),
+ getWidth(tileSpec),
+ )
+ }
+
+ private fun getWidth(tileSpec: String): Int {
+ return if (tileSpec.endsWith("large")) {
+ 2
+ } else {
+ 1
+ }
+ }
+
+ private val TestEditTiles =
+ listOf(
+ createEditTile("tileA"),
+ createEditTile("tileB"),
+ createEditTile("tileC"),
+ createEditTile("tileD_large"),
+ createEditTile("tileE"),
+ createEditTile("tileF", isCurrent = false),
+ createEditTile("tileG_large", isCurrent = false),
+ )
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/ResizingTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/ResizingTest.kt
index f23553eda3b2..a0be02f1ef7e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/ResizingTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/ResizingTest.kt
@@ -65,6 +65,7 @@ class ResizingTest : SysuiTestCase() {
columns = 4,
largeTilesSpan = 4,
modifier = Modifier.fillMaxSize(),
+ onAddTile = {},
onRemoveTile = {},
onSetTiles = {},
onResize = onResize,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt
index 330b887b70a3..1305b0c4c499 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt
@@ -238,7 +238,8 @@ class BluetoothTileTest(flags: FlagsParameterization) : SysuiTestCase() {
tile.handleClick(null)
- verify(bluetoothTileDialogViewModel).showDialog(null)
+ verify(bluetoothTileDialogViewModel)
+ .showDetailsContent(/* expandable= */ null, /* view= */ null)
}
@Test
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/brightness/ui/viewmodel/BrightnessSliderViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/brightness/ui/viewmodel/BrightnessSliderViewModelKosmos.kt
index 35e85bb1e68d..b6f8ec666001 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/brightness/ui/viewmodel/BrightnessSliderViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/brightness/ui/viewmodel/BrightnessSliderViewModelKosmos.kt
@@ -19,6 +19,7 @@ package com.android.systemui.brightness.ui.viewmodel
import com.android.systemui.brightness.domain.interactor.brightnessPolicyEnforcementInteractor
import com.android.systemui.brightness.domain.interactor.screenBrightnessInteractor
import com.android.systemui.classifier.domain.interactor.falsingInteractor
+import com.android.systemui.graphics.imageLoader
import com.android.systemui.haptics.slider.sliderHapticsViewModelFactory
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.settings.brightness.domain.interactor.brightnessMirrorShowingInteractor
@@ -36,6 +37,7 @@ val Kosmos.brightnessSliderViewModelFactory: BrightnessSliderViewModel.Factory b
supportsMirroring = allowsMirroring,
falsingInteractor = falsingInteractor,
brightnessWarningToast = brightnessWarningToast,
+ imageLoader = imageLoader,
)
}
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalSceneRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalSceneRepository.kt
index b3c1411243c1..3f35bb9f3520 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalSceneRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalSceneRepository.kt
@@ -17,8 +17,7 @@ import kotlinx.coroutines.launch
/** Fake implementation of [CommunalSceneRepository]. */
class FakeCommunalSceneRepository(
private val applicationScope: CoroutineScope,
- override val currentScene: MutableStateFlow<SceneKey> =
- MutableStateFlow(CommunalScenes.Default),
+ override val currentScene: MutableStateFlow<SceneKey> = MutableStateFlow(CommunalScenes.Default),
) : CommunalSceneRepository {
override fun changeScene(toScene: SceneKey, transitionKey: TransitionKey?) =
@@ -31,6 +30,10 @@ class FakeCommunalSceneRepository(
}
}
+ override suspend fun showHubFromPowerButton() {
+ snapToScene(CommunalScenes.Communal)
+ }
+
private val defaultTransitionState = ObservableTransitionState.Idle(CommunalScenes.Default)
private val _transitionState = MutableStateFlow<Flow<ObservableTransitionState>?>(null)
override val transitionState: StateFlow<ObservableTransitionState> =
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/statusbar/StatusBarStateControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/statusbar/StatusBarStateControllerKosmos.kt
index 2e6d8ed5aa5b..4f024d7509ba 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/statusbar/StatusBarStateControllerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/statusbar/StatusBarStateControllerKosmos.kt
@@ -19,7 +19,6 @@ package com.android.systemui.plugins.statusbar
import com.android.internal.logging.uiEventLogger
import com.android.systemui.bouncer.domain.interactor.alternateBouncerInteractor
import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor
-import com.android.systemui.jank.interactionJankMonitor
import com.android.systemui.keyguard.domain.interactor.keyguardClockInteractor
import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
@@ -37,7 +36,6 @@ var Kosmos.statusBarStateController: SysuiStatusBarStateController by
Kosmos.Fixture {
StatusBarStateControllerImpl(
uiEventLogger,
- { interactionJankMonitor },
mock(),
{ keyguardInteractor },
{ keyguardTransitionInteractor },
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelKosmos.kt
index 65e580cafcb5..583a9def8094 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelKosmos.kt
@@ -33,6 +33,7 @@ import com.android.systemui.qs.footerActionsViewModelFactory
import com.android.systemui.qs.panels.domain.interactor.tileSquishinessInteractor
import com.android.systemui.qs.panels.ui.viewmodel.inFirstPageViewModel
import com.android.systemui.qs.panels.ui.viewmodel.mediaInRowInLandscapeViewModelFactory
+import com.android.systemui.qs.panels.ui.viewmodel.quickQuickSettingsViewModelFactory
import com.android.systemui.qs.ui.viewmodel.quickSettingsContainerViewModelFactory
import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.shade.largeScreenHeaderHelper
@@ -48,6 +49,7 @@ val Kosmos.qsFragmentComposeViewModelFactory by
): QSFragmentComposeViewModel {
return QSFragmentComposeViewModel(
quickSettingsContainerViewModelFactory,
+ quickQuickSettingsViewModelFactory,
mainResources,
footerActionsViewModelFactory,
footerActionsController,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsContainerViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsContainerViewModelKosmos.kt
index f8fa5db4ddf7..3fc73cbc5552 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsContainerViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsContainerViewModelKosmos.kt
@@ -20,9 +20,9 @@ import com.android.systemui.brightness.ui.viewmodel.brightnessSliderViewModelFac
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.qs.panels.ui.viewmodel.detailsViewModel
import com.android.systemui.qs.panels.ui.viewmodel.editModeViewModel
-import com.android.systemui.qs.panels.ui.viewmodel.quickQuickSettingsViewModelFactory
import com.android.systemui.qs.panels.ui.viewmodel.tileGridViewModel
import com.android.systemui.qs.panels.ui.viewmodel.toolbar.toolbarViewModelFactory
+import com.android.systemui.shade.domain.interactor.shadeModeInteractor
import com.android.systemui.shade.ui.viewmodel.shadeHeaderViewModelFactory
val Kosmos.quickSettingsContainerViewModelFactory by
@@ -33,13 +33,13 @@ val Kosmos.quickSettingsContainerViewModelFactory by
): QuickSettingsContainerViewModel {
return QuickSettingsContainerViewModel(
brightnessSliderViewModelFactory,
- quickQuickSettingsViewModelFactory,
shadeHeaderViewModelFactory,
supportsBrightnessMirroring,
tileGridViewModel,
editModeViewModel,
detailsViewModel,
toolbarViewModelFactory,
+ shadeModeInteractor,
)
}
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModelKosmos.kt
index 4f3b8f3541e1..108a20ab73d6 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModelKosmos.kt
@@ -19,7 +19,6 @@ package com.android.systemui.qs.ui.viewmodel
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.shade.domain.interactor.shadeInteractor
-import com.android.systemui.shade.ui.viewmodel.shadeHeaderViewModelFactory
import com.android.systemui.statusbar.notification.stack.domain.interactor.notificationStackAppearanceInteractor
val Kosmos.quickSettingsShadeOverlayContentViewModel: QuickSettingsShadeOverlayContentViewModel by
@@ -28,7 +27,5 @@ val Kosmos.quickSettingsShadeOverlayContentViewModel: QuickSettingsShadeOverlayC
shadeInteractor = shadeInteractor,
sceneInteractor = sceneInteractor,
notificationStackAppearanceInteractor = notificationStackAppearanceInteractor,
- shadeHeaderViewModelFactory = shadeHeaderViewModelFactory,
- quickSettingsContainerViewModelFactory = quickSettingsContainerViewModelFactory,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneKosmos.kt
index 609f97d0b249..ae4e8d275341 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneKosmos.kt
@@ -4,6 +4,7 @@ import android.view.View
import com.android.compose.animation.scene.ObservableTransitionState
import com.android.systemui.classifier.domain.interactor.falsingInteractor
import com.android.systemui.haptics.msdl.msdlPlayer
+import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
import com.android.systemui.keyguard.ui.viewmodel.lightRevealScrimViewModel
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
@@ -100,6 +101,7 @@ val Kosmos.sceneContainerViewModelFactory by Fixture {
motionEventHandlerReceiver = motionEventHandlerReceiver,
lightRevealScrim = lightRevealScrimViewModel,
wallpaperViewModel = wallpaperViewModel,
+ keyguardInteractor = keyguardInteractor,
)
}
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelKosmos.kt
index 7eb9f3472482..2be8acb845b9 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelKosmos.kt
@@ -17,6 +17,7 @@
package com.android.systemui.shade.ui.viewmodel
import android.content.applicationContext
+import com.android.systemui.battery.BatteryMeterViewController
import com.android.systemui.broadcast.broadcastDispatcher
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.plugins.activityStarter
@@ -24,8 +25,12 @@ import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.shade.domain.interactor.privacyChipInteractor
import com.android.systemui.shade.domain.interactor.shadeHeaderClockInteractor
import com.android.systemui.shade.domain.interactor.shadeInteractor
+import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerStatusBarViewBinder
+import com.android.systemui.statusbar.phone.ui.StatusBarIconController
+import com.android.systemui.statusbar.phone.ui.TintedIconManager
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.mobileIconsInteractor
import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.mobileIconsViewModel
+import org.mockito.kotlin.mock
val Kosmos.shadeHeaderViewModel: ShadeHeaderViewModel by
Kosmos.Fixture {
@@ -38,6 +43,11 @@ val Kosmos.shadeHeaderViewModel: ShadeHeaderViewModel by
mobileIconsViewModel = mobileIconsViewModel,
privacyChipInteractor = privacyChipInteractor,
clockInteractor = shadeHeaderClockInteractor,
+ tintedIconManagerFactory = mock<TintedIconManager.Factory>(),
+ batteryMeterViewControllerFactory = mock<BatteryMeterViewController.Factory>(),
+ statusBarIconController = mock<StatusBarIconController>(),
+ notificationIconContainerStatusBarViewBinder =
+ mock<NotificationIconContainerStatusBarViewBinder>(),
broadcastDispatcher = broadcastDispatcher,
)
}
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java
index 3cb6c5a6bd16..7af03ed2e6c8 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java
@@ -44,6 +44,7 @@ import android.app.UiAutomation;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.res.Resources;
+import android.icu.util.ULocale;
import android.os.Binder;
import android.os.Build;
import android.os.Build.VERSION_CODES;
@@ -81,6 +82,7 @@ import java.io.IOException;
import java.io.PrintStream;
import java.util.Collections;
import java.util.HashMap;
+import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Random;
@@ -228,6 +230,9 @@ public class RavenwoodRuntimeEnvironmentController {
RuntimeInit.redirectLogStreams();
dumpCommandLineArgs();
+ dumpEnvironment();
+ dumpJavaProperties();
+ dumpOtherInfo();
// We haven't initialized liblog yet, so directly write to System.out here.
RavenwoodCommonUtils.log(TAG, "globalInitInner()");
@@ -564,4 +569,34 @@ public class RavenwoodRuntimeEnvironmentController {
Log.v(TAG, " " + arg);
}
}
+
+ private static void dumpJavaProperties() {
+ Log.v(TAG, "JVM properties:");
+ dumpMap(System.getProperties());
+ }
+
+ private static void dumpEnvironment() {
+ Log.v(TAG, "Environment:");
+ dumpMap(System.getenv());
+ }
+
+ private static void dumpMap(Map<?, ?> map) {
+ for (var key : map.keySet().stream().sorted().toList()) {
+ Log.v(TAG, " " + key + "=" + map.get(key));
+ }
+ }
+
+ private static void dumpOtherInfo() {
+ Log.v(TAG, "Other key information:");
+ var jloc = Locale.getDefault();
+ Log.v(TAG, " java.util.Locale=" + jloc + " / " + jloc.toLanguageTag());
+ var uloc = ULocale.getDefault();
+ Log.v(TAG, " android.icu.util.ULocale=" + uloc + " / " + uloc.toLanguageTag());
+
+ var jtz = java.util.TimeZone.getDefault();
+ Log.v(TAG, " java.util.TimeZone=" + jtz.getDisplayName() + " / " + jtz);
+
+ var itz = android.icu.util.TimeZone.getDefault();
+ Log.v(TAG, " android.icu.util.TimeZone=" + itz.getDisplayName() + " / " + itz);
+ }
}
diff --git a/services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerService.java b/services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerService.java
index df47c98d6433..89c9d690a82c 100644
--- a/services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerService.java
+++ b/services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerService.java
@@ -27,10 +27,6 @@ import static android.content.Intent.FLAG_ACTIVITY_NO_USER_ACTION;
import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE;
import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
-import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
-import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL;
-import static android.view.WindowManager.LayoutParams.TYPE_POINTER;
-import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR;
import static com.android.server.wm.ActivityTaskManagerInternal.ASSIST_KEY_CONTENT;
import static com.android.server.wm.ActivityTaskManagerInternal.ASSIST_KEY_STRUCTURE;
@@ -74,6 +70,7 @@ import android.util.Log;
import android.util.Slog;
import android.view.IWindowManager;
import android.window.ScreenCapture;
+import android.window.ScreenCapture.ScreenshotHardwareBuffer;
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
@@ -333,10 +330,10 @@ public class ContextualSearchManagerService extends SystemService {
isManagedProfileVisible = true;
}
}
+ final String csPackage = Objects.requireNonNull(launchIntent.getPackage());
+ final int csUid = mPackageManager.getPackageUid(csPackage, /* flags */ 0L, userId);
if (isAssistDataAllowed) {
try {
- final String csPackage = Objects.requireNonNull(launchIntent.getPackage());
- final int csUid = mPackageManager.getPackageUid(csPackage, 0, 0);
mAssistDataRequester.requestAssistData(
activityTokens,
/* fetchData */ true,
@@ -350,17 +347,8 @@ public class ContextualSearchManagerService extends SystemService {
Log.e(TAG, "Could not request assist data", e);
}
}
- final ScreenCapture.ScreenshotHardwareBuffer shb;
- if (mWmInternal != null) {
- shb = mWmInternal.takeAssistScreenshot(Set.of(
- TYPE_STATUS_BAR,
- TYPE_NAVIGATION_BAR,
- TYPE_NAVIGATION_BAR_PANEL,
- TYPE_POINTER));
- } else {
- if (DEBUG) Log.w(TAG, "Can't capture contextual screenshot: mWmInternal is null");
- shb = null;
- }
+ final ScreenshotHardwareBuffer shb = mWmInternal.takeContextualSearchScreenshot(
+ (Flags.contextualSearchWindowLayer() ? csUid : -1));
final Bitmap bm = shb != null ? shb.asBitmap() : null;
// Now that everything is fetched, putting it in the launchIntent.
if (bm != null) {
@@ -509,15 +497,17 @@ public class ContextualSearchManagerService extends SystemService {
bundle.putParcelable(ContextualSearchManager.EXTRA_TOKEN, mToken);
// We get take the screenshot with the system server's identity because the system
// server has READ_FRAME_BUFFER permission to get the screenshot.
+ final int callingUid = Binder.getCallingUid();
Binder.withCleanCallingIdentity(() -> {
- if (mWmInternal != null) {
+ final ScreenshotHardwareBuffer shb =
+ mWmInternal.takeContextualSearchScreenshot(
+ (Flags.contextualSearchWindowLayer() ? callingUid : -1));
+ final Bitmap bm = shb != null ? shb.asBitmap() : null;
+ if (bm != null) {
bundle.putParcelable(ContextualSearchManager.EXTRA_SCREENSHOT,
- mWmInternal.takeAssistScreenshot(Set.of(
- TYPE_STATUS_BAR,
- TYPE_NAVIGATION_BAR,
- TYPE_NAVIGATION_BAR_PANEL,
- TYPE_POINTER))
- .asBitmap().asShared());
+ bm.asShared());
+ bundle.putBoolean(ContextualSearchManager.EXTRA_FLAG_SECURE_FOUND,
+ shb.containsSecureLayers());
}
try {
callback.onResult(
diff --git a/services/core/java/com/android/server/BootReceiver.java b/services/core/java/com/android/server/BootReceiver.java
index 7a5b8660ef7c..cf0d7e72ba8b 100644
--- a/services/core/java/com/android/server/BootReceiver.java
+++ b/services/core/java/com/android/server/BootReceiver.java
@@ -53,6 +53,7 @@ import com.android.modules.utils.TypedXmlSerializer;
import com.android.server.am.DropboxRateLimiter;
import com.android.server.os.TombstoneProtos.Tombstone;
+import libcore.io.IoUtils;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -154,6 +155,10 @@ public class BootReceiver extends BroadcastReceiver {
@Override
public void onReceive(final Context context, Intent intent) {
+ if (!Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction())) {
+ return;
+ }
+
// Log boot events in the background to avoid blocking the main thread with I/O
new Thread() {
@Override
@@ -219,6 +224,8 @@ public class BootReceiver extends BroadcastReceiver {
} catch (Exception e) {
Slog.wtf(TAG, "Error watching for trace events", e);
return 0; // Unregister the handler.
+ } finally {
+ IoUtils.closeQuietly(fd);
}
return OnFileDescriptorEventListener.EVENT_INPUT;
}
diff --git a/services/core/java/com/android/server/TradeInModeService.java b/services/core/java/com/android/server/TradeInModeService.java
index a69667395ebd..1a9e02c86560 100644
--- a/services/core/java/com/android/server/TradeInModeService.java
+++ b/services/core/java/com/android/server/TradeInModeService.java
@@ -41,12 +41,15 @@ import android.util.Slog;
import java.io.FileWriter;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Paths;
public final class TradeInModeService extends SystemService {
private static final String TAG = "TradeInModeService";
private static final String TIM_PROP = "persist.adb.tradeinmode";
+ private static final String TIM_TEST_PROP = "persist.adb.test_tradeinmode";
private static final int TIM_STATE_UNSET = 0;
@@ -108,6 +111,10 @@ public final class TradeInModeService extends SystemService {
// setup completion observer.
if (isDeviceSetup()) {
stopTradeInMode();
+ } else if (isDebuggable() && !isForceEnabledForTesting()) {
+ // The device was made debuggable after entering TIM. This
+ // can happen while flashing. For convenience, leave test mode.
+ leaveTestMode();
} else {
watchForSetupCompletion();
watchForNetworkChange();
@@ -171,12 +178,7 @@ public final class TradeInModeService extends SystemService {
Slog.e(TAG, "Cannot enter evaluation mode, FRP lock is present.");
return false;
}
-
- try (FileWriter fw = new FileWriter(WIPE_INDICATOR_FILE,
- StandardCharsets.US_ASCII)) {
- fw.write("0");
- } catch (IOException e) {
- Slog.e(TAG, "Failed to write " + WIPE_INDICATOR_FILE, e);
+ if (!scheduleTradeInModeWipe()) {
return false;
}
@@ -189,7 +191,7 @@ public final class TradeInModeService extends SystemService {
}
SystemProperties.set(TIM_PROP, Integer.toString(TIM_STATE_EVALUATION_MODE));
- SystemProperties.set("ctl.restart", "adbd");
+ restartAdbd();
return true;
}
@@ -200,6 +202,55 @@ public final class TradeInModeService extends SystemService {
"Cannot test for trade-in evaluation mode allowed");
return !isFrpActive();
}
+
+ @Override
+ @RequiresPermission(android.Manifest.permission.ENTER_TRADE_IN_MODE)
+ public void scheduleWipeForTesting() {
+ enforceTestingPermissions();
+
+ scheduleTradeInModeWipe();
+ }
+
+ @Override
+ @RequiresPermission(android.Manifest.permission.ENTER_TRADE_IN_MODE)
+ public void startTesting() {
+ enforceTestingPermissions();
+
+ enterTestMode();
+ }
+
+ @Override
+ @RequiresPermission(android.Manifest.permission.ENTER_TRADE_IN_MODE)
+ public void stopTesting() {
+ enforceTestingPermissions();
+
+ if (!isForceEnabledForTesting()) {
+ throw new IllegalStateException("testing must have been started");
+ }
+
+ final long callingId = Binder.clearCallingIdentity();
+ try {
+ leaveTestMode();
+ } finally {
+ Binder.restoreCallingIdentity(callingId);
+ }
+ }
+
+ @Override
+ @RequiresPermission(android.Manifest.permission.ENTER_TRADE_IN_MODE)
+ public boolean isTesting() {
+ enforceTestingPermissions();
+
+ return isForceEnabledForTesting();
+ }
+
+ private void enforceTestingPermissions() {
+ mContext.enforceCallingOrSelfPermission("android.permission.ENTER_TRADE_IN_MODE",
+ "Caller must have ENTER_TRADE_IN_MODE permission");
+ if (!isDebuggable()) {
+ throw new SecurityException("ro.debuggable must be set to 1");
+ }
+ }
}
private void startTradeInMode() {
@@ -207,8 +258,7 @@ public final class TradeInModeService extends SystemService {
SystemProperties.set(TIM_PROP, Integer.toString(TIM_STATE_FOYER));
- final ContentResolver cr = mContext.getContentResolver();
- Settings.Global.putInt(cr, Settings.Global.ADB_ENABLED, 1);
+ setAdbEnabled(true);
watchForSetupCompletion();
watchForNetworkChange();
@@ -223,8 +273,51 @@ public final class TradeInModeService extends SystemService {
removeNetworkWatch();
removeAccountsWatch();
+ if (isForceEnabledForTesting()) {
+ // If testing in a debug build, we need to re-enable ADB.
+ restartAdbd();
+ } else {
+ // Otherwise, ADB must not be enabled.
+ setAdbEnabled(false);
+ }
+ }
+
+ private void enterTestMode() {
+ SystemProperties.set(TIM_TEST_PROP, "1");
+ }
+
+ private void leaveTestMode() {
+ if (getTradeInModeState() == TIM_STATE_FOYER) {
+ stopTradeInMode();
+ }
+
+ SystemProperties.set(TIM_TEST_PROP, "");
+ SystemProperties.set(TIM_PROP, "");
+ try {
+ Files.deleteIfExists(Paths.get(WIPE_INDICATOR_FILE));
+ } catch (IOException e) {
+ Slog.e(TAG, "Failed to remove wipe indicator", e);
+ }
+ }
+
+ private boolean scheduleTradeInModeWipe() {
+ try (FileWriter fw = new FileWriter(WIPE_INDICATOR_FILE,
+ StandardCharsets.US_ASCII)) {
+ fw.write("0");
+ } catch (IOException e) {
+ Slog.e(TAG, "Failed to write " + WIPE_INDICATOR_FILE, e);
+ return false;
+ }
+ return true;
+ }
+
+ private void restartAdbd() {
+ SystemProperties.set("ctl.restart", "adbd");
+ }
+
+ private void setAdbEnabled(boolean enabled) {
final ContentResolver cr = mContext.getContentResolver();
- Settings.Global.putInt(cr, Settings.Global.ADB_ENABLED, 0);
+ Settings.Global.putInt(cr, Settings.Global.ADB_ENABLED, enabled ? 1 : 0);
}
private int getTradeInModeState() {
@@ -236,7 +329,7 @@ public final class TradeInModeService extends SystemService {
}
private boolean isForceEnabledForTesting() {
- return SystemProperties.getInt("persist.adb.test_tradeinmode", 0) == 1;
+ return isDebuggable() && SystemProperties.getInt(TIM_TEST_PROP, 0) == 1;
}
private boolean isAdbEnabled() {
diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index 5184a2c4ec30..c6338307b192 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -186,8 +186,10 @@ public class SettingsToPropertiesMapper {
"core_libraries",
"crumpet",
"dck_framework",
+ "desktop_connectivity",
"desktop_hwsec",
"desktop_stats",
+ "desktop_wifi",
"devoptions_settings",
"game",
"gpu",
@@ -218,6 +220,7 @@ public class SettingsToPropertiesMapper {
"pixel_continuity",
"pixel_display",
"pixel_perf",
+ "pixel_sensai",
"pixel_sensors",
"pixel_state_server",
"pixel_system_sw_video",
diff --git a/services/core/java/com/android/server/audio/AudioManagerShellCommand.java b/services/core/java/com/android/server/audio/AudioManagerShellCommand.java
index fece7a899f0a..ae961b53f547 100644
--- a/services/core/java/com/android/server/audio/AudioManagerShellCommand.java
+++ b/services/core/java/com/android/server/audio/AudioManagerShellCommand.java
@@ -83,6 +83,8 @@ class AudioManagerShellCommand extends ShellCommand {
return setGroupVolume();
case "adj-group-volume":
return adjGroupVolume();
+ case "set-hardening":
+ return setEnableHardening();
}
return 0;
}
@@ -130,6 +132,8 @@ class AudioManagerShellCommand extends ShellCommand {
pw.println(" Sets the volume for GROUP_ID to VOLUME_INDEX");
pw.println(" adj-group-volume GROUP_ID <RAISE|LOWER|MUTE|UNMUTE>");
pw.println(" Adjusts the group volume for GROUP_ID given the specified direction");
+ pw.println(" set-enable-hardening <1|0>");
+ pw.println(" Enables full audio hardening enforcement, disabling any exemptions");
}
private int setSurroundFormatEnabled() {
@@ -405,6 +409,20 @@ class AudioManagerShellCommand extends ShellCommand {
return 0;
}
+ private int setEnableHardening() {
+ final Context context = mService.mContext;
+ final AudioManager am = context.getSystemService(AudioManager.class);
+ final boolean shouldEnable = !(readIntArg() == 0);
+ getOutPrintWriter().println(
+ "calling AudioManager.setEnableHardening(" + shouldEnable + ")");
+ try {
+ am.setEnableHardening(shouldEnable);
+ } catch (Exception e) {
+ getOutPrintWriter().println("Exception: " + e);
+ }
+ return 0;
+ }
+
private int readIntArg() throws IllegalArgumentException {
final String argText = getNextArg();
diff --git a/services/core/java/com/android/server/audio/AudioPolicyFacade.java b/services/core/java/com/android/server/audio/AudioPolicyFacade.java
index f652b33b3fd3..6c0b81f513be 100644
--- a/services/core/java/com/android/server/audio/AudioPolicyFacade.java
+++ b/services/core/java/com/android/server/audio/AudioPolicyFacade.java
@@ -26,4 +26,5 @@ public interface AudioPolicyFacade {
public boolean isHotwordStreamSupported(boolean lookbackAudio);
public INativePermissionController getPermissionController();
public void registerOnStartTask(Runnable r);
+ public void setEnableHardening(boolean shouldEnable);
}
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index f2830090e7db..02e0d9ffb1d4 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -781,7 +781,8 @@ public class AudioService extends IAudioService.Stub
private int mRingerModeExternal = -1; // reported ringer mode to outside clients (AudioManager)
/** @see System#MODE_RINGER_STREAMS_AFFECTED */
- private int mRingerModeAffectedStreams = 0;
+ @VisibleForTesting
+ protected int mRingerModeAffectedStreams = 0;
private int mZenModeAffectedStreams = 0;
@@ -1191,6 +1192,11 @@ public class AudioService extends IAudioService.Stub
private @AttributeSystemUsage int[] mSupportedSystemUsages =
new int[]{AudioAttributes.USAGE_CALL_ASSISTANT};
+ // Tracks the API/shell override of hardening enforcement used for debugging
+ // When this is set to true, enforcement is on regardless of flag state and any specific
+ // exemptions in place for compat purposes.
+ private final AtomicBoolean mShouldEnableAllHardening = new AtomicBoolean(false);
+
// Defines the format for the connection "address" for ALSA devices
public static String makeAlsaAddressString(int card, int device) {
return "card=" + card + ";device=" + device;
@@ -1334,6 +1340,10 @@ public class AudioService extends IAudioService.Stub
mAudioVolumeGroupHelper = audioVolumeGroupHelper;
mSettings = settings;
mAudioPolicy = audioPolicy;
+ mAudioPolicy.registerOnStartTask(() -> {
+ mAudioPolicy.setEnableHardening(mShouldEnableAllHardening.get());
+ });
+
mPlatformType = AudioSystem.getPlatformType(context);
mBroadcastHandlerThread = new HandlerThread("AudioService Broadcast");
@@ -6315,17 +6325,15 @@ public class AudioService extends IAudioService.Stub
}
}
sRingerAndZenModeMutedStreams &= ~(1 << streamType);
- sMuteLogger.enqueue(new AudioServiceEvents.RingerZenMutedStreamsEvent(
- sRingerAndZenModeMutedStreams, "muteRingerModeStreams"));
vss.mute(false, "muteRingerModeStreams");
} else {
// mute
sRingerAndZenModeMutedStreams |= (1 << streamType);
- sMuteLogger.enqueue(new AudioServiceEvents.RingerZenMutedStreamsEvent(
- sRingerAndZenModeMutedStreams, "muteRingerModeStreams"));
vss.mute(true, "muteRingerModeStreams");
}
}
+ sMuteLogger.enqueue(new AudioServiceEvents.RingerZenMutedStreamsEvent(
+ sRingerAndZenModeMutedStreams, "muteRingerModeStreams"));
}
private boolean isAlarm(int streamType) {
@@ -10045,12 +10053,14 @@ public class AudioService extends IAudioService.Stub
new AudioServiceEvents.StreamMuteEvent(mStreamType, state, src));
// check to see if unmuting should not have happened due to ringer muted streams
if (!state && isStreamMutedByRingerOrZenMode(mStreamType)) {
- Log.e(TAG, "Unmuting stream " + mStreamType
+ Slog.e(TAG, "Attempt to unmute stream " + mStreamType
+ " despite ringer-zen muted stream 0x"
+ Integer.toHexString(AudioService.sRingerAndZenModeMutedStreams),
new Exception()); // this will put a stack trace in the logs
sMuteLogger.enqueue(new AudioServiceEvents.StreamUnmuteErrorEvent(
mStreamType, AudioService.sRingerAndZenModeMutedStreams));
+ // do not change mute state
+ return false;
}
mIsMuted = state;
if (apply) {
@@ -15019,6 +15029,16 @@ public class AudioService extends IAudioService.Stub
return true;
}
+ /**
+ * @see AudioManager#setEnableHardening(boolean)
+ */
+ @android.annotation.EnforcePermission(MODIFY_AUDIO_SETTINGS_PRIVILEGED)
+ public void setEnableHardening(boolean shouldEnable) {
+ super.setEnableHardening_enforcePermission();
+ mShouldEnableAllHardening.set(shouldEnable);
+ mAudioPolicy.setEnableHardening(shouldEnable);
+ }
+
//======================
// Audioserver state dispatch
//======================
diff --git a/services/core/java/com/android/server/audio/DefaultAudioPolicyFacade.java b/services/core/java/com/android/server/audio/DefaultAudioPolicyFacade.java
index 09701e49a8ac..c41f41e0f31b 100644
--- a/services/core/java/com/android/server/audio/DefaultAudioPolicyFacade.java
+++ b/services/core/java/com/android/server/audio/DefaultAudioPolicyFacade.java
@@ -80,4 +80,14 @@ public class DefaultAudioPolicyFacade implements AudioPolicyFacade {
public void registerOnStartTask(Runnable task) {
mServiceHolder.registerOnStartTask(unused -> task.run());
}
+
+ @Override
+ public void setEnableHardening(boolean shouldEnable) {
+ IAudioPolicyService ap = mServiceHolder.waitForService();
+ try {
+ ap.setEnableHardening(shouldEnable);
+ } catch (RemoteException e) {
+ mServiceHolder.attemptClear(ap.asBinder());
+ }
+ }
}
diff --git a/services/core/java/com/android/server/media/SystemMediaRoute2Provider2.java b/services/core/java/com/android/server/media/SystemMediaRoute2Provider2.java
index b13dee530ee2..b529853c63a4 100644
--- a/services/core/java/com/android/server/media/SystemMediaRoute2Provider2.java
+++ b/services/core/java/com/android/server/media/SystemMediaRoute2Provider2.java
@@ -58,6 +58,8 @@ import java.util.stream.Stream;
private static final String UNIQUE_SYSTEM_ID_PREFIX = "SYSTEM";
private static final String UNIQUE_SYSTEM_ID_SEPARATOR = "-";
+ private static final boolean FORCE_GLOBAL_ROUTING_SESSION = true;
+ private static final String PACKAGE_NAME_FOR_GLOBAL_SESSION = "";
private final PackageManager mPackageManager;
@@ -118,6 +120,9 @@ import java.util.stream.Stream;
String routeOriginalId,
int transferReason) {
synchronized (mLock) {
+ if (FORCE_GLOBAL_ROUTING_SESSION) {
+ clientPackageName = PACKAGE_NAME_FOR_GLOBAL_SESSION;
+ }
var targetProviderProxyId = mOriginalRouteIdToProviderId.get(routeOriginalId);
var targetProviderProxyRecord = mProxyRecords.get(targetProviderProxyId);
// Holds the target route, if it's managed by a provider service. Holds null otherwise.
@@ -125,7 +130,7 @@ import java.util.stream.Stream;
targetProviderProxyRecord != null
? targetProviderProxyRecord.getRouteByOriginalId(routeOriginalId)
: null;
- var existingSessionRecord = mPackageNameToSessionRecord.get(clientPackageName);
+ var existingSessionRecord = getSessionRecordByPackageName(clientPackageName);
if (existingSessionRecord != null) {
var existingSession = existingSessionRecord.mSourceSessionInfo;
if (targetProviderProxyId != null
@@ -206,7 +211,7 @@ import java.util.stream.Stream;
if (systemSession == null) {
return null;
}
- var overridingSession = mPackageNameToSessionRecord.get(packageName);
+ var overridingSession = getSessionRecordByPackageName(packageName);
if (overridingSession != null) {
var builder =
new RoutingSessionInfo.Builder(overridingSession.mTranslatedSessionInfo)
@@ -251,7 +256,7 @@ import java.util.stream.Stream;
return;
}
synchronized (mLock) {
- var sessionRecord = mSessionOriginalIdToSessionRecord.get(sessionOriginalId);
+ var sessionRecord = getSessionRecordByOriginalId(sessionOriginalId);
var proxyRecord = sessionRecord != null ? sessionRecord.getProxyRecord() : null;
if (proxyRecord != null) {
proxyRecord.mProxy.setSessionVolume(
@@ -262,6 +267,23 @@ import java.util.stream.Stream;
notifyRequestFailed(requestId, MediaRoute2ProviderService.REASON_ROUTE_NOT_AVAILABLE);
}
+ @GuardedBy("mLock")
+ private SystemMediaSessionRecord getSessionRecordByOriginalId(String sessionOriginalId) {
+ if (FORCE_GLOBAL_ROUTING_SESSION) {
+ return getSessionRecordByPackageName(PACKAGE_NAME_FOR_GLOBAL_SESSION);
+ } else {
+ return mSessionOriginalIdToSessionRecord.get(sessionOriginalId);
+ }
+ }
+
+ @GuardedBy("mLock")
+ private SystemMediaSessionRecord getSessionRecordByPackageName(String clientPackageName) {
+ if (FORCE_GLOBAL_ROUTING_SESSION) {
+ clientPackageName = PACKAGE_NAME_FOR_GLOBAL_SESSION;
+ }
+ return mPackageNameToSessionRecord.get(clientPackageName);
+ }
+
/**
* Returns the uid that corresponds to the given name and user handle, or {@link
* Process#INVALID_UID} if a uid couldn't be found.
@@ -319,16 +341,34 @@ import java.util.stream.Stream;
*/
private void updateSessionInfo() {
synchronized (mLock) {
- var systemSessionInfo = mSystemSessionInfo;
- if (systemSessionInfo == null) {
+ var globalSessionInfoRecord =
+ getSessionRecordByPackageName(PACKAGE_NAME_FOR_GLOBAL_SESSION);
+ var globalSessionInfo =
+ globalSessionInfoRecord != null
+ ? globalSessionInfoRecord.mTranslatedSessionInfo
+ : null;
+ if (globalSessionInfo == null) {
+ globalSessionInfo = mSystemSessionInfo;
+ }
+ if (globalSessionInfo == null) {
// The system session info hasn't been initialized yet. Do nothing.
return;
}
- var builder = new RoutingSessionInfo.Builder(systemSessionInfo);
- mProxyRecords.values().stream()
- .flatMap(ProviderProxyRecord::getRoutesStream)
- .map(MediaRoute2Info::getOriginalId)
- .forEach(builder::addTransferableRoute);
+ var builder = new RoutingSessionInfo.Builder(globalSessionInfo);
+ if (globalSessionInfo == mSystemSessionInfo) {
+ // The session is the system one. So we make all the service-provided routes
+ // available for transfer. The system transferable routes are already there.
+ mProxyRecords.values().stream()
+ .flatMap(ProviderProxyRecord::getRoutesStream)
+ .map(MediaRoute2Info::getOriginalId)
+ .forEach(builder::addTransferableRoute);
+ } else {
+ // The session is service-provided. So we add the system-provided routes as
+ // transferable.
+ mLastSystemProviderInfo.getRoutes().stream()
+ .map(MediaRoute2Info::getOriginalId)
+ .forEach(builder::addTransferableRoute);
+ }
mSessionInfos.clear();
mSessionInfos.add(builder.build());
for (var sessionRecords : mPackageNameToSessionRecord.values()) {
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 21ac05c24259..638b2dd4a7fe 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -111,7 +111,9 @@ import static android.os.UserHandle.USER_SYSTEM;
import static android.service.notification.Adjustment.KEY_SUMMARIZATION;
import static android.service.notification.Adjustment.KEY_TYPE;
import static android.service.notification.Adjustment.TYPE_CONTENT_RECOMMENDATION;
+import static android.service.notification.Adjustment.TYPE_NEWS;
import static android.service.notification.Adjustment.TYPE_PROMOTION;
+import static android.service.notification.Adjustment.TYPE_SOCIAL_MEDIA;
import static android.service.notification.Flags.FLAG_NOTIFICATION_CONVERSATION_CHANNEL_MANAGEMENT;
import static android.service.notification.Flags.callstyleCallbackApi;
import static android.service.notification.Flags.notificationClassification;
@@ -492,7 +494,10 @@ public class NotificationManagerService extends SystemService {
};
static final Integer[] DEFAULT_ALLOWED_ADJUSTMENT_KEY_TYPES = new Integer[] {
- TYPE_PROMOTION
+ TYPE_PROMOTION,
+ TYPE_NEWS,
+ TYPE_CONTENT_RECOMMENDATION,
+ TYPE_SOCIAL_MEDIA
};
static final String[] NON_BLOCKABLE_DEFAULT_ROLES = new String[] {
@@ -4522,7 +4527,7 @@ public class NotificationManagerService extends SystemService {
if (key == null) {
return;
}
- mAssistants.setAdjustmentTypeSupportedState(info, key, supported);
+ mAssistants.setAdjustmentTypeSupportedState(info.userid, key, supported);
}
} finally {
Binder.restoreCallingIdentity(identity);
@@ -4565,6 +4570,12 @@ public class NotificationManagerService extends SystemService {
}
@Override
+ public String[] getAdjustmentDeniedPackages(String key) {
+ checkCallerIsSystemOrSystemUiOrShell();
+ return mAssistants.getAdjustmentDeniedPackages(key);
+ }
+
+ @Override
public boolean isAdjustmentSupportedForPackage(String key, String pkg) {
checkCallerIsSystemOrSystemUiOrShell();
return mAssistants.isAdjustmentAllowedForPackage(key, pkg);
@@ -7017,7 +7028,7 @@ public class NotificationManagerService extends SystemService {
final long identity = Binder.clearCallingIdentity();
try {
synchronized (mNotificationLock) {
- mAssistants.checkServiceTokenLocked(token);
+ ManagedServiceInfo info = mAssistants.checkServiceTokenLocked(token);
int N = mEnqueuedNotifications.size();
for (int i = 0; i < N; i++) {
final NotificationRecord r = mEnqueuedNotifications.get(i);
@@ -7363,6 +7374,10 @@ public class NotificationManagerService extends SystemService {
mAssistants.setPackageOrComponentEnabled(assistant.flattenToString(),
userId, true, granted, userSet);
+ if (android.service.notification.Flags.notificationClassification()) {
+ mAssistants.setNasUnsupportedDefaults(userId);
+ }
+
getContext().sendBroadcastAsUser(
new Intent(ACTION_NOTIFICATION_POLICY_ACCESS_GRANTED_CHANGED)
.setPackage(assistant.getPackageName())
@@ -7394,7 +7409,8 @@ public class NotificationManagerService extends SystemService {
toRemove.add(potentialKey);
}
if (notificationClassification() && potentialKey.equals(KEY_TYPE)) {
- mAssistants.setNasUnsupportedDefaults(r.getSbn().getNormalizedUserId());
+ mAssistants.setAdjustmentTypeSupportedState(
+ r.getSbn().getNormalizedUserId(), potentialKey, true);
if (!mAssistants.isAdjustmentKeyTypeAllowed(adjustments.getInt(KEY_TYPE))) {
toRemove.add(potentialKey);
} else if (notificationClassificationUi()
@@ -7405,6 +7421,8 @@ public class NotificationManagerService extends SystemService {
}
if ((nmSummarization() || nmSummarizationUi())
&& potentialKey.equals(KEY_SUMMARIZATION)) {
+ mAssistants.setAdjustmentTypeSupportedState(
+ r.getSbn().getNormalizedUserId(), potentialKey, true);
if (!mAssistants.isAdjustmentAllowedForPackage(KEY_SUMMARIZATION,
r.getSbn().getPackageName())) {
toRemove.add(potentialKey);
@@ -12062,8 +12080,8 @@ public class NotificationManagerService extends SystemService {
private static final String TAG_DENIED_KEY = "adjustment";
private static final String ATT_DENIED_KEY = "key";
private static final String ATT_DENIED_KEY_APPS = "denied_apps";
- private static final String TAG_ENABLED_TYPES = "enabled_key_types";
- private static final String ATT_NAS_UNSUPPORTED = "nas_unsupported_adjustments";
+ private static final String TAG_ENABLED_TYPES = "enabled_classification_types";
+ private static final String ATT_NAS_UNSUPPORTED = "unsupported_adjustments";
private final Object mLock = new Object();
@@ -12283,6 +12301,16 @@ public class NotificationManagerService extends SystemService {
}
}
+ protected @NonNull String[] getAdjustmentDeniedPackages(@Adjustment.Keys String key) {
+ synchronized (mLock) {
+ if (notificationClassificationUi() || nmSummarization() | nmSummarizationUi()) {
+ return mAdjustmentKeyDeniedPackages.getOrDefault(
+ key, new ArraySet<>()).toArray(new String[0]);
+ }
+ }
+ return new String[]{};
+ }
+
protected @NonNull boolean isAdjustmentAllowedForPackage(@Adjustment.Keys String key,
String pkg) {
synchronized (mLock) {
@@ -12665,10 +12693,6 @@ public class NotificationManagerService extends SystemService {
setNotificationAssistantAccessGrantedForUserInternal(
currentComponent, userId, false, userSet);
}
- } else {
- if (android.service.notification.Flags.notificationClassification()) {
- setNasUnsupportedDefaults(userId);
- }
}
super.setPackageOrComponentEnabled(pkgOrComponent, userId, isPrimary, enabled, userSet);
}
@@ -12701,36 +12725,37 @@ public class NotificationManagerService extends SystemService {
}
}
- @FlaggedApi(android.service.notification.Flags.FLAG_NOTIFICATION_CLASSIFICATION)
@GuardedBy("mNotificationLock")
- public void setAdjustmentTypeSupportedState(ManagedServiceInfo info,
+ public void setAdjustmentTypeSupportedState(@UserIdInt int userId,
@Adjustment.Keys String key, boolean supported) {
- if (!android.service.notification.Flags.notificationClassification()) {
+ if (!(android.service.notification.Flags.notificationClassification()
+ || android.app.Flags.nmSummarizationUi()
+ || android.app.Flags.nmSummarization())) {
return;
}
- setNasUnsupportedDefaults(info.userid);
- HashSet<String> disabledAdjustments = mNasUnsupported.get(info.userid);
+ HashSet<String> disabledAdjustments =
+ mNasUnsupported.getOrDefault(userId, new HashSet<>());
if (supported) {
disabledAdjustments.remove(key);
} else {
disabledAdjustments.add(key);
}
- mNasUnsupported.put(info.userid, disabledAdjustments);
+ mNasUnsupported.put(userId, disabledAdjustments);
handleSavePolicyFile();
}
- @FlaggedApi(android.service.notification.Flags.FLAG_NOTIFICATION_CLASSIFICATION)
@GuardedBy("mNotificationLock")
public @NonNull Set<String> getUnsupportedAdjustments(@UserIdInt int userId) {
- if (!android.service.notification.Flags.notificationClassification()) {
+ if (!(android.service.notification.Flags.notificationClassification()
+ || android.app.Flags.nmSummarizationUi()
+ || android.app.Flags.nmSummarization())) {
return new HashSet<>();
}
- setNasUnsupportedDefaults(userId);
- return mNasUnsupported.get(userId);
+ return mNasUnsupported.getOrDefault(userId, new HashSet<>());
}
private void setNasUnsupportedDefaults(@UserIdInt int userId) {
- if (mNasUnsupported != null && !mNasUnsupported.containsKey(userId)) {
+ if (mNasUnsupported != null) {
mNasUnsupported.put(userId, new HashSet(List.of(mDefaultUnsupportedAdjustments)));
handleSavePolicyFile();
}
@@ -12743,10 +12768,8 @@ public class NotificationManagerService extends SystemService {
return;
}
synchronized (mLock) {
- if (mNasUnsupported.containsKey(approvedUserId)) {
- out.attribute(null, ATT_NAS_UNSUPPORTED,
- TextUtils.join(",", mNasUnsupported.get(approvedUserId)));
- }
+ out.attribute(null, ATT_NAS_UNSUPPORTED, TextUtils.join(",",
+ mNasUnsupported.getOrDefault(approvedUserId, new HashSet<>())));
}
}
@@ -12759,8 +12782,15 @@ public class NotificationManagerService extends SystemService {
if (ManagedServices.TAG_MANAGED_SERVICES.equals(tag)) {
final String types = XmlUtils.readStringAttribute(parser, ATT_NAS_UNSUPPORTED);
synchronized (mLock) {
- if (!TextUtils.isEmpty(types)) {
- mNasUnsupported.put(approvedUserId, new HashSet(List.of(types.split(","))));
+ if (types == null) {
+ setNasUnsupportedDefaults(approvedUserId);
+ } else {
+ if (!TextUtils.isEmpty(types)) {
+ mNasUnsupported.put(approvedUserId,
+ new HashSet(List.of(types.split(","))));
+ } else {
+ mNasUnsupported.put(approvedUserId, new HashSet());
+ }
}
}
}
diff --git a/services/core/java/com/android/server/pm/UserManagerInternal.java b/services/core/java/com/android/server/pm/UserManagerInternal.java
index 4d97a83fc6b4..f88681dbcaeb 100644
--- a/services/core/java/com/android/server/pm/UserManagerInternal.java
+++ b/services/core/java/com/android/server/pm/UserManagerInternal.java
@@ -353,8 +353,10 @@ public abstract class UserManagerInternal {
boolean excludePreCreated);
/**
- * Returns an array of ids for profiles associated with the specified user including the user
- * itself.
+ * Returns a list of the users that are associated with the specified user, including the user
+ * itself. This includes the user, its profiles, its parent, and its parent's other profiles,
+ * as applicable.
+ *
* <p>Note that this includes all profile types (not including Restricted profiles).
*
* @param userId id of the user to return profiles for
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 0a90c3644c2f..7de7dde8c260 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -1636,7 +1636,7 @@ public class UserManagerService extends IUserManager.Stub {
final int userSize = mUsers.size();
for (int i = 0; i < userSize; i++) {
UserInfo profile = mUsers.valueAt(i).info;
- if (!isProfileOf(user, profile)) {
+ if (!isSameProfileGroup(user, profile)) {
continue;
}
if (enabledOnly && !profile.isEnabled()) {
@@ -1704,22 +1704,18 @@ public class UserManagerService extends IUserManager.Stub {
return isSameProfileGroupNoChecks(userId, otherUserId);
}
- /**
- * Returns whether users are in the same non-empty profile group.
- * Currently, false if empty profile group, even if they are the same user, for whatever reason.
- */
+ /** Returns whether users are in the same profile group. */
private boolean isSameProfileGroupNoChecks(@UserIdInt int userId, int otherUserId) {
synchronized (mUsersLock) {
UserInfo userInfo = getUserInfoLU(userId);
- if (userInfo == null || userInfo.profileGroupId == UserInfo.NO_PROFILE_GROUP_ID) {
+ if (userInfo == null) {
return false;
}
UserInfo otherUserInfo = getUserInfoLU(otherUserId);
- if (otherUserInfo == null
- || otherUserInfo.profileGroupId == UserInfo.NO_PROFILE_GROUP_ID) {
+ if (otherUserInfo == null) {
return false;
}
- return userInfo.profileGroupId == otherUserInfo.profileGroupId;
+ return isSameProfileGroup(userInfo, otherUserInfo);
}
}
@@ -1778,10 +1774,10 @@ public class UserManagerService extends IUserManager.Stub {
}
}
- private static boolean isProfileOf(UserInfo user, UserInfo profile) {
- return user.id == profile.id ||
- (user.profileGroupId != UserInfo.NO_PROFILE_GROUP_ID
- && user.profileGroupId == profile.profileGroupId);
+ private static boolean isSameProfileGroup(@NonNull UserInfo user1, @NonNull UserInfo user2) {
+ return user1.id == user2.id ||
+ (user1.profileGroupId != UserInfo.NO_PROFILE_GROUP_ID
+ && user1.profileGroupId == user2.profileGroupId);
}
private String getAvailabilityIntentAction(boolean enableQuietMode, boolean useManagedActions) {
@@ -7549,6 +7545,10 @@ public class UserManagerService extends IUserManager.Stub {
pw.println(" (and being updated after boot)");
}
}
+ if (isHeadlessSystemUserMode) {
+ pw.println(" Can switch to headless system user: " + Resources.getSystem()
+ .getBoolean(com.android.internal.R.bool.config_canSwitchToHeadlessSystemUser));
+ }
pw.println(" User version: " + mUserVersion);
pw.println(" Owner name: " + getOwnerName());
if (DBG_ALLOCATION) {
@@ -8411,21 +8411,27 @@ public class UserManagerService extends IUserManager.Stub {
}
/**
- * Checks if the given user has a profile associated with it.
- * @param userId The parent user
- * @return
+ * Formerly: Checks if the given user has a profile associated with it.
+ * Now: Just throws. Do not use it.
+ * @param userId The parent user (passing in a profile user is not supported)
+ * @deprecated
*/
boolean hasProfile(@UserIdInt int userId) {
- synchronized (mUsersLock) {
- UserInfo userInfo = getUserInfoLU(userId);
- final int userSize = mUsers.size();
- for (int i = 0; i < userSize; i++) {
- UserInfo profile = mUsers.valueAt(i).info;
- if (userId != profile.id && isProfileOf(userInfo, profile)) {
- return true;
+ if (!android.content.pm.Flags.removeCrossUserPermissionHack()) {
+ synchronized (mUsersLock) {
+ UserInfo userInfo = getUserInfoLU(userId);
+ final int userSize = mUsers.size();
+ for (int i = 0; i < userSize; i++) {
+ UserInfo profile = mUsers.valueAt(i).info;
+ if (userId != profile.id && isSameProfileGroup(userInfo, profile)) {
+ return true;
+ }
}
+ return false;
}
- return false;
+ } else {
+ // TODO(b/332664521): Remove this method entirely. It is no longer used.
+ throw new UnsupportedOperationException();
}
}
diff --git a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
index 2bc6d53147fb..a1082481abb8 100644
--- a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
+++ b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
@@ -309,7 +309,8 @@ public class UserRestrictionsUtils {
* in settings. So it is handled separately.
*/
private static final Set<String> DEFAULT_ENABLED_FOR_MANAGED_PROFILES = Sets.newArraySet(
- UserManager.DISALLOW_BLUETOOTH_SHARING
+ UserManager.DISALLOW_BLUETOOTH_SHARING,
+ UserManager.DISALLOW_DEBUGGING_FEATURES
);
/**
diff --git a/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java b/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java
index 1b7c7ad94dc9..c0441e4e4d46 100644
--- a/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java
+++ b/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java
@@ -155,11 +155,14 @@ public class AndroidPackageUtils {
public static NativeLibraryHelper.Handle createNativeLibraryHandle(AndroidPackage pkg)
throws IOException {
+ boolean pageSizeCompatDisabled = pkg.getPageSizeAppCompatFlags()
+ == ApplicationInfo.PAGE_SIZE_APP_COMPAT_FLAG_MANIFEST_OVERRIDE_DISABLED;
return NativeLibraryHelper.Handle.create(
AndroidPackageUtils.getAllCodePaths(pkg),
pkg.isMultiArch(),
pkg.isExtractNativeLibrariesRequested(),
- pkg.isDebuggable()
+ pkg.isDebuggable(),
+ pageSizeCompatDisabled
);
}
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index b905041b59e5..5c5a9c1b6c05 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -271,8 +271,9 @@ public class PermissionManagerService extends IPermissionManager.Stub {
@NonNull String permissionName, int deviceId) {
Objects.requireNonNull(permissionName, "permission can't be null.");
Objects.requireNonNull(packageName, "package name can't be null.");
+
return mPermissionManagerServiceImpl.getPermissionRequestState(packageName, permissionName,
- getPersistentDeviceId(deviceId));
+ deviceId, getPersistentDeviceId(deviceId));
}
private String getPersistentDeviceId(int deviceId) {
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
index ca70bddc5ac1..e51ec04e60fe 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
@@ -1014,7 +1014,8 @@ public class PermissionManagerServiceImpl implements PermissionManagerServiceInt
}
@Override
- public int getPermissionRequestState(String packageName, String permName, String deviceId) {
+ public int getPermissionRequestState(String packageName, String permName, int deviceId,
+ String persistentDeviceId) {
throw new IllegalStateException("getPermissionRequestState is not supported.");
}
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInterface.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInterface.java
index b607832767a1..3d295f773805 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInterface.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInterface.java
@@ -415,7 +415,7 @@ public interface PermissionManagerServiceInterface extends PermissionManagerInte
* for permission request permission flow.
*/
int getPermissionRequestState(@NonNull String packageName, @NonNull String permName,
- @NonNull String deviceId);
+ int deviceId, @NonNull String persistentDeviceId);
/**
* Gets the permission states for requested package, persistent device and user.
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceLoggingDecorator.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceLoggingDecorator.java
index ba5e97e7b113..f5764006e766 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceLoggingDecorator.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceLoggingDecorator.java
@@ -247,10 +247,12 @@ public class PermissionManagerServiceLoggingDecorator implements PermissionManag
}
@Override
- public int getPermissionRequestState(String packageName, String permName, String deviceId) {
+ public int getPermissionRequestState(String packageName, String permName, int deviceId,
+ String persistentDeviceId) {
Log.i(LOG_TAG, "checkUidPermissionState(permName = " + permName + ", deviceId = "
- + deviceId + ", packageName = " + packageName + ")");
- return mService.getPermissionRequestState(packageName, permName, deviceId);
+ + persistentDeviceId + ", packageName = " + packageName + ")");
+ return mService.getPermissionRequestState(
+ packageName, permName, deviceId, persistentDeviceId);
}
@Override
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceTestingShim.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceTestingShim.java
index 008c14db8b65..21a357025cfb 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceTestingShim.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceTestingShim.java
@@ -319,8 +319,10 @@ public class PermissionManagerServiceTestingShim implements PermissionManagerSer
}
@Override
- public int getPermissionRequestState(String packageName, String permName, String deviceId) {
- return mNewImplementation.getPermissionRequestState(packageName, permName, deviceId);
+ public int getPermissionRequestState(String packageName, String permName, int deviceId,
+ String persistentDeviceId) {
+ return mNewImplementation.getPermissionRequestState(
+ packageName, permName, deviceId, persistentDeviceId);
}
@Override
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceTracingDecorator.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceTracingDecorator.java
index 2a47f51da951..e51afb0f66c5 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceTracingDecorator.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceTracingDecorator.java
@@ -347,11 +347,13 @@ public class PermissionManagerServiceTracingDecorator implements PermissionManag
@Override
- public int getPermissionRequestState(String packageName, String permName, String deviceId) {
+ public int getPermissionRequestState(String packageName, String permName, int deviceId,
+ String persistentDeviceId) {
Trace.traceBegin(TRACE_TAG,
"TaggedTracingPermissionManagerServiceImpl#checkUidPermissionState");
try {
- return mService.getPermissionRequestState(packageName, permName, deviceId);
+ return mService.getPermissionRequestState(
+ packageName, permName, deviceId, persistentDeviceId);
} finally {
Trace.traceEnd(TRACE_TAG);
}
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index 090707db50a5..8fae875eb29b 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -5979,10 +5979,19 @@ public final class PowerManagerService extends SystemService
if (uids != null) {
ws = new WorkSource();
- // XXX should WorkSource have a way to set uids as an int[] instead of adding them
- // one at a time?
- for (int uid : uids) {
- ws.add(uid);
+ if (mFeatureFlags.isWakelockAttributionViaWorkchainEnabled()) {
+ int callingUid = Binder.getCallingUid();
+ for (int uid : uids) {
+ WorkChain workChain = ws.createWorkChain();
+ workChain.addNode(uid, null);
+ workChain.addNode(callingUid, null);
+ }
+ } else {
+ // XXX should WorkSource have a way to set uids as an int[] instead of
+ // adding them one at a time?
+ for (int uid : uids) {
+ ws.add(uid);
+ }
}
}
updateWakeLockWorkSource(lock, ws, null);
diff --git a/services/core/java/com/android/server/power/feature/PowerManagerFlags.java b/services/core/java/com/android/server/power/feature/PowerManagerFlags.java
index 42b44013bea2..ebc50fd85f24 100644
--- a/services/core/java/com/android/server/power/feature/PowerManagerFlags.java
+++ b/services/core/java/com/android/server/power/feature/PowerManagerFlags.java
@@ -63,6 +63,10 @@ public class PowerManagerFlags {
private final FlagState mMoveWscLoggingToNotifier =
new FlagState(Flags.FLAG_MOVE_WSC_LOGGING_TO_NOTIFIER, Flags::moveWscLoggingToNotifier);
+ private final FlagState mWakelockAttributionViaWorkchain =
+ new FlagState(Flags.FLAG_WAKELOCK_ATTRIBUTION_VIA_WORKCHAIN,
+ Flags::wakelockAttributionViaWorkchain);
+
/** Returns whether early-screen-timeout-detector is enabled on not. */
public boolean isEarlyScreenTimeoutDetectorEnabled() {
return mEarlyScreenTimeoutDetectorFlagState.isEnabled();
@@ -110,6 +114,13 @@ public class PowerManagerFlags {
}
/**
+ * @return Whether the wakelock attribution via workchain is enabled
+ */
+ public boolean isWakelockAttributionViaWorkchainEnabled() {
+ return mWakelockAttributionViaWorkchain.isEnabled();
+ }
+
+ /**
* dumps all flagstates
* @param pw printWriter
*/
@@ -120,6 +131,7 @@ public class PowerManagerFlags {
pw.println(" " + mPerDisplayWakeByTouch);
pw.println(" " + mFrameworkWakelockInfo);
pw.println(" " + mMoveWscLoggingToNotifier);
+ pw.println(" " + mWakelockAttributionViaWorkchain);
}
private static class FlagState {
diff --git a/services/core/java/com/android/server/power/feature/power_flags.aconfig b/services/core/java/com/android/server/power/feature/power_flags.aconfig
index 613daf820e34..fefe195dc337 100644
--- a/services/core/java/com/android/server/power/feature/power_flags.aconfig
+++ b/services/core/java/com/android/server/power/feature/power_flags.aconfig
@@ -23,6 +23,17 @@ flag {
}
flag {
+ name: "wakelock_attribution_via_workchain"
+ namespace: "power"
+ description: "Enables the attribution of wakelocks via WorkChain for updateWakelockUids"
+ bug: "331304805"
+ is_fixed_read_only: true
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "improve_wakelock_latency"
namespace: "power"
description: "Feature flag for tracking the optimizations to improve the latency of acquiring and releasing a wakelock."
diff --git a/services/core/java/com/android/server/power/stats/WakelockStatsFrameworkEvents.java b/services/core/java/com/android/server/power/stats/WakelockStatsFrameworkEvents.java
index f387feca05f2..a2971f302327 100644
--- a/services/core/java/com/android/server/power/stats/WakelockStatsFrameworkEvents.java
+++ b/services/core/java/com/android/server/power/stats/WakelockStatsFrameworkEvents.java
@@ -272,14 +272,10 @@ public class WakelockStatsFrameworkEvents {
WakeLockStats extraTime =
openOverflowStats.computeIfAbsent(key, k -> new WakeLockStats());
- stats.uptimeMillis += openWakeLockUptime + extraTime.uptimeMillis;
-
- logger.logResult(
- key.getUid(),
- key.getTag(),
- key.getPowerManagerWakeLockLevel(),
- stats.uptimeMillis,
- stats.completedCount);
+ long totalUpdate = openWakeLockUptime + stats.uptimeMillis + extraTime.uptimeMillis;
+ long totalCount = stats.completedCount + extraTime.completedCount;
+ logger.logResult(key.getUid(), key.getTag(), key.getPowerManagerWakeLockLevel(),
+ totalUpdate, totalCount);
}
}
}
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index e8498bca1809..0024a4166e71 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -5605,7 +5605,6 @@ final class ActivityRecord extends WindowToken {
}
mAtmService.mBackNavigationController.onAppVisibilityChanged(this, visible);
- onChildVisibilityRequested(visible);
final DisplayContent displayContent = getDisplayContent();
displayContent.mOpeningApps.remove(this);
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index 0d88a9b1f8ee..6a5adca91e39 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -162,6 +162,7 @@ import com.android.server.companion.virtual.VirtualDeviceManagerInternal;
import com.android.server.pm.SaferIntentUtils;
import com.android.server.utils.Slogf;
import com.android.server.wm.ActivityMetricsLogger.LaunchingState;
+import com.android.window.flags.Flags;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -278,7 +279,7 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks {
/** Helper for {@link Task#fillTaskInfo}. */
final TaskInfoHelper mTaskInfoHelper = new TaskInfoHelper();
- final OpaqueActivityHelper mOpaqueActivityHelper = new OpaqueActivityHelper();
+ final OpaqueContainerHelper mOpaqueContainerHelper = new OpaqueContainerHelper();
private final ActivityTaskSupervisorHandler mHandler;
final Looper mLooper;
@@ -2913,41 +2914,90 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks {
}
}
- /** The helper to get the top opaque activity of a container. */
- static class OpaqueActivityHelper implements Predicate<ActivityRecord> {
+ /** The helper to calculate whether a container is opaque. */
+ static class OpaqueContainerHelper implements Predicate<ActivityRecord> {
private ActivityRecord mStarting;
- private boolean mIncludeInvisibleAndFinishing;
+ private boolean mIgnoringInvisibleActivity;
private boolean mIgnoringKeyguard;
- ActivityRecord getOpaqueActivity(@NonNull WindowContainer<?> container) {
- mIncludeInvisibleAndFinishing = true;
- mIgnoringKeyguard = true;
- return container.getActivity(this,
- true /* traverseTopToBottom */, null /* boundary */);
+ /** Whether the container is opaque. */
+ boolean isOpaque(@NonNull WindowContainer<?> container) {
+ return isOpaque(container, null /* starting */, true /* ignoringKeyguard */,
+ false /* ignoringInvisibleActivity */);
}
- ActivityRecord getVisibleOpaqueActivity(
+ /**
+ * Whether the container is opaque, but only including visible activities in its
+ * calculation.
+ */
+ boolean isOpaque(
@NonNull WindowContainer<?> container, @Nullable ActivityRecord starting,
- boolean ignoringKeyguard) {
+ boolean ignoringKeyguard, boolean ignoringInvisibleActivity) {
mStarting = starting;
- mIncludeInvisibleAndFinishing = false;
+ mIgnoringInvisibleActivity = ignoringInvisibleActivity;
mIgnoringKeyguard = ignoringKeyguard;
- final ActivityRecord opaque = container.getActivity(this,
- true /* traverseTopToBottom */, null /* boundary */);
+
+ final boolean isOpaque;
+ if (!Flags.enableMultipleDesktopsBackend()) {
+ isOpaque = container.getActivity(this,
+ true /* traverseTopToBottom */, null /* boundary */) != null;
+ } else {
+ isOpaque = isOpaqueInner(container);
+ }
mStarting = null;
- return opaque;
+ return isOpaque;
+ }
+
+ private boolean isOpaqueInner(@NonNull WindowContainer<?> container) {
+ // If it's a leaf task fragment, then opacity is calculated based on its activities.
+ if (container.asTaskFragment() != null
+ && ((TaskFragment) container).isLeafTaskFragment()) {
+ return container.getActivity(this,
+ true /* traverseTopToBottom */, null /* boundary */) != null;
+ }
+ // When not a leaf, it's considered opaque if any of its opaque children fill this
+ // container, unless the children are adjacent fragments, in which case as long as they
+ // are all opaque then |container| is also considered opaque, even if the adjacent
+ // task fragment aren't filling.
+ for (int i = 0; i < container.getChildCount(); i++) {
+ final WindowContainer<?> child = container.getChildAt(i);
+ if (child.fillsParent() && isOpaque(child)) {
+ return true;
+ }
+
+ if (child.asTaskFragment() != null
+ && child.asTaskFragment().hasAdjacentTaskFragment()) {
+ final boolean isAnyTranslucent;
+ if (Flags.allowMultipleAdjacentTaskFragments()) {
+ final TaskFragment.AdjacentSet set =
+ child.asTaskFragment().getAdjacentTaskFragments();
+ isAnyTranslucent = set.forAllTaskFragments(
+ tf -> !isOpaque(tf), null);
+ } else {
+ final TaskFragment adjacent = child.asTaskFragment()
+ .getAdjacentTaskFragment();
+ isAnyTranslucent = !isOpaque(child) || !isOpaque(adjacent);
+ }
+ if (!isAnyTranslucent) {
+ // This task fragment and all its adjacent task fragments are opaque,
+ // consider it opaque even if it doesn't fill its parent.
+ return true;
+ }
+ }
+ }
+ return false;
}
@Override
public boolean test(ActivityRecord r) {
- if (!mIncludeInvisibleAndFinishing && r != mStarting
+ if (mIgnoringInvisibleActivity && r != mStarting
&& ((mIgnoringKeyguard && !r.visibleIgnoringKeyguard)
|| (!mIgnoringKeyguard && !r.isVisible()))) {
// Ignore invisible activities that are not the currently starting activity
// (about to be visible).
return false;
}
- return r.occludesParent(mIncludeInvisibleAndFinishing /* includingFinishing */);
+ return r.occludesParent(!mIgnoringInvisibleActivity /* includingFinishing */);
}
}
diff --git a/services/core/java/com/android/server/wm/AppTransitionController.java b/services/core/java/com/android/server/wm/AppTransitionController.java
index 0a2f6852f6e6..d5fe056a2ba4 100644
--- a/services/core/java/com/android/server/wm/AppTransitionController.java
+++ b/services/core/java/com/android/server/wm/AppTransitionController.java
@@ -80,7 +80,6 @@ import android.os.Trace;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Pair;
-import android.view.Display;
import android.view.RemoteAnimationAdapter;
import android.view.RemoteAnimationDefinition;
import android.view.WindowManager;
@@ -252,7 +251,6 @@ public class AppTransitionController {
// Check if there is any override
if (!overrideWithTaskFragmentRemoteAnimation(transit, activityTypes)) {
// Unfreeze the windows that were previously frozen for TaskFragment animation.
- unfreezeEmbeddedChangingWindows();
overrideWithRemoteAnimationIfSet(animLpActivity, transit, activityTypes);
}
@@ -545,16 +543,6 @@ public class AppTransitionController {
: null;
}
- private void unfreezeEmbeddedChangingWindows() {
- final ArraySet<WindowContainer> changingContainers = mDisplayContent.mChangingContainers;
- for (int i = changingContainers.size() - 1; i >= 0; i--) {
- final WindowContainer wc = changingContainers.valueAt(i);
- if (wc.isEmbedded()) {
- wc.mSurfaceFreezer.unfreeze(wc.getSyncTransaction());
- }
- }
- }
-
private boolean transitionMayContainNonAppWindows(@TransitionOldType int transit) {
// We don't want to have the client to animate any non-app windows.
// Having {@code transit} of those types doesn't mean it will contain non-app windows, but
diff --git a/services/core/java/com/android/server/wm/DesktopModeHelper.java b/services/core/java/com/android/server/wm/DesktopModeHelper.java
index e3906f9119c2..0eea30a29580 100644
--- a/services/core/java/com/android/server/wm/DesktopModeHelper.java
+++ b/services/core/java/com/android/server/wm/DesktopModeHelper.java
@@ -52,8 +52,7 @@ public final class DesktopModeHelper {
* Return {@code true} if the current device supports desktop mode.
*/
// TODO(b/337819319): use a companion object instead.
- @VisibleForTesting
- static boolean isDesktopModeSupported(@NonNull Context context) {
+ private static boolean isDesktopModeSupported(@NonNull Context context) {
return context.getResources().getBoolean(R.bool.config_isDesktopModeSupported);
}
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index a0d2d260b39e..ecf2787a2080 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -5165,7 +5165,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
/**
* Creates a LayerCaptureArgs object to represent the entire DisplayContent
*/
- LayerCaptureArgs getLayerCaptureArgs(Set<Integer> windowTypesToExclude) {
+ LayerCaptureArgs getLayerCaptureArgs(@Nullable ToBooleanFunction<WindowState> predicate) {
if (!mWmService.mPolicy.isScreenOn()) {
if (DEBUG_SCREENSHOT) {
Slog.i(TAG_WM, "Attempted to take screenshot while display was off.");
@@ -5178,17 +5178,16 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
LayerCaptureArgs.Builder builder = new LayerCaptureArgs.Builder(getSurfaceControl())
.setSourceCrop(mTmpRect);
- if (!windowTypesToExclude.isEmpty()) {
- ArrayList<SurfaceControl> surfaceControls = new ArrayList<>();
+ if (predicate != null) {
+ ArrayList<SurfaceControl> excludeLayers = new ArrayList<>();
forAllWindows(
window -> {
- if (windowTypesToExclude.contains(window.getWindowType())) {
- surfaceControls.add(window.mSurfaceControl);
+ if (!predicate.apply(window)) {
+ excludeLayers.add(window.mSurfaceControl);
}
- }, true
- );
- if (!surfaceControls.isEmpty()) {
- builder.setExcludeLayers(surfaceControls.toArray(new SurfaceControl[0]));
+ }, true);
+ if (!excludeLayers.isEmpty()) {
+ builder.setExcludeLayers(excludeLayers.toArray(new SurfaceControl[0]));
}
}
return builder.build();
diff --git a/services/core/java/com/android/server/wm/SurfaceAnimator.java b/services/core/java/com/android/server/wm/SurfaceAnimator.java
index d7b6d96c781d..3dfff39e9b68 100644
--- a/services/core/java/com/android/server/wm/SurfaceAnimator.java
+++ b/services/core/java/com/android/server/wm/SurfaceAnimator.java
@@ -60,8 +60,6 @@ public class SurfaceAnimator {
@VisibleForTesting
SurfaceControl mLeash;
@VisibleForTesting
- SurfaceFreezer.Snapshot mSnapshot;
- @VisibleForTesting
final Animatable mAnimatable;
@VisibleForTesting
final OnAnimationFinishedCallback mInnerAnimationFinishedCallback;
@@ -165,7 +163,7 @@ public class SurfaceAnimator {
@AnimationType int type,
@Nullable OnAnimationFinishedCallback animationFinishedCallback,
@Nullable Runnable animationCancelledCallback,
- @Nullable AnimationAdapter snapshotAnim, @Nullable SurfaceFreezer freezer) {
+ @Nullable AnimationAdapter snapshotAnim) {
cancelAnimation(t, true /* restarting */, true /* forwardCancel */);
mAnimation = anim;
mAnimationType = type;
@@ -177,7 +175,6 @@ public class SurfaceAnimator {
cancelAnimation();
return;
}
- mLeash = freezer != null ? freezer.takeLeashForAnimation() : null;
if (mLeash == null) {
mLeash = createAnimationLeash(mAnimatable, surface, t, type,
mAnimatable.getSurfaceWidth(), mAnimatable.getSurfaceHeight(), 0 /* x */,
@@ -192,21 +189,13 @@ public class SurfaceAnimator {
mAnimation.dump(pw, "");
ProtoLog.d(WM_DEBUG_ANIM, "Animation start for %s, anim=%s", mAnimatable, sw);
}
- if (snapshotAnim != null) {
- mSnapshot = freezer.takeSnapshotForAnimation();
- if (mSnapshot == null) {
- Slog.e(TAG, "No snapshot target to start animation on for " + mAnimatable);
- return;
- }
- mSnapshot.startAnimation(t, snapshotAnim, type);
- }
setAnimatorPendingState(t);
}
void startAnimation(Transaction t, AnimationAdapter anim, boolean hidden,
@AnimationType int type) {
startAnimation(t, anim, hidden, type, null /* animationFinishedCallback */,
- null /* animationCancelledCallback */, null /* snapshotAnim */, null /* freezer */);
+ null /* animationCancelledCallback */, null /* snapshotAnim */);
}
/** Indicates that there are surface operations in the pending transaction. */
@@ -317,7 +306,6 @@ public class SurfaceAnimator {
final OnAnimationFinishedCallback animationFinishedCallback =
mSurfaceAnimationFinishedCallback;
final Runnable animationCancelledCallback = mAnimationCancelledCallback;
- final SurfaceFreezer.Snapshot snapshot = mSnapshot;
reset(t, false);
if (animation != null) {
if (forwardCancel) {
@@ -337,9 +325,6 @@ public class SurfaceAnimator {
}
if (forwardCancel) {
- if (snapshot != null) {
- snapshot.cancelAnimation(t, false /* restarting */);
- }
if (leash != null) {
t.remove(leash);
mService.scheduleAnimationLocked();
@@ -352,12 +337,6 @@ public class SurfaceAnimator {
mAnimation = null;
mSurfaceAnimationFinishedCallback = null;
mAnimationType = ANIMATION_TYPE_NONE;
- final SurfaceFreezer.Snapshot snapshot = mSnapshot;
- mSnapshot = null;
- if (snapshot != null) {
- // Reset the mSnapshot reference before calling the callback to prevent circular reset.
- snapshot.cancelAnimation(t, !destroyLeash);
- }
if (mLeash == null) {
return;
}
@@ -597,8 +576,7 @@ public class SurfaceAnimator {
void commitPendingTransaction();
/**
- * Called when the animation leash is created. Note that this is also called by
- * {@link SurfaceFreezer}, so this doesn't mean we're about to start animating.
+ * Called when the animation leash is created.
*
* @param t The transaction to use to apply any necessary changes.
* @param leash The leash that was created.
diff --git a/services/core/java/com/android/server/wm/SurfaceFreezer.java b/services/core/java/com/android/server/wm/SurfaceFreezer.java
deleted file mode 100644
index e126ed65d508..000000000000
--- a/services/core/java/com/android/server/wm/SurfaceFreezer.java
+++ /dev/null
@@ -1,303 +0,0 @@
-/*
- * Copyright (C) 2020 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.server.wm;
-
-import static com.android.internal.protolog.WmProtoLogGroups.WM_SHOW_TRANSACTIONS;
-import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_SCREEN_ROTATION;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.graphics.GraphicBuffer;
-import android.graphics.PixelFormat;
-import android.graphics.Point;
-import android.graphics.Rect;
-import android.hardware.HardwareBuffer;
-import android.util.Slog;
-import android.view.SurfaceControl;
-import android.window.ScreenCapture;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.protolog.ProtoLog;
-
-/**
- * This class handles "freezing" of an Animatable. The Animatable in question should implement
- * Freezable.
- *
- * The point of this is to enable WindowContainers to each be capable of freezing themselves.
- * Freezing means taking a snapshot and placing it above everything in the sub-hierarchy.
- * The "placing above" requires that a parent surface be inserted above the target surface so that
- * the target surface and the snapshot are siblings.
- *
- * The overall flow for a transition using this would be:
- * 1. Set transition and record animatable in mChangingApps
- * 2. Call {@link #freeze} to set-up the leashes and cover with a snapshot.
- * 3. When transition participants are ready, start SurfaceAnimator with this as a parameter
- * 4. SurfaceAnimator will then {@link #takeLeashForAnimation} instead of creating another leash.
- * 5. The animation system should eventually clean this up via {@link #unfreeze}.
- */
-class SurfaceFreezer {
-
- private static final String TAG = "SurfaceFreezer";
-
- private final @NonNull Freezable mAnimatable;
- private final @NonNull WindowManagerService mWmService;
- @VisibleForTesting
- SurfaceControl mLeash;
- Snapshot mSnapshot = null;
- final Rect mFreezeBounds = new Rect();
-
- /**
- * @param animatable The object to animate.
- */
- SurfaceFreezer(@NonNull Freezable animatable, @NonNull WindowManagerService service) {
- mAnimatable = animatable;
- mWmService = service;
- }
-
- /**
- * Freeze the target surface. This is done by creating a leash (inserting a parent surface
- * above the target surface) and then taking a snapshot and placing it over the target surface.
- *
- * @param startBounds The original bounds (on screen) of the surface we are snapshotting.
- * @param relativePosition The related position of the snapshot surface to its parent.
- * @param freezeTarget The surface to take snapshot from. If {@code null}, we will take a
- * snapshot from the {@link #mAnimatable} surface.
- */
- void freeze(SurfaceControl.Transaction t, Rect startBounds, Point relativePosition,
- @Nullable SurfaceControl freezeTarget) {
- reset(t);
- mFreezeBounds.set(startBounds);
-
- mLeash = SurfaceAnimator.createAnimationLeash(mAnimatable, mAnimatable.getSurfaceControl(),
- t, ANIMATION_TYPE_SCREEN_ROTATION, startBounds.width(), startBounds.height(),
- relativePosition.x, relativePosition.y, false /* hidden */,
- mWmService.mTransactionFactory);
- mAnimatable.onAnimationLeashCreated(t, mLeash);
-
- freezeTarget = freezeTarget != null ? freezeTarget : mAnimatable.getFreezeSnapshotTarget();
- if (freezeTarget != null) {
- ScreenCapture.ScreenshotHardwareBuffer screenshotBuffer = createSnapshotBufferInner(
- freezeTarget, startBounds);
- final HardwareBuffer buffer = screenshotBuffer == null ? null
- : screenshotBuffer.getHardwareBuffer();
- if (buffer == null || buffer.getWidth() <= 1 || buffer.getHeight() <= 1) {
- // This can happen when display is not ready.
- Slog.w(TAG, "Failed to capture screenshot for " + mAnimatable);
- unfreeze(t);
- return;
- }
- mSnapshot = new Snapshot(t, screenshotBuffer, mLeash);
- }
- }
-
- /**
- * Used by {@link SurfaceAnimator}. This "transfers" the leash to be used for animation.
- * By transferring the leash, this will no longer try to clean-up the leash when finished.
- */
- SurfaceControl takeLeashForAnimation() {
- SurfaceControl out = mLeash;
- mLeash = null;
- return out;
- }
-
- /**
- * Used by {@link SurfaceAnimator}. This "transfers" the snapshot leash to be used for
- * animation. By transferring the leash, this will no longer try to clean-up the leash when
- * finished.
- */
- @Nullable
- Snapshot takeSnapshotForAnimation() {
- final Snapshot out = mSnapshot;
- mSnapshot = null;
- return out;
- }
-
- /**
- * Clean-up the snapshot and remove leash. If the leash was taken, this just cleans-up the
- * snapshot.
- */
- void unfreeze(SurfaceControl.Transaction t) {
- unfreezeInner(t);
- mAnimatable.onUnfrozen();
- }
-
- private void unfreezeInner(SurfaceControl.Transaction t) {
- if (mSnapshot != null) {
- mSnapshot.cancelAnimation(t, false /* restarting */);
- mSnapshot = null;
- }
- if (mLeash == null) {
- return;
- }
- SurfaceControl leash = mLeash;
- mLeash = null;
- final boolean scheduleAnim = SurfaceAnimator.removeLeash(t, mAnimatable, leash,
- true /* destroy */);
- if (scheduleAnim) {
- mWmService.scheduleAnimationLocked();
- }
- }
-
- /** Resets the snapshot before taking another one if the animation hasn't been started yet. */
- private void reset(SurfaceControl.Transaction t) {
- // Those would have been taken by the SurfaceAnimator if the animation has been started, so
- // we can remove the leash directly.
- // No need to reset the mAnimatable leash, as this is called before a new animation leash is
- // created, so another #onAnimationLeashCreated will be called.
- if (mSnapshot != null) {
- mSnapshot.destroy(t);
- mSnapshot = null;
- }
- if (mLeash != null) {
- t.remove(mLeash);
- mLeash = null;
- }
- }
-
- void setLayer(SurfaceControl.Transaction t, int layer) {
- if (mLeash != null) {
- t.setLayer(mLeash, layer);
- }
- }
-
- void setRelativeLayer(SurfaceControl.Transaction t, SurfaceControl relativeTo, int layer) {
- if (mLeash != null) {
- t.setRelativeLayer(mLeash, relativeTo, layer);
- }
- }
-
- boolean hasLeash() {
- return mLeash != null;
- }
-
- private static ScreenCapture.ScreenshotHardwareBuffer createSnapshotBuffer(
- @NonNull SurfaceControl target, @Nullable Rect bounds) {
- Rect cropBounds = null;
- if (bounds != null) {
- cropBounds = new Rect(bounds);
- cropBounds.offsetTo(0, 0);
- }
- ScreenCapture.LayerCaptureArgs captureArgs =
- new ScreenCapture.LayerCaptureArgs.Builder(target)
- .setSourceCrop(cropBounds)
- .setCaptureSecureLayers(true)
- .setAllowProtected(true)
- .build();
- return ScreenCapture.captureLayers(captureArgs);
- }
-
- @VisibleForTesting
- ScreenCapture.ScreenshotHardwareBuffer createSnapshotBufferInner(
- SurfaceControl target, Rect bounds) {
- return createSnapshotBuffer(target, bounds);
- }
-
- @VisibleForTesting
- GraphicBuffer createFromHardwareBufferInner(
- ScreenCapture.ScreenshotHardwareBuffer screenshotBuffer) {
- return GraphicBuffer.createFromHardwareBuffer(screenshotBuffer.getHardwareBuffer());
- }
-
- class Snapshot {
- private SurfaceControl mSurfaceControl;
- private AnimationAdapter mAnimation;
-
- /**
- * @param t Transaction to create the thumbnail in.
- * @param screenshotBuffer A thumbnail or placeholder for thumbnail to initialize with.
- */
- Snapshot(SurfaceControl.Transaction t,
- ScreenCapture.ScreenshotHardwareBuffer screenshotBuffer, SurfaceControl parent) {
- GraphicBuffer graphicBuffer = createFromHardwareBufferInner(screenshotBuffer);
-
- mSurfaceControl = mAnimatable.makeAnimationLeash()
- .setName("snapshot anim: " + mAnimatable.toString())
- .setFormat(PixelFormat.TRANSLUCENT)
- .setParent(parent)
- .setSecure(screenshotBuffer.containsSecureLayers())
- .setCallsite("SurfaceFreezer.Snapshot")
- .setBLASTLayer()
- .build();
-
- ProtoLog.i(WM_SHOW_TRANSACTIONS, " THUMBNAIL %s: CREATE", mSurfaceControl);
-
- t.setBuffer(mSurfaceControl, graphicBuffer);
- t.setColorSpace(mSurfaceControl, screenshotBuffer.getColorSpace());
- t.show(mSurfaceControl);
-
- // We parent the thumbnail to the container, and just place it on top of anything else
- // in the container.
- t.setLayer(mSurfaceControl, Integer.MAX_VALUE);
- }
-
- void destroy(SurfaceControl.Transaction t) {
- if (mSurfaceControl == null) {
- return;
- }
- t.remove(mSurfaceControl);
- mSurfaceControl = null;
- }
-
- /**
- * Starts an animation.
- *
- * @param anim The object that bridges the controller, {@link SurfaceAnimator}, with the
- * component responsible for running the animation. It runs the animation with
- * {@link AnimationAdapter#startAnimation} once the hierarchy with
- * the Leash has been set up.
- */
- void startAnimation(SurfaceControl.Transaction t, AnimationAdapter anim, int type) {
- cancelAnimation(t, true /* restarting */);
- mAnimation = anim;
- if (mSurfaceControl == null) {
- cancelAnimation(t, false /* restarting */);
- return;
- }
- mAnimation.startAnimation(mSurfaceControl, t, type, (typ, ani) -> { });
- }
-
- /**
- * Cancels the animation, and resets the leash.
- *
- * @param t The transaction to use for all cancelling surface operations.
- * @param restarting Whether we are restarting the animation.
- */
- void cancelAnimation(SurfaceControl.Transaction t, boolean restarting) {
- final SurfaceControl leash = mSurfaceControl;
- final AnimationAdapter animation = mAnimation;
- mAnimation = null;
- if (animation != null) {
- animation.onAnimationCancelled(leash);
- }
- if (!restarting) {
- destroy(t);
- }
- }
- }
-
- /** freezable */
- public interface Freezable extends SurfaceAnimator.Animatable {
- /**
- * @return The surface to take a snapshot of. If this returns {@code null}, no snapshot
- * will be generated (but the rest of the freezing logic will still happen).
- */
- @Nullable SurfaceControl getFreezeSnapshotTarget();
-
- /** Called when the {@link #unfreeze(SurfaceControl.Transaction)} is called. */
- void onUnfrozen();
- }
-}
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index c6136f316c3e..f2f926ac952c 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -52,11 +52,9 @@ import static android.view.SurfaceControl.METADATA_TASK_ID;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES;
-import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_FLAG_APP_CRASHED;
import static android.view.WindowManager.TRANSIT_NONE;
-import static android.view.WindowManager.TRANSIT_OLD_TASK_CHANGE_WINDOWING_MODE;
import static android.view.WindowManager.TRANSIT_OPEN;
import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
@@ -75,7 +73,6 @@ import static com.android.server.wm.ActivityRecord.TRANSFER_SPLASH_SCREEN_COPYIN
import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_RECENTS;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_SWITCH;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_TRANSITION;
-import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_USER_LEAVING;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_CLEANUP;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_RECENTS;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_SWITCH;
@@ -161,13 +158,11 @@ import android.os.Trace;
import android.os.UserHandle;
import android.provider.Settings;
import android.service.voice.IVoiceInteractionSession;
-import android.util.ArraySet;
import android.util.DisplayMetrics;
import android.util.Slog;
import android.util.proto.ProtoOutputStream;
import android.view.DisplayInfo;
import android.view.InsetsState;
-import android.view.RemoteAnimationAdapter;
import android.view.SurfaceControl;
import android.view.WindowInsets;
import android.view.WindowManager;
@@ -2345,31 +2340,8 @@ class Task extends TaskFragment {
}
@VisibleForTesting
- Point getLastSurfaceSize() {
- return mLastSurfaceSize;
- }
-
- @VisibleForTesting
boolean isInChangeTransition() {
- return mSurfaceFreezer.hasLeash() || AppTransition.isChangeTransitOld(mTransit);
- }
-
- @Override
- public SurfaceControl getFreezeSnapshotTarget() {
- if (!mDisplayContent.mAppTransition.containsTransitRequest(TRANSIT_CHANGE)) {
- return null;
- }
- // Skip creating snapshot if this transition is controlled by a remote animator which
- // doesn't need it.
- final ArraySet<Integer> activityTypes = new ArraySet<>();
- activityTypes.add(getActivityType());
- final RemoteAnimationAdapter adapter =
- mDisplayContent.mAppTransitionController.getRemoteAnimationOverride(
- this, TRANSIT_OLD_TASK_CHANGE_WINDOWING_MODE, activityTypes);
- if (adapter != null && !adapter.getChangeNeedsSnapshot()) {
- return null;
- }
- return getSurfaceControl();
+ return AppTransition.isChangeTransitOld(mTransit);
}
@Override
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index fc7437b95e03..ba48fdc963de 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -1195,10 +1195,8 @@ class TaskFragment extends WindowContainer<WindowContainer> {
if (!isAttached() || isForceHidden() || isForceTranslucent()) {
return true;
}
- // A TaskFragment isn't translucent if it has at least one visible activity that occludes
- // this TaskFragment.
- return mTaskSupervisor.mOpaqueActivityHelper.getVisibleOpaqueActivity(this,
- starting, true /* ignoringKeyguard */) == null;
+ return !mTaskSupervisor.mOpaqueContainerHelper.isOpaque(
+ this, starting, true /* ignoringKeyguard */, true /* ignoringInvisibleActivity */);
}
/**
@@ -1211,7 +1209,7 @@ class TaskFragment extends WindowContainer<WindowContainer> {
return true;
}
// Including finishing Activity if the TaskFragment is becoming invisible in the transition.
- return mTaskSupervisor.mOpaqueActivityHelper.getOpaqueActivity(this) == null;
+ return !mTaskSupervisor.mOpaqueContainerHelper.isOpaque(this);
}
/**
@@ -1222,8 +1220,8 @@ class TaskFragment extends WindowContainer<WindowContainer> {
if (!isAttached() || isForceHidden() || isForceTranslucent()) {
return true;
}
- return mTaskSupervisor.mOpaqueActivityHelper.getVisibleOpaqueActivity(this, null,
- false /* ignoringKeyguard */) == null;
+ return !mTaskSupervisor.mOpaqueContainerHelper.isOpaque(this, /* starting */ null,
+ false /* ignoringKeyguard */, true /* ignoringInvisibleActivity */);
}
ActivityRecord getTopNonFinishingActivity() {
@@ -2758,7 +2756,7 @@ class TaskFragment extends WindowContainer<WindowContainer> {
// We only want to update for organized TaskFragment. Task will handle itself.
return;
}
- if (mSurfaceControl == null || mSurfaceAnimator.hasLeash() || mSurfaceFreezer.hasLeash()) {
+ if (mSurfaceControl == null || mSurfaceAnimator.hasLeash()) {
return;
}
@@ -2900,20 +2898,6 @@ class TaskFragment extends WindowContainer<WindowContainer> {
return task != null && !task.isDragResizing() && super.canStartChangeTransition();
}
- /**
- * Returns {@code true} if the starting bounds of the closing organized TaskFragment is
- * recorded. Otherwise, return {@code false}.
- */
- boolean setClosingChangingStartBoundsIfNeeded() {
- if (isOrganizedTaskFragment() && mDisplayContent != null
- && mDisplayContent.mChangingContainers.remove(this)) {
- mDisplayContent.mClosingChangingContainers.put(
- this, new Rect(mSurfaceFreezer.mFreezeBounds));
- return true;
- }
- return false;
- }
-
@Override
boolean isSyncFinished(BLASTSyncEngine.SyncGroup group) {
return super.isSyncFinished(group) && isReadyToTransit();
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 27683b2fcff2..5217a759c6ae 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -505,7 +505,7 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
final WindowContainer<?> sibling = rootParent.getChildAt(j);
if (sibling == transientRoot) break;
if (!sibling.getWindowConfiguration().isAlwaysOnTop() && mController.mAtm
- .mTaskSupervisor.mOpaqueActivityHelper.getOpaqueActivity(sibling) != null) {
+ .mTaskSupervisor.mOpaqueContainerHelper.isOpaque(sibling)) {
occludedCount++;
break;
}
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 883d8f95b612..225951dbd345 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -138,7 +138,7 @@ import java.util.function.Predicate;
* changes are made to this class.
*/
class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<E>
- implements Comparable<WindowContainer>, Animatable, SurfaceFreezer.Freezable,
+ implements Comparable<WindowContainer>, Animatable,
InsetsControlTarget {
private static final String TAG = TAG_WITH_CLASS_NAME ? "WindowContainer" : TAG_WM;
@@ -226,7 +226,6 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
@Nullable
private SurfaceControl mAnimationLeash;
- final SurfaceFreezer mSurfaceFreezer;
protected final WindowManagerService mWmService;
final TransitionController mTransitionController;
@@ -361,7 +360,6 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
mTransitionController = mWmService.mAtmService.getTransitionController();
mSyncTransaction = wms.mTransactionFactory.get();
mSurfaceAnimator = new SurfaceAnimator(this, this::onAnimationFinished, wms);
- mSurfaceFreezer = new SurfaceFreezer(this, wms);
}
/**
@@ -908,7 +906,6 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
final DisplayContent dc = getDisplayContent();
if (dc != null) {
dc.mClosingChangingContainers.remove(this);
- mSurfaceFreezer.unfreeze(getSyncTransaction());
}
while (!mChildren.isEmpty()) {
final E child = mChildren.getLast();
@@ -1124,9 +1121,7 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
// Cancel any change transition queued-up for this container on the old display when
// this container is moved from the old display.
mDisplayContent.mClosingChangingContainers.remove(this);
- if (mDisplayContent.mChangingContainers.remove(this)) {
- mSurfaceFreezer.unfreeze(getSyncTransaction());
- }
+ mDisplayContent.mChangingContainers.remove(this);
}
mDisplayContent = dc;
if (dc != null && dc != this && mPendingTransaction != null) {
@@ -1434,33 +1429,6 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
return setVisibleRequested(newVisReq);
}
- /**
- * Called when the visibility of a child is asked to change. This is before visibility actually
- * changes (eg. a transition animation might play out first).
- */
- void onChildVisibilityRequested(boolean visible) {
- // If we are losing visibility, then a snapshot isn't necessary and we are no-longer
- // part of a change transition.
- if (!visible) {
- boolean skipUnfreeze = false;
- if (asTaskFragment() != null) {
- // If the organized TaskFragment is closing while resizing, we want to keep track of
- // its starting bounds to make sure the animation starts at the correct position.
- // This should be called before unfreeze() because we record the starting bounds
- // in SurfaceFreezer.
- skipUnfreeze = asTaskFragment().setClosingChangingStartBoundsIfNeeded();
- }
-
- if (!skipUnfreeze) {
- mSurfaceFreezer.unfreeze(getSyncTransaction());
- }
- }
- WindowContainer parent = getParent();
- if (parent != null) {
- parent.onChildVisibilityRequested(visible);
- }
- }
-
/** Whether this window is closing while resizing. */
boolean isClosingWhenResizing() {
return mDisplayContent != null
@@ -1545,9 +1513,6 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
}
void onAppTransitionDone() {
- if (mSurfaceFreezer.hasLeash()) {
- mSurfaceFreezer.unfreeze(getSyncTransaction());
- }
for (int i = mChildren.size() - 1; i >= 0; --i) {
final WindowContainer wc = mChildren.get(i);
wc.onAppTransitionDone();
@@ -2773,15 +2738,9 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
}
protected void setLayer(Transaction t, int layer) {
- if (mSurfaceFreezer.hasLeash()) {
- // When the freezer has created animation leash parent for the window, set the layer
- // there instead.
- mSurfaceFreezer.setLayer(t, layer);
- } else {
- // Route through surface animator to accommodate that our surface control might be
- // attached to the leash, and leash is attached to parent container.
- mSurfaceAnimator.setLayer(t, layer);
- }
+ // Route through surface animator to accommodate that our surface control might be
+ // attached to the leash, and leash is attached to parent container.
+ mSurfaceAnimator.setLayer(t, layer);
}
int getLastLayer() {
@@ -2793,20 +2752,14 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
}
protected void setRelativeLayer(Transaction t, SurfaceControl relativeTo, int layer) {
- if (mSurfaceFreezer.hasLeash()) {
- // When the freezer has created animation leash parent for the window, set the layer
- // there instead.
- mSurfaceFreezer.setRelativeLayer(t, relativeTo, layer);
- } else {
- // Route through surface animator to accommodate that our surface control might be
- // attached to the leash, and leash is attached to parent container.
- mSurfaceAnimator.setRelativeLayer(t, relativeTo, layer);
- }
+ // Route through surface animator to accommodate that our surface control might be
+ // attached to the leash, and leash is attached to parent container.
+ mSurfaceAnimator.setRelativeLayer(t, relativeTo, layer);
}
protected void reparentSurfaceControl(Transaction t, SurfaceControl newParent) {
// Don't reparent active leashes since the animator won't know about the change.
- if (mSurfaceFreezer.hasLeash() || mSurfaceAnimator.hasLeash()) return;
+ if (mSurfaceAnimator.hasLeash()) return;
t.reparent(getSurfaceControl(), newParent);
}
@@ -3044,7 +2997,7 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
// TODO: This should use isVisible() but because isVisible has a really weird meaning at
// the moment this doesn't work for all animatable window containers.
mSurfaceAnimator.startAnimation(t, anim, hidden, type, animationFinishedCallback,
- animationCancelledCallback, snapshotAnim, mSurfaceFreezer);
+ animationCancelledCallback, snapshotAnim);
}
void startAnimation(Transaction t, AnimationAdapter anim, boolean hidden,
@@ -3066,7 +3019,6 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
void cancelAnimation() {
doAnimationFinished(mSurfaceAnimator.getAnimationType(), mSurfaceAnimator.getAnimation());
mSurfaceAnimator.cancelAnimation();
- mSurfaceFreezer.unfreeze(getSyncTransaction());
}
/** Whether we can start change transition with this window and current display status. */
@@ -3097,7 +3049,7 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
}
/**
- * Initializes a change transition. See {@link SurfaceFreezer} for more information.
+ * Initializes a change transition.
*
* For now, this will only be called for the following cases:
* 1. {@link Task} is changing windowing mode between fullscreen and freeform.
@@ -3109,8 +3061,6 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
* use case.
*
* @param startBounds The original bounds (on screen) of the surface we are snapshotting.
- * @param freezeTarget The surface to take snapshot from. If {@code null}, we will take a
- * snapshot from {@link #getFreezeSnapshotTarget()}.
*/
void initializeChangeTransition(Rect startBounds, @Nullable SurfaceControl freezeTarget) {
if (mDisplayContent.mTransitionController.isShellTransitionsEnabled()) {
@@ -3122,7 +3072,6 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
// Calculate the relative position in parent container.
final Rect parentBounds = getParent().getBounds();
mTmpPoint.set(startBounds.left - parentBounds.left, startBounds.top - parentBounds.top);
- mSurfaceFreezer.freeze(getSyncTransaction(), startBounds, mTmpPoint, freezeTarget);
}
void initializeChangeTransition(Rect startBounds) {
@@ -3134,23 +3083,6 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
}
@Override
- public SurfaceControl getFreezeSnapshotTarget() {
- // Only allow freezing if this window is in a TRANSIT_CHANGE
- if (!mDisplayContent.mAppTransition.containsTransitRequest(TRANSIT_CHANGE)
- || !mDisplayContent.mChangingContainers.contains(this)) {
- return null;
- }
- return getSurfaceControl();
- }
-
- @Override
- public void onUnfrozen() {
- if (mDisplayContent != null) {
- mDisplayContent.mChangingContainers.remove(this);
- }
- }
-
- @Override
public Builder makeAnimationLeash() {
return makeSurface().setContainerLayer();
}
@@ -3279,7 +3211,7 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
this, mTmpPoint, localBounds, screenBounds, closingStartBounds,
showBackdrop, false /* shouldCreateSnapshot */);
} else {
- final Rect startBounds = isChanging ? mSurfaceFreezer.mFreezeBounds : null;
+ final Rect startBounds = null;
adapters = controller.createRemoteAnimationRecord(
this, mTmpPoint, localBounds, screenBounds, startBounds, showBackdrop);
}
@@ -3298,16 +3230,12 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
mTmpRect.offsetTo(mTmpPoint.x, mTmpPoint.y);
final AnimationAdapter adapter = new LocalAnimationAdapter(
- new WindowChangeAnimationSpec(mSurfaceFreezer.mFreezeBounds, mTmpRect,
+ new WindowChangeAnimationSpec(null /* startBounds */, mTmpRect,
displayInfo, durationScale, true /* isAppAnimation */,
false /* isThumbnail */),
getSurfaceAnimationRunner());
- final AnimationAdapter thumbnailAdapter = mSurfaceFreezer.mSnapshot != null
- ? new LocalAnimationAdapter(new WindowChangeAnimationSpec(
- mSurfaceFreezer.mFreezeBounds, mTmpRect, displayInfo, durationScale,
- true /* isAppAnimation */, true /* isThumbnail */), getSurfaceAnimationRunner())
- : null;
+ final AnimationAdapter thumbnailAdapter = null;
resultAdapters = new Pair<>(adapter, thumbnailAdapter);
mTransit = transit;
mTransitFlags = getDisplayContent().mAppTransition.getTransitFlags();
@@ -3731,7 +3659,7 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
*/
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PROTECTED)
void updateSurfacePosition(Transaction t) {
- if (mSurfaceControl == null || mSurfaceAnimator.hasLeash() || mSurfaceFreezer.hasLeash()) {
+ if (mSurfaceControl == null || mSurfaceAnimator.hasLeash()) {
return;
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java
index c77b1d9a7bcf..6e224f07fcdc 100644
--- a/services/core/java/com/android/server/wm/WindowManagerInternal.java
+++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java
@@ -1137,6 +1137,15 @@ public abstract class WindowManagerInternal {
* Returns an instance of {@link ScreenshotHardwareBuffer} containing the current
* screenshot.
*/
- public abstract ScreenshotHardwareBuffer takeAssistScreenshot(
- Set<Integer> windowTypesToExclude);
+ public abstract ScreenshotHardwareBuffer takeAssistScreenshot();
+
+ /**
+ * Returns an instance of {@link ScreenshotHardwareBuffer} containing the current
+ * screenshot, excluding layers that are not appropriate to pass to contextual search
+ * services - such as the cursor or any current contextual search window.
+ *
+ * @param uid the UID of the contextual search application. System alert windows belonging
+ * to this UID will be excluded from the screenshot.
+ */
+ public abstract ScreenshotHardwareBuffer takeContextualSearchScreenshot(int uid);
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 3a1d652f82d4..7f1924005b2f 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -325,6 +325,7 @@ import android.window.WindowContainerToken;
import android.window.WindowContextInfo;
import com.android.internal.R;
+import com.android.internal.util.ToBooleanFunction;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.annotations.VisibleForTesting.Visibility;
@@ -4159,7 +4160,8 @@ public class WindowManagerService extends IWindowManager.Stub
}
@Nullable
- private ScreenshotHardwareBuffer takeAssistScreenshot(Set<Integer> windowTypesToExclude) {
+ private ScreenshotHardwareBuffer takeAssistScreenshot(
+ @Nullable ToBooleanFunction<WindowState> predicate) {
if (!checkCallingPermission(READ_FRAME_BUFFER, "requestAssistScreenshot()")) {
throw new SecurityException("Requires READ_FRAME_BUFFER permission");
}
@@ -4174,7 +4176,7 @@ public class WindowManagerService extends IWindowManager.Stub
}
captureArgs = null;
} else {
- captureArgs = displayContent.getLayerCaptureArgs(windowTypesToExclude);
+ captureArgs = displayContent.getLayerCaptureArgs(predicate);
}
}
@@ -4204,8 +4206,7 @@ public class WindowManagerService extends IWindowManager.Stub
*/
@Override
public boolean requestAssistScreenshot(final IAssistDataReceiver receiver) {
- final ScreenshotHardwareBuffer shb =
- takeAssistScreenshot(/* windowTypesToExclude= */ Set.of());
+ final ScreenshotHardwareBuffer shb = takeAssistScreenshot(/* predicate= */ null);
final Bitmap bm = shb != null ? shb.asBitmap() : null;
FgThread.getHandler().post(() -> {
try {
@@ -8618,9 +8619,27 @@ public class WindowManagerService extends IWindowManager.Stub
}
@Override
- public ScreenshotHardwareBuffer takeAssistScreenshot(Set<Integer> windowTypesToExclude) {
+ public ScreenshotHardwareBuffer takeAssistScreenshot() {
// WMS.takeAssistScreenshot takes care of the locking.
- return WindowManagerService.this.takeAssistScreenshot(windowTypesToExclude);
+ return WindowManagerService.this.takeAssistScreenshot(/* predicate */ null);
+ }
+
+ @Override
+ public ScreenshotHardwareBuffer takeContextualSearchScreenshot(int uid) {
+ // WMS.takeAssistScreenshot takes care of the locking.
+ return WindowManagerService.this.takeAssistScreenshot(win -> {
+ switch (win.getWindowType()) {
+ case LayoutParams.TYPE_STATUS_BAR:
+ case LayoutParams.TYPE_NAVIGATION_BAR:
+ case LayoutParams.TYPE_NAVIGATION_BAR_PANEL:
+ case LayoutParams.TYPE_POINTER:
+ return false;
+ case LayoutParams.TYPE_APPLICATION_OVERLAY:
+ return uid != win.getOwningUid();
+ default:
+ return true;
+ }
+ });
}
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 582cd4ed8003..e11c31c88c87 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -2723,16 +2723,16 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
}
- /**
- * Apply default restrictions that haven't been applied to a given admin yet.
- */
+ /** Apply default restrictions that haven't been applied to a given admin yet. */
private void maybeSetDefaultRestrictionsForAdminLocked(int userId, ActiveAdmin admin) {
- Set<String> defaultRestrictions =
- UserRestrictionsUtils.getDefaultEnabledForManagedProfiles();
- if (defaultRestrictions.equals(admin.defaultEnabledRestrictionsAlreadySet)) {
+ Set<String> newDefaultRestrictions = new HashSet(
+ UserRestrictionsUtils.getDefaultEnabledForManagedProfiles());
+ newDefaultRestrictions.removeAll(admin.defaultEnabledRestrictionsAlreadySet);
+ if (newDefaultRestrictions.isEmpty()) {
return; // The same set of default restrictions has been already applied.
}
- for (String restriction : defaultRestrictions) {
+
+ for (String restriction : newDefaultRestrictions) {
mDevicePolicyEngine.setLocalPolicy(
PolicyDefinition.getPolicyDefinitionForUserRestriction(restriction),
EnforcingAdmin.createEnterpriseEnforcingAdmin(
@@ -2740,10 +2740,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
admin.getUserHandle().getIdentifier()),
new BooleanPolicyValue(true),
userId);
+ admin.defaultEnabledRestrictionsAlreadySet.add(restriction);
+ Slogf.i(LOG_TAG, "Enabled the following restriction by default: " + restriction);
}
- admin.defaultEnabledRestrictionsAlreadySet.addAll(defaultRestrictions);
- Slogf.i(LOG_TAG, "Enabled the following restrictions by default: "
- + defaultRestrictions);
}
private void maybeStartSecurityLogMonitorOnActivityManagerReady() {
@@ -10282,7 +10281,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
return false;
}
- if (isAdb(caller)) {
+ boolean isAdb = isAdb(caller);
+ if (isAdb) {
// Log profile owner provisioning was started using adb.
MetricsLogger.action(mContext, PROVISIONING_ENTRY_POINT_ADB, LOG_TAG_PROFILE_OWNER);
DevicePolicyEventLogger
@@ -10305,6 +10305,18 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
ensureUnknownSourcesRestrictionForProfileOwnerLocked(userHandle, admin,
true /* newOwner */);
}
+ if(isAdb) {
+ // DISALLOW_DEBUGGING_FEATURES is being added to newly-created
+ // work profile by default due to b/382064697 . This would have
+ // impacted certain CTS test flows when they interact with the
+ // work profile via ADB (for example installing an app into the
+ // work profile). Remove DISALLOW_DEBUGGING_FEATURES here to
+ // reduce the potential impact.
+ setLocalUserRestrictionInternal(
+ EnforcingAdmin.createEnterpriseEnforcingAdmin(who, userHandle),
+ UserManager.DISALLOW_DEBUGGING_FEATURES, false, userHandle);
+ }
+
sendOwnerChangedBroadcast(DevicePolicyManager.ACTION_PROFILE_OWNER_CHANGED,
userHandle);
});
diff --git a/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt b/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt
index c62cd6e962b3..7128af5464e8 100644
--- a/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt
@@ -467,11 +467,17 @@ class PermissionService(private val service: AccessCheckingService) :
override fun getPermissionRequestState(
packageName: String,
permissionName: String,
- deviceId: String
+ deviceId: Int,
+ persistentDeviceId: String
): Int {
val pid = Binder.getCallingPid()
val uid = Binder.getCallingUid()
- val result = context.checkPermission(permissionName, pid, uid)
+ val deviceContext = if (deviceId == context.deviceId){
+ context
+ } else {
+ context.createDeviceContext(deviceId)
+ }
+ val result = deviceContext.checkPermission(permissionName, pid, uid)
if (result == PackageManager.PERMISSION_GRANTED) {
return Context.PERMISSION_REQUEST_STATE_GRANTED
}
@@ -497,14 +503,14 @@ class PermissionService(private val service: AccessCheckingService) :
val permissionFlags =
service.getState {
- getPermissionFlagsWithPolicy(appId, userId, permissionName, deviceId)
+ getPermissionFlagsWithPolicy(appId, userId, permissionName, persistentDeviceId)
}
val isUnreqestable = permissionFlags.hasAnyBit(UNREQUESTABLE_MASK)
// Special case for READ_MEDIA_IMAGES due to photo picker
if ((permissionName == Manifest.permission.READ_MEDIA_IMAGES ||
permissionName == Manifest.permission.READ_MEDIA_VIDEO) && isUnreqestable) {
val isUserSelectedGranted =
- context.checkPermission(
+ deviceContext.checkPermission(
Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED,
pid,
uid,
@@ -515,7 +521,7 @@ class PermissionService(private val service: AccessCheckingService) :
appId,
userId,
Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED,
- deviceId,
+ persistentDeviceId,
)
}
if (
diff --git a/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java b/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java
index 6b138b986fe7..29a17e1c85ab 100644
--- a/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java
+++ b/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java
@@ -89,9 +89,13 @@ import android.os.PowerManager;
import android.os.PowerManagerInternal;
import android.os.PowerSaveState;
import android.os.UserHandle;
+import android.os.WorkSource;
import android.os.test.TestLooper;
import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.DeviceConfig;
import android.provider.Settings;
@@ -200,6 +204,9 @@ public class PowerManagerServiceTest {
@Rule public SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
private PowerManagerService mService;
private ContextWrapper mContextSpy;
private BatteryReceiver mBatteryReceiver;
@@ -3045,6 +3052,40 @@ public class PowerManagerServiceTest {
}
/**
+ * Test IPowerManager.updateWakeLockUids() updates the workchain with the new uids
+ */
+ @Test
+ @RequiresFlagsEnabled({Flags.FLAG_WAKELOCK_ATTRIBUTION_VIA_WORKCHAIN})
+ public void test_updateWakelockUids_updatesWorkchain() {
+ createService();
+ startSystem();
+ final String tag = "wakelock1";
+ final String packageName = "pkg.name";
+ final IBinder token = new Binder();
+ int flags = PowerManager.PARTIAL_WAKE_LOCK;
+ final IWakeLockCallback callback1 = Mockito.mock(IWakeLockCallback.class);
+ final IBinder callbackBinder1 = Mockito.mock(Binder.class);
+ when(callback1.asBinder()).thenReturn(callbackBinder1);
+ WorkSource oldWorksource = new WorkSource();
+ oldWorksource.createWorkChain().addNode(1000, null);
+ mService.getBinderServiceInstance().acquireWakeLock(token, flags, tag, packageName,
+ oldWorksource, null /* historyTag */, Display.INVALID_DISPLAY, callback1);
+ verify(mNotifierMock).onWakeLockAcquired(anyInt(), eq(tag), eq(packageName),
+ anyInt(), anyInt(), eq(oldWorksource), any(), same(callback1));
+
+ WorkSource newWorksource = new WorkSource();
+ newWorksource.createWorkChain().addNode(1011, null)
+ .addNode(Binder.getCallingUid(), null);
+ newWorksource.createWorkChain().addNode(1012, null)
+ .addNode(Binder.getCallingUid(), null);
+ mService.getBinderServiceInstance().updateWakeLockUids(token, new int[]{1011, 1012});
+ verify(mNotifierMock).onWakeLockChanging(anyInt(), eq(tag), eq(packageName),
+ anyInt(), anyInt(), eq(oldWorksource), any(), any(),
+ anyInt(), eq(tag), eq(packageName), anyInt(), anyInt(), eq(newWorksource), any(),
+ any());
+ }
+
+ /**
* Test IPowerManager.updateWakeLockCallback() with a new IWakeLockCallback.
*/
@Test
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/WakelockStatsFrameworkEventsTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/WakelockStatsFrameworkEventsTest.java
index 1fe3f58a9dcb..f24e9ef03398 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/WakelockStatsFrameworkEventsTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/WakelockStatsFrameworkEventsTest.java
@@ -186,14 +186,16 @@ public class WakelockStatsFrameworkEventsTest {
public void wakelockOpen() throws Exception {
mEvents.noteStartWakeLock(UID_1, TAG_1, WAKELOCK_TYPE_1, TS_1);
- ArrayList<WakelockInfo> info = pullResults(TS_3);
-
- assertEquals("size", 1, info.size());
- assertEquals("uid", UID_1, info.get(0).uid);
- assertEquals("tag", TAG_1, info.get(0).tag);
- assertEquals("type", WAKELOCK_TYPE_1, info.get(0).type);
- assertEquals("duration", TS_3 - TS_1, info.get(0).uptimeMillis);
- assertEquals("count", 0, info.get(0).completedCount);
+ for (int i = 0; i < 5; i++) {
+ ArrayList<WakelockInfo> info = pullResults(TS_3);
+
+ assertEquals("size", 1, info.size());
+ assertEquals("uid", UID_1, info.get(0).uid);
+ assertEquals("tag", TAG_1, info.get(0).tag);
+ assertEquals("type", WAKELOCK_TYPE_1, info.get(0).type);
+ assertEquals("duration", TS_3 - TS_1, info.get(0).uptimeMillis);
+ assertEquals("count", 0, info.get(0).completedCount);
+ }
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/audio/VolumeHelperTest.java b/services/tests/servicestests/src/com/android/server/audio/VolumeHelperTest.java
index 0bbae247d8bb..2d81f72e3319 100644
--- a/services/tests/servicestests/src/com/android/server/audio/VolumeHelperTest.java
+++ b/services/tests/servicestests/src/com/android/server/audio/VolumeHelperTest.java
@@ -117,6 +117,12 @@ public class VolumeHelperTest {
/** Choose a default stream volume value which does not depend on min/max. */
private static final int DEFAULT_STREAM_VOLUME = 2;
+ /**
+ * The default ringer mode affected stream value since the ringer mode delegate is not used
+ * for unit testing.
+ */
+ private static final int DEFAULT_RINGER_MODE_AFFECTED_STREAMS = 0x1a6;
+
@Rule
public final MockitoRule mockito = MockitoJUnit.rule();
@@ -186,6 +192,10 @@ public class VolumeHelperTest {
public void setMuteAffectedStreams(int muteAffectedStreams) {
mMuteAffectedStreams = muteAffectedStreams;
}
+
+ public void setRingerModeAffectedStreams(int ringerModeAffectedStreams) {
+ mRingerModeAffectedStreams = ringerModeAffectedStreams;
+ }
}
private static class TestDeviceVolumeBehaviorDispatcherStub
@@ -550,6 +560,48 @@ public class VolumeHelperTest {
assertEquals(RINGER_MODE_VIBRATE, mAudioService.getRingerModeInternal());
}
+ @Test
+ public void setStreamVolume_doesNotUnmuteStreamAffectedByRingerMode() throws Exception {
+ assumeFalse("Skipping ringer mode test on automotive", mIsAutomotive);
+ mAudioService.setRingerModeAffectedStreams(DEFAULT_RINGER_MODE_AFFECTED_STREAMS);
+ mAudioService.setRingerModeInternal(RINGER_MODE_VIBRATE, mContext.getOpPackageName());
+
+ mAudioService.setStreamVolume(STREAM_NOTIFICATION, /*index=*/1, /*flags=*/0,
+ mContext.getOpPackageName());
+ mTestLooper.dispatchAll();
+
+ assertEquals(0, mAudioService.getStreamVolume(STREAM_NOTIFICATION));
+ }
+
+ @Test
+ public void adjustUnmuteStreamVolume_doesNotUnmuteStreamAffectedByRingerMode()
+ throws Exception {
+ assumeFalse("Skipping ringer mode test on automotive", mIsAutomotive);
+ mAudioService.setRingerModeAffectedStreams(DEFAULT_RINGER_MODE_AFFECTED_STREAMS);
+ mAudioService.setRingerModeInternal(RINGER_MODE_VIBRATE, mContext.getOpPackageName());
+
+ mAudioService.adjustStreamVolume(STREAM_NOTIFICATION, ADJUST_UNMUTE, /*flags=*/0,
+ mContext.getOpPackageName());
+ mTestLooper.dispatchAll();
+
+ assertEquals(0, mAudioService.getStreamVolume(STREAM_NOTIFICATION));
+ }
+
+ @Test
+ public void adjustRaiseStreamVolume_doesNotUnmuteStreamAffectedByRingerMode()
+ throws Exception {
+ assumeFalse("Skipping ringer mode test on automotive", mIsAutomotive);
+ mAudioService.setRingerModeAffectedStreams(DEFAULT_RINGER_MODE_AFFECTED_STREAMS);
+ mAudioService.setRingerModeInternal(RINGER_MODE_VIBRATE, mContext.getOpPackageName());
+
+ mAudioService.adjustStreamVolume(STREAM_NOTIFICATION, ADJUST_RAISE, /*flags=*/0,
+ mContext.getOpPackageName());
+ mTestLooper.dispatchAll();
+
+ assertEquals(0, mAudioService.getStreamVolume(STREAM_NOTIFICATION));
+ }
+
+
// --------------------- Permission tests ---------------------
@Test
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/ActiveSourceActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/ActiveSourceActionTest.java
index 6d863015231c..fcde4055cf17 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/ActiveSourceActionTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/ActiveSourceActionTest.java
@@ -16,12 +16,15 @@
package com.android.server.hdmi;
+import static android.content.pm.PackageManager.FEATURE_HDMI_CEC;
+
import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY;
import static com.android.server.hdmi.Constants.ADDR_TV;
import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC;
import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assume.assumeTrue;
import static org.mockito.Mockito.spy;
import android.annotation.RequiresPermission;
@@ -60,6 +63,9 @@ public class ActiveSourceActionTest {
@Before
public void setUp() throws Exception {
+ assumeTrue("Test requires FEATURE_HDMI_CEC",
+ InstrumentationRegistry.getTargetContext().getPackageManager()
+ .hasSystemFeature(FEATURE_HDMI_CEC));
mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
FakeAudioFramework audioFramework = new FakeAudioFramework();
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/ArcInitiationActionFromAvrTest.java b/services/tests/servicestests/src/com/android/server/hdmi/ArcInitiationActionFromAvrTest.java
index a5f7bb117e7d..9a6a24c52143 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/ArcInitiationActionFromAvrTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/ArcInitiationActionFromAvrTest.java
@@ -15,11 +15,14 @@
*/
package com.android.server.hdmi;
+import static android.content.pm.PackageManager.FEATURE_HDMI_CEC;
+
import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY;
import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC;
import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assume.assumeTrue;
import static org.mockito.Mockito.spy;
import android.annotation.RequiresPermission;
@@ -59,6 +62,9 @@ public class ArcInitiationActionFromAvrTest {
@Before
public void setUp() throws Exception {
+ assumeTrue("Test requires FEATURE_HDMI_CEC",
+ InstrumentationRegistry.getTargetContext().getPackageManager()
+ .hasSystemFeature(FEATURE_HDMI_CEC));
mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
FakeAudioFramework audioFramework = new FakeAudioFramework();
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java b/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java
index 857ee1aa176f..ee2f1767d849 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java
@@ -15,11 +15,14 @@
*/
package com.android.server.hdmi;
+import static android.content.pm.PackageManager.FEATURE_HDMI_CEC;
+
import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY;
import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC;
import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assume.assumeTrue;
import static org.mockito.Mockito.spy;
import android.annotation.RequiresPermission;
@@ -64,6 +67,9 @@ public class ArcTerminationActionFromAvrTest {
@Before
public void setUp() throws Exception {
+ assumeTrue("Test requires FEATURE_HDMI_CEC",
+ InstrumentationRegistry.getTargetContext().getPackageManager()
+ .hasSystemFeature(FEATURE_HDMI_CEC));
MockitoAnnotations.initMocks(this);
mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/DevicePowerStatusActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/DevicePowerStatusActionTest.java
index 2296911a4e7e..40f9dbc074c5 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/DevicePowerStatusActionTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/DevicePowerStatusActionTest.java
@@ -16,12 +16,15 @@
package com.android.server.hdmi;
+import static android.content.pm.PackageManager.FEATURE_HDMI_CEC;
+
import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY;
import static com.android.server.hdmi.Constants.ADDR_BROADCAST;
import static com.android.server.hdmi.Constants.ADDR_TV;
import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assume.assumeTrue;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
@@ -73,6 +76,9 @@ public class DevicePowerStatusActionTest {
@Before
public void setUp() throws Exception {
+ assumeTrue("Test requires FEATURE_HDMI_CEC",
+ InstrumentationRegistry.getTargetContext().getPackageManager()
+ .hasSystemFeature(FEATURE_HDMI_CEC));
MockitoAnnotations.initMocks(this);
mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromPlaybackTest.java b/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromPlaybackTest.java
index 47cfa4218435..461e98b4bf06 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromPlaybackTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromPlaybackTest.java
@@ -16,6 +16,7 @@
package com.android.server.hdmi;
+import static android.content.pm.PackageManager.FEATURE_HDMI_CEC;
import static android.hardware.hdmi.HdmiControlManager.POWER_STATUS_ON;
import static android.hardware.hdmi.HdmiControlManager.POWER_STATUS_STANDBY;
import static android.hardware.hdmi.HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON;
@@ -30,6 +31,8 @@ import static com.android.server.hdmi.DeviceSelectActionFromPlayback.STATE_WAIT_
import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assume.assumeTrue;
+
import android.annotation.RequiresPermission;
import android.content.Context;
import android.content.Intent;
@@ -94,6 +97,9 @@ public class DeviceSelectActionFromPlaybackTest {
@Before
public void setUp() {
+ assumeTrue("Test requires FEATURE_HDMI_CEC",
+ InstrumentationRegistry.getTargetContext().getPackageManager()
+ .hasSystemFeature(FEATURE_HDMI_CEC));
MockitoAnnotations.initMocks(this);
Context context = InstrumentationRegistry.getTargetContext();
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromTvTest.java b/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromTvTest.java
index 792faab5b196..a4c71bd6094e 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromTvTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromTvTest.java
@@ -16,6 +16,7 @@
package com.android.server.hdmi;
+import static android.content.pm.PackageManager.FEATURE_HDMI_CEC;
import static android.hardware.hdmi.HdmiControlManager.POWER_STATUS_ON;
import static android.hardware.hdmi.HdmiControlManager.POWER_STATUS_STANDBY;
import static android.hardware.hdmi.HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON;
@@ -30,6 +31,8 @@ import static com.android.server.hdmi.DeviceSelectActionFromTv.STATE_WAIT_FOR_RE
import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assume.assumeTrue;
+
import android.annotation.RequiresPermission;
import android.content.Context;
import android.content.Intent;
@@ -106,6 +109,9 @@ public class DeviceSelectActionFromTvTest {
@Before
public void setUp() {
+ assumeTrue("Test requires FEATURE_HDMI_CEC",
+ InstrumentationRegistry.getTargetContext().getPackageManager()
+ .hasSystemFeature(FEATURE_HDMI_CEC));
Context context = InstrumentationRegistry.getTargetContext();
mMyLooper = mTestLooper.getLooper();
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecAtomLoggingTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecAtomLoggingTest.java
index 30dac9f3813d..0e9f21948907 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecAtomLoggingTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecAtomLoggingTest.java
@@ -15,12 +15,15 @@
*/
package com.android.server.hdmi;
+import static android.content.pm.PackageManager.FEATURE_HDMI_CEC;
+
import static com.android.server.SystemService.PHASE_BOOT_COMPLETED;
import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_1;
import static com.android.server.hdmi.Constants.ADDR_TV;
import static com.android.server.hdmi.Constants.PATH_RELATIONSHIP_ANCESTOR;
import static com.android.server.hdmi.HdmiControlService.WAKE_UP_SCREEN_ON;
+import static org.junit.Assume.assumeTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -92,6 +95,9 @@ public class HdmiCecAtomLoggingTest {
@Before
public void setUp() throws RemoteException {
+ assumeTrue("Test requires FEATURE_HDMI_CEC",
+ InstrumentationRegistry.getTargetContext().getPackageManager()
+ .hasSystemFeature(FEATURE_HDMI_CEC));
mHdmiCecAtomWriterSpy = spy(new HdmiCecAtomWriter());
mLooper = mTestLooper.getLooper();
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecAtomLoggingTvTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecAtomLoggingTvTest.java
index e1e101fc1724..a0e21ed1bdb1 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecAtomLoggingTvTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecAtomLoggingTvTest.java
@@ -15,10 +15,13 @@
*/
package com.android.server.hdmi;
+import static android.content.pm.PackageManager.FEATURE_HDMI_CEC;
+
import static com.android.server.hdmi.Constants.HDMI_EARC_STATUS_EARC_PENDING;
import static com.android.server.hdmi.Constants.HDMI_EARC_STATUS_UNKNOWN;
import static com.android.server.hdmi.HdmiControlService.WAKE_UP_SCREEN_ON;
+import static org.junit.Assume.assumeTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
@@ -78,6 +81,9 @@ public class HdmiCecAtomLoggingTvTest {
@Before
public void setUp() throws RemoteException {
+ assumeTrue("Test requires FEATURE_HDMI_CEC",
+ InstrumentationRegistry.getInstrumentation().getTargetContext().getPackageManager()
+ .hasSystemFeature(FEATURE_HDMI_CEC));
mHdmiCecAtomWriterSpy = spy(new HdmiCecAtomWriter());
mLooper = mTestLooper.getLooper();
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecConfigTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecConfigTest.java
index d8c58a8e16b6..e66026735ec4 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecConfigTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecConfigTest.java
@@ -15,10 +15,13 @@
*/
package com.android.server.hdmi;
+import static android.content.pm.PackageManager.FEATURE_HDMI_CEC;
+
import static com.google.common.truth.Truth.assertThat;
import static junit.framework.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
@@ -65,6 +68,9 @@ public final class HdmiCecConfigTest {
@Before
public void setUp() throws Exception {
+ assumeTrue("Test requires FEATURE_HDMI_CEC",
+ InstrumentationRegistry.getTargetContext().getPackageManager()
+ .hasSystemFeature(FEATURE_HDMI_CEC));
MockitoAnnotations.initMocks(this);
mContext = FakeHdmiCecConfig.buildContext(InstrumentationRegistry.getTargetContext());
}
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java
index 4f551119b42e..314fe05b6367 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java
@@ -15,6 +15,8 @@
*/
package com.android.server.hdmi;
+import static android.content.pm.PackageManager.FEATURE_HDMI_CEC;
+
import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY;
import static com.android.server.hdmi.Constants.ADDR_AUDIO_SYSTEM;
import static com.android.server.hdmi.Constants.ADDR_BROADCAST;
@@ -26,6 +28,8 @@ import static com.android.server.hdmi.HdmiControlService.STANDBY_SCREEN_OFF;
import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assume.assumeTrue;
+
import android.annotation.RequiresPermission;
import android.content.Context;
import android.content.Intent;
@@ -85,6 +89,9 @@ public class HdmiCecLocalDeviceAudioSystemTest {
@Before
public void setUp() {
+ assumeTrue("Test requires FEATURE_HDMI_CEC",
+ InstrumentationRegistry.getTargetContext().getPackageManager()
+ .hasSystemFeature(FEATURE_HDMI_CEC));
Context context = InstrumentationRegistry.getTargetContext();
mMyLooper = mTestLooper.getLooper();
mLocalDeviceTypes.add(HdmiDeviceInfo.DEVICE_PLAYBACK);
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
index cfdf17668229..d600e16c6f13 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
@@ -15,6 +15,8 @@
*/
package com.android.server.hdmi;
+import static android.content.pm.PackageManager.FEATURE_HDMI_CEC;
+
import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY;
import static com.android.server.hdmi.Constants.ABORT_UNRECOGNIZED_OPCODE;
import static com.android.server.hdmi.Constants.ADDR_AUDIO_SYSTEM;
@@ -29,6 +31,8 @@ import static com.android.server.hdmi.PowerStatusMonitorActionFromPlayback.MONIT
import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assume.assumeTrue;
+
import android.annotation.RequiresPermission;
import android.content.Context;
import android.content.Intent;
@@ -97,6 +101,9 @@ public class HdmiCecLocalDevicePlaybackTest {
new FakePowerManagerInternalWrapper();
@Before
public void setUp() {
+ assumeTrue("Test requires FEATURE_HDMI_CEC",
+ InstrumentationRegistry.getTargetContext().getPackageManager()
+ .hasSystemFeature(FEATURE_HDMI_CEC));
MockitoAnnotations.initMocks(this);
Context context = InstrumentationRegistry.getTargetContext();
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
index 5be4490e67ef..f74e2ace7ae3 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
@@ -15,6 +15,8 @@
*/
package com.android.server.hdmi;
+import static android.content.pm.PackageManager.FEATURE_HDMI_CEC;
+
import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY;
import static com.android.server.hdmi.Constants.ABORT_UNRECOGNIZED_OPCODE;
import static com.android.server.hdmi.Constants.ADDR_AUDIO_SYSTEM;
@@ -29,8 +31,8 @@ import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_WAKE_UP_ME
import static com.android.server.hdmi.HdmiControlService.STANDBY_SCREEN_OFF;
import static com.android.server.hdmi.HdmiControlService.WAKE_UP_SCREEN_ON;
import static com.android.server.hdmi.RequestActiveSourceAction.TIMEOUT_WAIT_FOR_TV_ASSERT_ACTIVE_SOURCE_MS;
-import static com.android.server.hdmi.RoutingControlAction.TIMEOUT_ROUTING_INFORMATION_MS;
import static com.android.server.hdmi.RequestSadAction.RETRY_COUNTER_MAX;
+import static com.android.server.hdmi.RoutingControlAction.TIMEOUT_ROUTING_INFORMATION_MS;
import static com.google.common.truth.Truth.assertThat;
@@ -38,6 +40,7 @@ import static junit.framework.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.eq;
@@ -167,6 +170,9 @@ public class HdmiCecLocalDeviceTvTest {
@Before
public void setUp() {
+ assumeTrue("Test requires FEATURE_HDMI_CEC",
+ InstrumentationRegistry.getTargetContext().getPackageManager()
+ .hasSystemFeature(FEATURE_HDMI_CEC));
Context context = InstrumentationRegistry.getTargetContext();
mMyLooper = mTestLooper.getLooper();
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageValidatorTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageValidatorTest.java
index 6577e09a986e..587f4370636c 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageValidatorTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageValidatorTest.java
@@ -16,6 +16,8 @@
package com.android.server.hdmi;
+import static android.content.pm.PackageManager.FEATURE_HDMI_CEC;
+
import static com.android.server.hdmi.Constants.ADDR_AUDIO_SYSTEM;
import static com.android.server.hdmi.Constants.ADDR_BROADCAST;
import static com.android.server.hdmi.HdmiCecMessageValidator.ERROR_DESTINATION;
@@ -27,6 +29,8 @@ import static com.android.server.hdmi.HdmiCecMessageValidator.OK;
import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assume.assumeTrue;
+
import android.os.test.TestLooper;
import android.platform.test.annotations.Presubmit;
@@ -53,6 +57,9 @@ public class HdmiCecMessageValidatorTest {
@Before
public void setUp() throws Exception {
+ assumeTrue("Test requires FEATURE_HDMI_CEC",
+ InstrumentationRegistry.getTargetContext().getPackageManager()
+ .hasSystemFeature(FEATURE_HDMI_CEC));
FakeAudioFramework audioFramework = new FakeAudioFramework();
HdmiControlService mHdmiControlService = new HdmiControlService(
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecNetworkTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecNetworkTest.java
index 4f7f381a33d6..b1460b33cdf8 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecNetworkTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecNetworkTest.java
@@ -17,10 +17,13 @@
package com.android.server.hdmi;
+import static android.content.pm.PackageManager.FEATURE_HDMI_CEC;
import static android.hardware.hdmi.DeviceFeatures.FEATURE_SUPPORTED;
import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assume.assumeTrue;
+
import android.content.Context;
import android.hardware.hdmi.DeviceFeatures;
import android.hardware.hdmi.HdmiControlManager;
@@ -66,6 +69,9 @@ public class HdmiCecNetworkTest {
@Before
public void setUp() throws Exception {
+ assumeTrue("Test requires FEATURE_HDMI_CEC",
+ InstrumentationRegistry.getTargetContext().getPackageManager()
+ .hasSystemFeature(FEATURE_HDMI_CEC));
mContext = InstrumentationRegistry.getTargetContext();
FakeAudioFramework audioFramework = new FakeAudioFramework();
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecPowerStatusControllerTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecPowerStatusControllerTest.java
index 3361e7f359e2..c48e4b6cf710 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecPowerStatusControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecPowerStatusControllerTest.java
@@ -15,10 +15,13 @@
*/
package com.android.server.hdmi;
+import static android.content.pm.PackageManager.FEATURE_HDMI_CEC;
+
import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY;
import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assume.assumeTrue;
import static org.mockito.Mockito.spy;
import android.annotation.RequiresPermission;
@@ -63,6 +66,9 @@ public class HdmiCecPowerStatusControllerTest {
@Before
public void setUp() throws Exception {
+ assumeTrue("Test requires FEATURE_HDMI_CEC",
+ InstrumentationRegistry.getTargetContext().getPackageManager()
+ .hasSystemFeature(FEATURE_HDMI_CEC));
Context contextSpy = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
Looper myLooper = mTestLooper.getLooper();
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
index 126a65863f59..4eb3c15eed95 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
@@ -15,6 +15,7 @@
*/
package com.android.server.hdmi;
+import static android.content.pm.PackageManager.FEATURE_HDMI_CEC;
import static android.hardware.hdmi.HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM;
import static android.hardware.hdmi.HdmiDeviceInfo.DEVICE_PLAYBACK;
import static android.hardware.hdmi.HdmiDeviceInfo.DEVICE_TV;
@@ -38,6 +39,7 @@ import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
import static junit.framework.TestCase.assertEquals;
+import static org.junit.Assume.assumeTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -111,6 +113,9 @@ public class HdmiControlServiceTest {
@Before
public void setUp() throws Exception {
+ assumeTrue("Test requires FEATURE_HDMI_CEC",
+ InstrumentationRegistry.getTargetContext().getPackageManager()
+ .hasSystemFeature(FEATURE_HDMI_CEC));
mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
HdmiCecConfig hdmiCecConfig = new FakeHdmiCecConfig(mContextSpy);
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTvTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTvTest.java
index eed99756abb1..d28306458d55 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTvTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTvTest.java
@@ -16,12 +16,15 @@
package com.android.server.hdmi;
+import static android.content.pm.PackageManager.FEATURE_HDMI_CEC;
import static android.hardware.hdmi.HdmiDeviceInfo.DEVICE_TV;
import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY;
import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assume.assumeTrue;
+
import android.content.Context;
import android.hardware.hdmi.HdmiControlManager;
import android.hardware.hdmi.HdmiDeviceInfo;
@@ -60,6 +63,9 @@ public class HdmiControlServiceTvTest {
@Before
public void setUp() throws Exception {
+ assumeTrue("Test requires FEATURE_HDMI_CEC",
+ InstrumentationRegistry.getTargetContext().getPackageManager()
+ .hasSystemFeature(FEATURE_HDMI_CEC));
Context context = InstrumentationRegistry.getTargetContext();
mMyLooper = mTestLooper.getLooper();
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiEarcLocalDeviceTxTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiEarcLocalDeviceTxTest.java
index 185f90f4e803..98d2dfb21a0c 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiEarcLocalDeviceTxTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiEarcLocalDeviceTxTest.java
@@ -16,6 +16,7 @@
package com.android.server.hdmi;
+import static android.content.pm.PackageManager.FEATURE_HDMI_CEC;
import static android.media.AudioProfile.AUDIO_ENCAPSULATION_TYPE_NONE;
import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY;
@@ -25,6 +26,7 @@ import static com.android.server.hdmi.Constants.HDMI_EARC_STATUS_EARC_PENDING;
import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assume.assumeTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
@@ -84,6 +86,9 @@ public class HdmiEarcLocalDeviceTxTest {
@Before
public void setUp() {
+ assumeTrue("Test requires FEATURE_HDMI_CEC",
+ InstrumentationRegistry.getTargetContext().getPackageManager()
+ .hasSystemFeature(FEATURE_HDMI_CEC));
MockitoAnnotations.initMocks(this);
Context context = InstrumentationRegistry.getTargetContext();
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/PowerStatusMonitorActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/PowerStatusMonitorActionTest.java
index 974f64dbd84f..76d4b56f0fb3 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/PowerStatusMonitorActionTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/PowerStatusMonitorActionTest.java
@@ -16,6 +16,8 @@
package com.android.server.hdmi;
+import static android.content.pm.PackageManager.FEATURE_HDMI_CEC;
+
import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY;
import static com.android.server.hdmi.Constants.ADDR_BROADCAST;
import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_1;
@@ -23,6 +25,7 @@ import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_2;
import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assume.assumeTrue;
import static org.mockito.Mockito.spy;
import android.annotation.RequiresPermission;
@@ -64,6 +67,9 @@ public class PowerStatusMonitorActionTest {
@Before
public void setUp() throws Exception {
+ assumeTrue("Test requires FEATURE_HDMI_CEC",
+ InstrumentationRegistry.getTargetContext().getPackageManager()
+ .hasSystemFeature(FEATURE_HDMI_CEC));
mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
FakeAudioFramework audioFramework = new FakeAudioFramework();
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/RequestSadActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/RequestSadActionTest.java
index 4cf293758519..02e63f43c6c3 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/RequestSadActionTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/RequestSadActionTest.java
@@ -16,12 +16,16 @@
package com.android.server.hdmi;
+import static android.content.pm.PackageManager.FEATURE_HDMI_CEC;
+
import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY;
import static com.android.server.hdmi.Constants.ADDR_AUDIO_SYSTEM;
import static com.android.server.hdmi.RequestSadAction.RETRY_COUNTER_MAX;
import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assume.assumeTrue;
+
import android.annotation.RequiresPermission;
import android.content.Context;
import android.content.Intent;
@@ -95,6 +99,9 @@ public class RequestSadActionTest {
@Before
public void setUp() {
+ assumeTrue("Test requires FEATURE_HDMI_CEC",
+ InstrumentationRegistry.getTargetContext().getPackageManager()
+ .hasSystemFeature(FEATURE_HDMI_CEC));
Context context = InstrumentationRegistry.getTargetContext();
mMyLooper = mTestLooper.getLooper();
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/ResendCecCommandActionPlaybackTest.java b/services/tests/servicestests/src/com/android/server/hdmi/ResendCecCommandActionPlaybackTest.java
index 67a3f2a64d32..fa1d3261b84b 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/ResendCecCommandActionPlaybackTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/ResendCecCommandActionPlaybackTest.java
@@ -16,11 +16,15 @@
package com.android.server.hdmi;
+import static android.content.pm.PackageManager.FEATURE_HDMI_CEC;
+
import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY;
import static com.android.server.hdmi.ResendCecCommandAction.SEND_COMMAND_RETRY_MS;
import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assume.assumeTrue;
+
import android.annotation.RequiresPermission;
import android.content.Context;
import android.content.Intent;
@@ -58,6 +62,9 @@ public class ResendCecCommandActionPlaybackTest {
@Before
public void setUp() throws Exception {
+ assumeTrue("Test requires FEATURE_HDMI_CEC",
+ InstrumentationRegistry.getTargetContext().getPackageManager()
+ .hasSystemFeature(FEATURE_HDMI_CEC));
Context context = InstrumentationRegistry.getTargetContext();
FakeAudioFramework audioFramework = new FakeAudioFramework();
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/ResendCecCommandActionTvTest.java b/services/tests/servicestests/src/com/android/server/hdmi/ResendCecCommandActionTvTest.java
index 047a04c60176..2f68bab743b3 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/ResendCecCommandActionTvTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/ResendCecCommandActionTvTest.java
@@ -16,11 +16,15 @@
package com.android.server.hdmi;
+import static android.content.pm.PackageManager.FEATURE_HDMI_CEC;
+
import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY;
import static com.android.server.hdmi.ResendCecCommandAction.SEND_COMMAND_RETRY_MS;
import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assume.assumeTrue;
+
import android.annotation.RequiresPermission;
import android.content.Context;
import android.content.Intent;
@@ -56,6 +60,9 @@ public class ResendCecCommandActionTvTest {
@Before
public void setUp() throws Exception {
+ assumeTrue("Test requires FEATURE_HDMI_CEC",
+ InstrumentationRegistry.getTargetContext().getPackageManager()
+ .hasSystemFeature(FEATURE_HDMI_CEC));
Context context = InstrumentationRegistry.getTargetContext();
FakeAudioFramework audioFramework = new FakeAudioFramework();
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/RoutingControlActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/RoutingControlActionTest.java
index 1019db46482d..912392f1b70f 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/RoutingControlActionTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/RoutingControlActionTest.java
@@ -16,6 +16,8 @@
package com.android.server.hdmi;
+import static android.content.pm.PackageManager.FEATURE_HDMI_CEC;
+
import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY;
import static com.android.server.hdmi.Constants.ADDR_AUDIO_SYSTEM;
import static com.android.server.hdmi.Constants.ADDR_BROADCAST;
@@ -29,6 +31,8 @@ import static com.android.server.hdmi.RoutingControlAction.STATE_WAIT_FOR_ROUTIN
import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assume.assumeTrue;
+
import android.annotation.RequiresPermission;
import android.content.Context;
import android.content.Intent;
@@ -143,6 +147,9 @@ public class RoutingControlActionTest {
@Before
public void setUp() {
+ assumeTrue("Test requires FEATURE_HDMI_CEC",
+ InstrumentationRegistry.getTargetContext().getPackageManager()
+ .hasSystemFeature(FEATURE_HDMI_CEC));
Context context = InstrumentationRegistry.getTargetContext();
mMyLooper = mTestLooper.getLooper();
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/SetAudioVolumeLevelDiscoveryActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/SetAudioVolumeLevelDiscoveryActionTest.java
index e4297effed92..a1a5ffe55eaa 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/SetAudioVolumeLevelDiscoveryActionTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/SetAudioVolumeLevelDiscoveryActionTest.java
@@ -16,6 +16,7 @@
package com.android.server.hdmi;
+import static android.content.pm.PackageManager.FEATURE_HDMI_CEC;
import static android.hardware.hdmi.DeviceFeatures.FEATURE_NOT_SUPPORTED;
import static android.hardware.hdmi.DeviceFeatures.FEATURE_SUPPORTED;
import static android.hardware.hdmi.DeviceFeatures.FEATURE_SUPPORT_UNKNOWN;
@@ -24,6 +25,7 @@ import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY;
import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assume.assumeTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.doNothing;
@@ -79,6 +81,9 @@ public class SetAudioVolumeLevelDiscoveryActionTest {
*/
@Before
public void setUp() throws RemoteException {
+ assumeTrue("Test requires FEATURE_HDMI_CEC",
+ InstrumentationRegistry.getTargetContext().getPackageManager()
+ .hasSystemFeature(FEATURE_HDMI_CEC));
mContextSpy = spy(new ContextWrapper(
InstrumentationRegistry.getInstrumentation().getTargetContext()));
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioAutoInitiationActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioAutoInitiationActionTest.java
index effea5abcecb..c358e1d4d5db 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioAutoInitiationActionTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioAutoInitiationActionTest.java
@@ -17,12 +17,15 @@
package com.android.server.hdmi;
+import static android.content.pm.PackageManager.FEATURE_HDMI_CEC;
+
import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY;
import static com.android.server.hdmi.Constants.ADDR_AUDIO_SYSTEM;
import static com.android.server.hdmi.SystemAudioAutoInitiationAction.RETRIES_ON_TIMEOUT;
import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assume.assumeTrue;
import static org.mockito.Mockito.spy;
import android.annotation.RequiresPermission;
@@ -65,6 +68,9 @@ public class SystemAudioAutoInitiationActionTest {
@Before
public void setUp() throws Exception {
+ assumeTrue("Test requires FEATURE_HDMI_CEC",
+ InstrumentationRegistry.getTargetContext().getPackageManager()
+ .hasSystemFeature(FEATURE_HDMI_CEC));
mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
Looper myLooper = mTestLooper.getLooper();
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java
index 19b90b6b76d9..076e3e9fcc24 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java
@@ -22,6 +22,7 @@ import static android.service.notification.Adjustment.KEY_TYPE;
import static android.service.notification.Adjustment.TYPE_CONTENT_RECOMMENDATION;
import static android.service.notification.Adjustment.TYPE_NEWS;
import static android.service.notification.Adjustment.TYPE_PROMOTION;
+import static android.service.notification.Adjustment.TYPE_SOCIAL_MEDIA;
import static com.android.server.notification.NotificationManagerService.DEFAULT_ALLOWED_ADJUSTMENTS;
@@ -611,7 +612,8 @@ public class NotificationAssistantsTest extends UiServiceTestCase {
ManagedServices.ManagedServiceInfo info =
mAssistants.new ManagedServiceInfo(null, mCn, userId, false, null, 35, 2345256);
- mAssistants.setAdjustmentTypeSupportedState(info, Adjustment.KEY_NOT_CONVERSATION, false);
+ mAssistants.setAdjustmentTypeSupportedState(
+ info.userid, Adjustment.KEY_NOT_CONVERSATION, false);
assertThat(mAssistants.getUnsupportedAdjustments(userId)).contains(
Adjustment.KEY_NOT_CONVERSATION);
@@ -632,7 +634,8 @@ public class NotificationAssistantsTest extends UiServiceTestCase {
ManagedServices.ManagedServiceInfo info =
mAssistants.new ManagedServiceInfo(null, mCn, userId, false, null, 35, 2345256);
- mAssistants.setAdjustmentTypeSupportedState(info, Adjustment.KEY_NOT_CONVERSATION, false);
+ mAssistants.setAdjustmentTypeSupportedState(
+ info.userid, Adjustment.KEY_NOT_CONVERSATION, false);
writeXmlAndReload(USER_ALL);
@@ -654,7 +657,6 @@ public class NotificationAssistantsTest extends UiServiceTestCase {
assertNotNull(current);
writeXmlAndReload(USER_ALL);
-
assertThat(mAssistants.getUnsupportedAdjustments(userId).size()).isEqualTo(0);
}
@@ -707,26 +709,29 @@ public class NotificationAssistantsTest extends UiServiceTestCase {
@Test
@EnableFlags(android.service.notification.Flags.FLAG_NOTIFICATION_CLASSIFICATION)
public void testSetAssistantAdjustmentKeyTypeState_allow() {
- assertThat(mAssistants.getAllowedClassificationTypes()).asList()
- .containsExactly(TYPE_PROMOTION);
+ mAssistants.setAssistantAdjustmentKeyTypeState(TYPE_CONTENT_RECOMMENDATION, false);
+ assertThat(mAssistants.getAllowedClassificationTypes())
+ .asList().doesNotContain(TYPE_CONTENT_RECOMMENDATION);
mAssistants.setAssistantAdjustmentKeyTypeState(TYPE_CONTENT_RECOMMENDATION, true);
assertThat(mAssistants.getAllowedClassificationTypes()).asList()
- .containsExactlyElementsIn(List.of(TYPE_PROMOTION, TYPE_CONTENT_RECOMMENDATION));
+ .contains(TYPE_CONTENT_RECOMMENDATION);
}
@Test
@EnableFlags(android.service.notification.Flags.FLAG_NOTIFICATION_CLASSIFICATION)
public void testSetAssistantAdjustmentKeyTypeState_disallow() {
mAssistants.setAssistantAdjustmentKeyTypeState(TYPE_PROMOTION, false);
- assertThat(mAssistants.getAllowedClassificationTypes()).isEmpty();
+ assertThat(mAssistants.getAllowedClassificationTypes())
+ .asList().doesNotContain(TYPE_PROMOTION);
}
@Test
@EnableFlags(Flags.FLAG_NOTIFICATION_CLASSIFICATION_UI)
public void testDisallowAdjustmentKeyType_readWriteXml() throws Exception {
mAssistants.loadDefaultsFromConfig(true);
+ mAssistants.setAssistantAdjustmentKeyTypeState(TYPE_SOCIAL_MEDIA, false);
mAssistants.setAssistantAdjustmentKeyTypeState(TYPE_PROMOTION, false);
mAssistants.setAssistantAdjustmentKeyTypeState(TYPE_NEWS, true);
mAssistants.setAssistantAdjustmentKeyTypeState(TYPE_CONTENT_RECOMMENDATION, true);
@@ -745,7 +750,8 @@ public class NotificationAssistantsTest extends UiServiceTestCase {
writeXmlAndReload(USER_ALL);
assertThat(mAssistants.getAllowedClassificationTypes()).asList()
- .containsExactly(TYPE_PROMOTION);
+ .containsExactlyElementsIn(List.of(TYPE_PROMOTION, TYPE_NEWS, TYPE_SOCIAL_MEDIA,
+ TYPE_CONTENT_RECOMMENDATION));
}
@Test
@@ -757,18 +763,22 @@ public class NotificationAssistantsTest extends UiServiceTestCase {
String allowedPackage = "allowed.package";
assertThat(mAssistants.isAdjustmentAllowedForPackage(key, allowedPackage)).isTrue();
+ assertThat(mAssistants.getAdjustmentDeniedPackages(key)).isEmpty();
// Set type adjustment disallowed for this package
mAssistants.setAdjustmentSupportedForPackage(key, allowedPackage, false);
// Then the package is marked as denied
assertThat(mAssistants.isAdjustmentAllowedForPackage(key, allowedPackage)).isFalse();
+ assertThat(mAssistants.getAdjustmentDeniedPackages(key)).asList()
+ .containsExactly(allowedPackage);
// Set type adjustment allowed again
mAssistants.setAdjustmentSupportedForPackage(key, allowedPackage, true);
// Then the package is marked as allowed again
assertThat(mAssistants.isAdjustmentAllowedForPackage(key, allowedPackage)).isTrue();
+ assertThat(mAssistants.getAdjustmentDeniedPackages(key)).isEmpty();
}
@Test
@@ -789,6 +799,8 @@ public class NotificationAssistantsTest extends UiServiceTestCase {
assertThat(mAssistants.isAdjustmentAllowedForPackage(key, deniedPkg1)).isFalse();
assertThat(mAssistants.isAdjustmentAllowedForPackage(key, deniedPkg2)).isFalse();
assertThat(mAssistants.isAdjustmentAllowedForPackage(key, deniedPkg3)).isFalse();
+ assertThat(mAssistants.getAdjustmentDeniedPackages(key)).asList()
+ .containsExactlyElementsIn(List.of(deniedPkg1, deniedPkg2, deniedPkg3));
// And when we re-allow one of them,
mAssistants.setAdjustmentSupportedForPackage(key, deniedPkg2, true);
@@ -797,6 +809,8 @@ public class NotificationAssistantsTest extends UiServiceTestCase {
assertThat(mAssistants.isAdjustmentAllowedForPackage(key, deniedPkg1)).isFalse();
assertThat(mAssistants.isAdjustmentAllowedForPackage(key, deniedPkg2)).isTrue();
assertThat(mAssistants.isAdjustmentAllowedForPackage(key, deniedPkg3)).isFalse();
+ assertThat(mAssistants.getAdjustmentDeniedPackages(key)).asList()
+ .containsExactlyElementsIn(List.of(deniedPkg1, deniedPkg3));
}
@Test
@@ -860,8 +874,9 @@ public class NotificationAssistantsTest extends UiServiceTestCase {
mAssistants.new ManagedServiceInfo(null, mCn, userId, false, null, 35, 2345256);
// Ensure bundling is enabled
- mAssistants.setAdjustmentTypeSupportedState(info, KEY_TYPE, true);
+ mAssistants.setAdjustmentTypeSupportedState(info.userid, KEY_TYPE, true);
// Enable these specific bundle types
+ mAssistants.setAssistantAdjustmentKeyTypeState(TYPE_SOCIAL_MEDIA, false);
mAssistants.setAssistantAdjustmentKeyTypeState(TYPE_PROMOTION, false);
mAssistants.setAssistantAdjustmentKeyTypeState(TYPE_NEWS, true);
mAssistants.setAssistantAdjustmentKeyTypeState(TYPE_CONTENT_RECOMMENDATION, true);
@@ -894,7 +909,7 @@ public class NotificationAssistantsTest extends UiServiceTestCase {
.isEqualTo(NotificationProtoEnums.TYPE_CONTENT_RECOMMENDATION);
// Disable the top-level bundling setting
- mAssistants.setAdjustmentTypeSupportedState(info, KEY_TYPE, false);
+ mAssistants.setAdjustmentTypeSupportedState(info.userid, KEY_TYPE, false);
// Enable these specific bundle types
mAssistants.setAssistantAdjustmentKeyTypeState(TYPE_PROMOTION, true);
mAssistants.setAssistantAdjustmentKeyTypeState(TYPE_NEWS, false);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
index a9e8f4a0d965..cc68e4e73a4f 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -18,7 +18,6 @@ package com.android.server.notification;
import static android.app.AppOpsManager.MODE_ALLOWED;
import static android.app.AppOpsManager.MODE_DEFAULT;
import static android.app.AppOpsManager.OP_SYSTEM_ALERT_WINDOW;
-import static android.app.Flags.FLAG_MODES_UI;
import static android.app.Flags.FLAG_NOTIFICATION_CLASSIFICATION_UI;
import static android.app.Notification.VISIBILITY_PRIVATE;
import static android.app.Notification.VISIBILITY_SECRET;
@@ -259,10 +258,9 @@ public class PreferencesHelperTest extends UiServiceTestCase {
@Parameters(name = "{0}")
public static List<FlagsParameterization> getParams() {
return FlagsParameterization.allCombinationsOf(
- android.app.Flags.FLAG_API_RICH_ONGOING,
android.app.Flags.FLAG_UI_RICH_ONGOING,
- FLAG_NOTIFICATION_CLASSIFICATION, FLAG_NOTIFICATION_CLASSIFICATION_UI,
- FLAG_MODES_UI, android.app.Flags.FLAG_NM_BINDER_PERF_CACHE_CHANNELS);
+ FLAG_NOTIFICATION_CLASSIFICATION_UI,
+ android.app.Flags.FLAG_NM_BINDER_PERF_CACHE_CHANNELS);
}
public PreferencesHelperTest(FlagsParameterization flags) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java
index 70f57eb40385..3c74ad06a21f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java
@@ -19,6 +19,7 @@ package com.android.server.wm;
import static android.app.ActivityManager.START_DELIVERED_TO_TOP;
import static android.app.ActivityManager.START_TASK_TO_FRONT;
import static android.app.ITaskStackListener.FORCED_RESIZEABLE_REASON_SECONDARY_DISPLAY;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
@@ -44,20 +45,26 @@ import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.timeout;
+import android.annotation.NonNull;
import android.app.ActivityOptions;
import android.app.WaitResult;
+import android.app.WindowConfiguration;
import android.content.ComponentName;
import android.content.Intent;
import android.content.pm.ActivityInfo;
+import android.graphics.Rect;
import android.os.Binder;
import android.os.ConditionVariable;
import android.os.IBinder;
import android.os.RemoteException;
+import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.Presubmit;
import android.view.Display;
import androidx.test.filters.MediumTest;
+import com.android.window.flags.Flags;
+
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentMatchers;
@@ -424,4 +431,95 @@ public class ActivityTaskSupervisorTests extends WindowTestsBase {
assertThat(activity.mLaunchCookie).isNull();
verify(mAtm).moveTaskToFrontLocked(any(), eq(null), anyInt(), anyInt(), eq(safeOptions));
}
+
+ @Test
+ public void testOpaque_leafTask_occludingActivity_isOpaque() {
+ final ActivityRecord activity = new ActivityBuilder(mAtm).setCreateTask(true).build();
+ activity.setOccludesParent(true);
+ final TaskFragment tf = activity.getTaskFragment();
+
+ assertThat(mSupervisor.mOpaqueContainerHelper.isOpaque(tf)).isTrue();
+ }
+
+ @Test
+ public void testOpaque_leafTask_nonOccludingActivity_isTranslucent() {
+ final ActivityRecord activity = new ActivityBuilder(mAtm).setCreateTask(true).build();
+ activity.setOccludesParent(false);
+ final TaskFragment tf = activity.getTaskFragment();
+
+ assertThat(mSupervisor.mOpaqueContainerHelper.isOpaque(tf)).isFalse();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ public void testOpaque_rootTask_translucentFillingChild_isTranslucent() {
+ final Task rootTask = new TaskBuilder(mSupervisor).setOnTop(true).build();
+ createChildTaskFragment(/* parent */ rootTask,
+ WINDOWING_MODE_FREEFORM, /* opaque */ false, /* filling */ true);
+
+ assertThat(mSupervisor.mOpaqueContainerHelper.isOpaque(rootTask)).isFalse();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ public void testOpaque_rootTask_opaqueAndNotFillingChild_isTranslucent() {
+ final Task rootTask = new TaskBuilder(mSupervisor).setOnTop(true).build();
+ createChildTaskFragment(/* parent */ rootTask,
+ WINDOWING_MODE_FREEFORM, /* opaque */ true, /* filling */ false);
+
+ assertThat(mSupervisor.mOpaqueContainerHelper.isOpaque(rootTask)).isFalse();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ public void testOpaque_rootTask_opaqueAndFillingChild_isOpaque() {
+ final Task rootTask = new TaskBuilder(mSupervisor).setOnTop(true).build();
+ createChildTaskFragment(/* parent */ rootTask,
+ WINDOWING_MODE_FREEFORM, /* opaque */ true, /* filling */ true);
+
+ assertThat(mSupervisor.mOpaqueContainerHelper.isOpaque(rootTask)).isTrue();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ public void testOpaque_rootTask_nonFillingOpaqueAdjacentChildren_isOpaque() {
+ final Task rootTask = new TaskBuilder(mSupervisor).setOnTop(true).build();
+ final TaskFragment tf1 = createChildTaskFragment(/* parent */ rootTask,
+ WINDOWING_MODE_MULTI_WINDOW, /* opaque */ true, /* filling */ false);
+ final TaskFragment tf2 = createChildTaskFragment(/* parent */ rootTask,
+ WINDOWING_MODE_MULTI_WINDOW, /* opaque */ true, /* filling */ false);
+ tf1.setAdjacentTaskFragment(tf2);
+
+ assertThat(mSupervisor.mOpaqueContainerHelper.isOpaque(rootTask)).isTrue();
+ }
+
+ @Test
+ @EnableFlags({Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+ Flags.FLAG_ALLOW_MULTIPLE_ADJACENT_TASK_FRAGMENTS})
+ public void testOpaque_rootTask_nonFillingOpaqueAdjacentChildren_multipleAdjacent_isOpaque() {
+ final Task rootTask = new TaskBuilder(mSupervisor).setOnTop(true).build();
+ final TaskFragment tf1 = createChildTaskFragment(/* parent */ rootTask,
+ WINDOWING_MODE_MULTI_WINDOW, /* opaque */ true, /* filling */ false);
+ final TaskFragment tf2 = createChildTaskFragment(/* parent */ rootTask,
+ WINDOWING_MODE_MULTI_WINDOW, /* opaque */ true, /* filling */ false);
+ final TaskFragment tf3 = createChildTaskFragment(/* parent */ rootTask,
+ WINDOWING_MODE_MULTI_WINDOW, /* opaque */ true, /* filling */ false);
+ tf1.setAdjacentTaskFragments(new TaskFragment.AdjacentSet(tf1, tf2, tf3));
+
+ assertThat(mSupervisor.mOpaqueContainerHelper.isOpaque(rootTask)).isTrue();
+ }
+
+ @NonNull
+ private TaskFragment createChildTaskFragment(@NonNull Task parent,
+ @WindowConfiguration.WindowingMode int windowingMode,
+ boolean opaque,
+ boolean filling) {
+ final ActivityRecord activity = new ActivityBuilder(mAtm)
+ .setCreateTask(true).setParentTask(parent).build();
+ activity.setOccludesParent(opaque);
+ final TaskFragment tf = activity.getTaskFragment();
+ tf.setWindowingMode(windowingMode);
+ tf.setBounds(filling ? new Rect() : new Rect(100, 100, 200, 200));
+ return tf;
+ }
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppChangeTransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/AppChangeTransitionTests.java
deleted file mode 100644
index 169968c75fc5..000000000000
--- a/services/tests/wmtests/src/com/android/server/wm/AppChangeTransitionTests.java
+++ /dev/null
@@ -1,175 +0,0 @@
-/*
- * Copyright (C) 2019 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.server.wm;
-
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
-import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
-import static android.view.WindowManager.TRANSIT_OLD_TASK_CHANGE_WINDOWING_MODE;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-
-import android.os.IBinder;
-import android.platform.test.annotations.Presubmit;
-import android.view.Display;
-import android.view.IRemoteAnimationFinishedCallback;
-import android.view.IRemoteAnimationRunner;
-import android.view.RemoteAnimationAdapter;
-import android.view.RemoteAnimationDefinition;
-import android.view.RemoteAnimationTarget;
-import android.view.WindowManager;
-
-import androidx.test.filters.SmallTest;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-/**
- * Tests for change transitions
- *
- * Build/Install/Run:
- * atest WmTests:AppChangeTransitionTests
- */
-@SmallTest
-@Presubmit
-@RunWith(WindowTestRunner.class)
-public class AppChangeTransitionTests extends WindowTestsBase {
-
- private Task mTask;
- private ActivityRecord mActivity;
-
- public void setUpOnDisplay(DisplayContent dc) {
- mActivity = createActivityRecord(dc, WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_STANDARD);
- mTask = mActivity.getTask();
-
- // Set a remote animator with snapshot disabled. Snapshots don't work in wmtests.
- RemoteAnimationDefinition definition = new RemoteAnimationDefinition();
- RemoteAnimationAdapter adapter =
- new RemoteAnimationAdapter(new TestRemoteAnimationRunner(), 10, 1, false);
- definition.addRemoteAnimation(TRANSIT_OLD_TASK_CHANGE_WINDOWING_MODE, adapter);
- dc.registerRemoteAnimations(definition);
- }
-
- class TestRemoteAnimationRunner implements IRemoteAnimationRunner {
- @Override
- public void onAnimationStart(@WindowManager.TransitionOldType int transit,
- RemoteAnimationTarget[] apps,
- RemoteAnimationTarget[] wallpapers,
- RemoteAnimationTarget[] nonApps,
- IRemoteAnimationFinishedCallback finishedCallback) {
- for (RemoteAnimationTarget target : apps) {
- assertNotNull(target.startBounds);
- }
- try {
- finishedCallback.onAnimationFinished();
- } catch (Exception e) {
- throw new RuntimeException("Something went wrong");
- }
- }
-
- @Override
- public void onAnimationCancelled() {
- }
-
- @Override
- public IBinder asBinder() {
- return null;
- }
- }
-
- @Test
- public void testModeChangeRemoteAnimatorNoSnapshot() {
- // setup currently defaults to no snapshot.
- setUpOnDisplay(mDisplayContent);
-
- mTask.setWindowingMode(WINDOWING_MODE_FREEFORM);
- assertEquals(1, mDisplayContent.mChangingContainers.size());
-
- // Verify we are in a change transition, but without a snapshot.
- // Though, the test will actually have crashed by now if a snapshot is attempted.
- assertNull(mTask.mSurfaceFreezer.mSnapshot);
- assertTrue(mTask.isInChangeTransition());
-
- waitUntilHandlersIdle();
- mActivity.removeImmediately();
- }
-
- @Test
- public void testCancelPendingChangeOnRemove() {
- // setup currently defaults to no snapshot.
- setUpOnDisplay(mDisplayContent);
-
- mTask.setWindowingMode(WINDOWING_MODE_FREEFORM);
- assertEquals(1, mDisplayContent.mChangingContainers.size());
- assertTrue(mTask.isInChangeTransition());
-
- // Removing the app-token from the display should clean-up the
- // the change leash.
- mDisplayContent.removeAppToken(mActivity.token);
- assertEquals(0, mDisplayContent.mChangingContainers.size());
- assertFalse(mTask.isInChangeTransition());
-
- waitUntilHandlersIdle();
- mActivity.removeImmediately();
- }
-
- @Test
- public void testNoChangeOnOldDisplayWhenMoveDisplay() {
- mDisplayContent.getDefaultTaskDisplayArea().setWindowingMode(WINDOWING_MODE_FULLSCREEN);
- final DisplayContent dc1 = createNewDisplay(Display.STATE_ON);
- dc1.getDefaultTaskDisplayArea().setWindowingMode(WINDOWING_MODE_FREEFORM);
- setUpOnDisplay(dc1);
-
- assertEquals(WINDOWING_MODE_FREEFORM, mTask.getWindowingMode());
-
- // Reparenting to a display with different windowing mode may trigger
- // a change transition internally, but it should be cleaned-up once
- // the display change is complete.
- mTask.reparent(mDisplayContent.getDefaultTaskDisplayArea(), true);
-
- assertEquals(WINDOWING_MODE_FULLSCREEN, mTask.getWindowingMode());
-
- // Make sure the change transition is not the old display
- assertFalse(dc1.mChangingContainers.contains(mTask));
-
- waitUntilHandlersIdle();
- mActivity.removeImmediately();
- }
-
- @Test
- public void testCancelPendingChangeOnHide() {
- // setup currently defaults to no snapshot.
- setUpOnDisplay(mDisplayContent);
-
- mTask.setWindowingMode(WINDOWING_MODE_FREEFORM);
- assertEquals(1, mDisplayContent.mChangingContainers.size());
- assertTrue(mTask.isInChangeTransition());
-
- // Changing visibility should cancel the change transition and become closing
- mActivity.setVisibility(false);
- assertEquals(0, mDisplayContent.mChangingContainers.size());
- assertFalse(mTask.isInChangeTransition());
-
- waitUntilHandlersIdle();
- mActivity.removeImmediately();
- }
-}
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
index 03d904283e83..8553fbd30ab8 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
@@ -409,7 +409,6 @@ public class AppTransitionTests extends WindowTestsBase {
task.getBounds(taskBounds);
taskFragment.setBounds(0, 0, taskBounds.right / 2, taskBounds.bottom);
spyOn(taskFragment);
- mockSurfaceFreezerSnapshot(taskFragment.mSurfaceFreezer);
assertTrue(mDc.mChangingContainers.isEmpty());
assertFalse(mDc.mAppTransition.isTransitionSet());
@@ -422,7 +421,6 @@ public class AppTransitionTests extends WindowTestsBase {
verify(taskFragment).initializeChangeTransition(activity.getBounds(), activityLeash);
assertTrue(mDc.mChangingContainers.contains(taskFragment));
assertTrue(mDc.mAppTransition.containsTransitRequest(TRANSIT_CHANGE));
- assertEquals(startBounds, taskFragment.mSurfaceFreezer.mFreezeBounds);
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/CameraCompatFreeformPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/CameraCompatFreeformPolicyTests.java
index 716f86418bcb..560725241853 100644
--- a/services/tests/wmtests/src/com/android/server/wm/CameraCompatFreeformPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/CameraCompatFreeformPolicyTests.java
@@ -158,7 +158,7 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase {
public void testIsCameraRunningAndWindowingModeEligible_notFreeformWindowing_returnsFalse() {
configureActivity(SCREEN_ORIENTATION_PORTRAIT, WINDOWING_MODE_FULLSCREEN);
- mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+ onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
assertFalse(mCameraCompatFreeformPolicy.isCameraRunningAndWindowingModeEligible(mActivity));
}
@@ -169,7 +169,7 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase {
public void testIsCameraRunningAndWindowingModeEligible_optInFreeformCameraRunning_true() {
configureActivity(SCREEN_ORIENTATION_PORTRAIT);
- mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+ onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
assertTrue(mCameraCompatFreeformPolicy.isCameraRunningAndWindowingModeEligible(mActivity));
}
@@ -179,7 +179,7 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase {
public void testIsFreeformLetterboxingForCameraAllowed_overrideDisabled_returnsFalse() {
configureActivity(SCREEN_ORIENTATION_PORTRAIT);
- mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+ onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
assertFalse(mCameraCompatFreeformPolicy.isFreeformLetterboxingForCameraAllowed(mActivity));
}
@@ -199,7 +199,7 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase {
public void testIsFreeformLetterboxingForCameraAllowed_notFreeformWindowing_returnsFalse() {
configureActivity(SCREEN_ORIENTATION_PORTRAIT, WINDOWING_MODE_FULLSCREEN);
- mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+ onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
assertFalse(mCameraCompatFreeformPolicy.isFreeformLetterboxingForCameraAllowed(mActivity));
}
@@ -210,7 +210,7 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase {
public void testIsFreeformLetterboxingForCameraAllowed_optInFreeformCameraRunning_true() {
configureActivity(SCREEN_ORIENTATION_PORTRAIT);
- mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+ onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
assertTrue(mCameraCompatFreeformPolicy.isFreeformLetterboxingForCameraAllowed(mActivity));
}
@@ -222,7 +222,7 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase {
configureActivity(SCREEN_ORIENTATION_PORTRAIT, WINDOWING_MODE_FULLSCREEN);
doReturn(false).when(mActivity).inFreeformWindowingMode();
- mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+ onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
assertNotInCameraCompatMode();
}
@@ -250,7 +250,8 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase {
public void testCameraConnected_deviceInPortrait_portraitCameraCompatMode() throws Exception {
configureActivity(SCREEN_ORIENTATION_PORTRAIT);
setDisplayRotation(ROTATION_0);
- mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+
+ onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
assertInCameraCompatMode(CAMERA_COMPAT_FREEFORM_PORTRAIT_DEVICE_IN_PORTRAIT);
assertActivityRefreshRequested(/* refreshRequested */ false);
@@ -262,7 +263,8 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase {
public void testCameraConnected_deviceInLandscape_portraitCameraCompatMode() throws Exception {
configureActivity(SCREEN_ORIENTATION_PORTRAIT);
setDisplayRotation(ROTATION_270);
- mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+
+ onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
assertInCameraCompatMode(CAMERA_COMPAT_FREEFORM_PORTRAIT_DEVICE_IN_LANDSCAPE);
assertActivityRefreshRequested(/* refreshRequested */ false);
@@ -274,7 +276,8 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase {
public void testCameraConnected_deviceInPortrait_landscapeCameraCompatMode() throws Exception {
configureActivity(SCREEN_ORIENTATION_LANDSCAPE);
setDisplayRotation(ROTATION_0);
- mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+
+ onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
assertInCameraCompatMode(CAMERA_COMPAT_FREEFORM_LANDSCAPE_DEVICE_IN_PORTRAIT);
assertActivityRefreshRequested(/* refreshRequested */ false);
@@ -286,7 +289,8 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase {
public void testCameraConnected_deviceInLandscape_landscapeCameraCompatMode() throws Exception {
configureActivity(SCREEN_ORIENTATION_LANDSCAPE);
setDisplayRotation(ROTATION_270);
- mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+
+ onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
assertInCameraCompatMode(CAMERA_COMPAT_FREEFORM_LANDSCAPE_DEVICE_IN_LANDSCAPE);
assertActivityRefreshRequested(/* refreshRequested */ false);
@@ -299,12 +303,12 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase {
configureActivity(SCREEN_ORIENTATION_PORTRAIT);
setDisplayRotation(ROTATION_270);
- mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+ onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
callOnActivityConfigurationChanging(mActivity, /* letterboxNew= */ true,
/* lastLetterbox= */ false);
assertActivityRefreshRequested(/* refreshRequested */ true);
- mCameraAvailabilityCallback.onCameraClosed(CAMERA_ID_1);
- mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+ onCameraClosed(CAMERA_ID_1);
+ onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
// Activity is letterboxed from the previous configuration change.
callOnActivityConfigurationChanging(mActivity, /* letterboxNew= */ true,
/* lastLetterbox= */ true);
@@ -319,7 +323,7 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase {
public void testCameraOpenedForDifferentPackage_notInCameraCompatMode() {
configureActivity(SCREEN_ORIENTATION_PORTRAIT);
- mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_2);
+ onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_2);
assertNotInCameraCompatMode();
}
@@ -329,7 +333,7 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase {
public void testShouldApplyCameraCompatFreeformTreatment_overrideNotEnabled_returnsFalse() {
configureActivity(SCREEN_ORIENTATION_PORTRAIT);
- mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+ onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
assertFalse(mCameraCompatFreeformPolicy.isTreatmentEnabledForActivity(mActivity,
/* checkOrientation */ true));
@@ -341,7 +345,7 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase {
public void testShouldApplyCameraCompatFreeformTreatment_enabledByOverride_returnsTrue() {
configureActivity(SCREEN_ORIENTATION_PORTRAIT);
- mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+ onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
assertTrue(mActivity.info
.isChangeEnabled(OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT));
@@ -356,7 +360,7 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase {
configureActivity(SCREEN_ORIENTATION_PORTRAIT);
Configuration oldConfiguration = createConfiguration(/* letterbox= */ false);
Configuration newConfiguration = createConfiguration(/* letterbox= */ true);
- mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+ onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
assertTrue(mCameraCompatFreeformPolicy.shouldRefreshActivity(mActivity, newConfiguration,
oldConfiguration));
@@ -372,7 +376,7 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase {
oldConfiguration.windowConfiguration.setDisplayRotation(0);
newConfiguration.windowConfiguration.setDisplayRotation(90);
- mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+ onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
assertTrue(mCameraCompatFreeformPolicy.shouldRefreshActivity(mActivity, newConfiguration,
oldConfiguration));
@@ -388,7 +392,7 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase {
oldConfiguration.windowConfiguration.setDisplayRotation(0);
newConfiguration.windowConfiguration.setDisplayRotation(0);
- mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+ onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
assertFalse(mCameraCompatFreeformPolicy.shouldRefreshActivity(mActivity, newConfiguration,
oldConfiguration));
@@ -404,7 +408,7 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase {
doReturn(false).when(mActivity.mAppCompatController.getCameraOverrides())
.shouldRefreshActivityForCameraCompat();
- mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+ onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
callOnActivityConfigurationChanging(mActivity);
assertActivityRefreshRequested(/* refreshRequested */ false);
@@ -419,7 +423,7 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase {
configureActivity(SCREEN_ORIENTATION_PORTRAIT);
- mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+ onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
callOnActivityConfigurationChanging(mActivity);
assertActivityRefreshRequested(/* refreshRequested */ true, /* cycleThroughStop */ false);
@@ -434,7 +438,7 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase {
doReturn(true).when(mActivity.mAppCompatController.getCameraOverrides())
.shouldRefreshActivityViaPauseForCameraCompat();
- mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+ onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
callOnActivityConfigurationChanging(mActivity);
assertActivityRefreshRequested(/* refreshRequested */ true, /* cycleThroughStop */ false);
@@ -446,7 +450,7 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase {
public void testGetCameraCompatAspectRatio_activityNotInCameraCompat_returnsDefaultAspRatio() {
configureActivity(SCREEN_ORIENTATION_FULL_USER);
- mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+ onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
callOnActivityConfigurationChanging(mActivity);
assertEquals(MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO,
@@ -462,7 +466,7 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase {
final float configAspectRatio = 1.5f;
mWm.mAppCompatConfiguration.setCameraCompatAspectRatio(configAspectRatio);
- mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+ onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
callOnActivityConfigurationChanging(mActivity);
assertEquals(configAspectRatio,
@@ -480,7 +484,7 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase {
doReturn(true).when(mActivity.mAppCompatController.getCameraOverrides())
.isOverrideMinAspectRatioForCameraEnabled();
- mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+ onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
callOnActivityConfigurationChanging(mActivity);
assertEquals(MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO,
@@ -496,7 +500,7 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase {
configureActivity(SCREEN_ORIENTATION_PORTRAIT);
setDisplayRotation(ROTATION_270);
- mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+ onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
// This is a portrait rotation for a device with portrait natural orientation (most common,
// currently the only one supported).
@@ -511,7 +515,7 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase {
configureActivity(SCREEN_ORIENTATION_LANDSCAPE);
setDisplayRotation(ROTATION_0);
- mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+ onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
// This is a landscape rotation for a device with portrait natural orientation (most common,
// currently the only one supported).
@@ -616,6 +620,16 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase {
.inFreeformWindowingMode();
}
+ private void onCameraOpened(@NonNull String cameraId, @NonNull String packageName) {
+ mCameraAvailabilityCallback.onCameraOpened(cameraId, packageName);
+ waitHandlerIdle(mDisplayContent.mWmService.mH);
+ }
+
+ private void onCameraClosed(@NonNull String cameraId) {
+ mCameraAvailabilityCallback.onCameraClosed(cameraId);
+ waitHandlerIdle(mDisplayContent.mWmService.mH);
+ }
+
private void assertInCameraCompatMode(@CameraCompatTaskInfo.FreeformCameraCompatMode int mode) {
assertEquals(mode, mCameraCompatFreeformPolicy.getCameraCompatMode(mActivity));
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java b/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java
index cdb51fc1c645..fdde3b38f19f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java
@@ -1345,7 +1345,7 @@ public class DesktopModeLaunchParamsModifierTests extends
private void setupDesktopModeLaunchParamsModifier(boolean isDesktopModeSupported,
boolean enforceDeviceRestrictions) {
doReturn(isDesktopModeSupported)
- .when(() -> DesktopModeHelper.isDesktopModeSupported(any()));
+ .when(() -> DesktopModeHelper.isDeviceEligibleForDesktopMode(any()));
doReturn(enforceDeviceRestrictions)
.when(DesktopModeHelper::shouldEnforceDeviceRestrictions);
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
index 0d9772492e59..ee8d7308f6b3 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
@@ -152,7 +152,6 @@ public class TaskFragmentTest extends WindowTestsBase {
ACTIVITY_TYPE_STANDARD);
task.setBoundsUnchecked(new Rect(0, 0, 1000, 1000));
mTaskFragment = createTaskFragmentWithEmbeddedActivity(task, mOrganizer);
- mockSurfaceFreezerSnapshot(mTaskFragment.mSurfaceFreezer);
final Rect startBounds = new Rect(0, 0, 500, 1000);
final Rect endBounds = new Rect(500, 0, 1000, 1000);
mTaskFragment.setRelativeEmbeddedBounds(startBounds);
@@ -179,44 +178,6 @@ public class TaskFragmentTest extends WindowTestsBase {
}
@Test
- public void testStartChangeTransition_resetSurface() {
- final Task task = createTask(mDisplayContent, WINDOWING_MODE_MULTI_WINDOW,
- ACTIVITY_TYPE_STANDARD);
- task.setBoundsUnchecked(new Rect(0, 0, 1000, 1000));
- mTaskFragment = createTaskFragmentWithEmbeddedActivity(task, mOrganizer);
- doReturn(mTransaction).when(mTaskFragment).getSyncTransaction();
- doReturn(mTransaction).when(mTaskFragment).getPendingTransaction();
- mLeash = mTaskFragment.getSurfaceControl();
- mockSurfaceFreezerSnapshot(mTaskFragment.mSurfaceFreezer);
- final Rect startBounds = new Rect(0, 0, 1000, 1000);
- final Rect endBounds = new Rect(500, 500, 1000, 1000);
- mTaskFragment.setRelativeEmbeddedBounds(startBounds);
- mTaskFragment.recomputeConfiguration();
- doReturn(true).when(mTaskFragment).isVisible();
- doReturn(true).when(mTaskFragment).isVisibleRequested();
-
- clearInvocations(mTransaction);
- final Rect relStartBounds = new Rect(mTaskFragment.getRelativeEmbeddedBounds());
- mTaskFragment.deferOrganizedTaskFragmentSurfaceUpdate();
- mTaskFragment.setRelativeEmbeddedBounds(endBounds);
- mTaskFragment.recomputeConfiguration();
- assertTrue(mTaskFragment.shouldStartChangeTransition(startBounds, relStartBounds));
- mTaskFragment.initializeChangeTransition(startBounds);
- mTaskFragment.continueOrganizedTaskFragmentSurfaceUpdate();
-
- // Surface reset when prepare transition.
- verify(mTransaction).setPosition(mLeash, 0, 0);
- verify(mTransaction).setWindowCrop(mLeash, 0, 0);
-
- clearInvocations(mTransaction);
- mTaskFragment.mSurfaceFreezer.unfreeze(mTransaction);
-
- // Update surface after animation.
- verify(mTransaction).setPosition(mLeash, 500, 500);
- verify(mTransaction).setWindowCrop(mLeash, 500, 500);
- }
-
- @Test
public void testStartChangeTransition_doNotFreezeWhenOnlyMoved() {
final Rect startBounds = new Rect(0, 0, 1000, 1000);
final Rect endBounds = new Rect(startBounds);
@@ -235,7 +196,6 @@ public class TaskFragmentTest extends WindowTestsBase {
@Test
public void testNotOkToAnimate_doNotStartChangeTransition() {
- mockSurfaceFreezerSnapshot(mTaskFragment.mSurfaceFreezer);
final Rect startBounds = new Rect(0, 0, 1000, 1000);
final Rect endBounds = new Rect(500, 500, 1000, 1000);
mTaskFragment.setRelativeEmbeddedBounds(startBounds);
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
index cc447a18758c..001446550304 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
@@ -32,7 +32,6 @@ import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_OLD_TASK_CLOSE;
-import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_CHANGE;
import static android.view.WindowManager.TRANSIT_OLD_TASK_OPEN;
import static android.view.WindowManager.TRANSIT_OPEN;
import static android.window.DisplayAreaOrganizer.FEATURE_DEFAULT_TASK_CONTAINER;
@@ -64,7 +63,6 @@ import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
@@ -1014,30 +1012,6 @@ public class WindowContainerTests extends WindowTestsBase {
}
@Test
- public void testOnDisplayChanged_cleanupChanging() {
- final Task task = createTask(mDisplayContent);
- addLocalInsets(task);
- spyOn(task.mSurfaceFreezer);
- mDisplayContent.mChangingContainers.add(task);
-
- // Don't remove the changing transition of this window when it is still the old display.
- // This happens on display info changed.
- task.onDisplayChanged(mDisplayContent);
-
- assertTrue(task.mLocalInsetsSources.size() == 1);
- assertTrue(mDisplayContent.mChangingContainers.contains(task));
- verify(task.mSurfaceFreezer, never()).unfreeze(any());
-
- // Remove the changing transition of this window when it is moved or reparented from the old
- // display.
- final DisplayContent newDc = createNewDisplay();
- task.onDisplayChanged(newDc);
-
- assertFalse(mDisplayContent.mChangingContainers.contains(task));
- verify(task.mSurfaceFreezer).unfreeze(any());
- }
-
- @Test
public void testHandleCompleteDeferredRemoval() {
final DisplayContent displayContent = createNewDisplay();
// Do not reparent activity to default display when removing the display.
@@ -1290,157 +1264,17 @@ public class WindowContainerTests extends WindowTestsBase {
final WindowContainer container = new WindowContainer(mWm);
container.mSurfaceControl = mock(SurfaceControl.class);
final SurfaceAnimator surfaceAnimator = container.mSurfaceAnimator;
- final SurfaceFreezer surfaceFreezer = container.mSurfaceFreezer;
final SurfaceControl relativeParent = mock(SurfaceControl.class);
final SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class);
spyOn(container);
spyOn(surfaceAnimator);
- spyOn(surfaceFreezer);
doReturn(t).when(container).getSyncTransaction();
container.setLayer(t, 1);
container.setRelativeLayer(t, relativeParent, 2);
- // Set through surfaceAnimator if surfaceFreezer doesn't have leash.
verify(surfaceAnimator).setLayer(t, 1);
verify(surfaceAnimator).setRelativeLayer(t, relativeParent, 2);
- verify(surfaceFreezer, never()).setLayer(any(), anyInt());
- verify(surfaceFreezer, never()).setRelativeLayer(any(), any(), anyInt());
-
- clearInvocations(surfaceAnimator);
- clearInvocations(surfaceFreezer);
- doReturn(true).when(surfaceFreezer).hasLeash();
-
- container.setLayer(t, 1);
- container.setRelativeLayer(t, relativeParent, 2);
-
- // Set through surfaceFreezer if surfaceFreezer has leash.
- verify(surfaceFreezer).setLayer(t, 1);
- verify(surfaceFreezer).setRelativeLayer(t, relativeParent, 2);
- verify(surfaceAnimator, never()).setLayer(any(), anyInt());
- verify(surfaceAnimator, never()).setRelativeLayer(any(), any(), anyInt());
- }
-
- @Test
- public void testStartChangeTransitionWhenPreviousIsNotFinished() {
- final WindowContainer container = createTaskFragmentWithActivity(
- createTask(mDisplayContent));
- container.mSurfaceControl = mock(SurfaceControl.class);
- final SurfaceAnimator surfaceAnimator = container.mSurfaceAnimator;
- final SurfaceFreezer surfaceFreezer = container.mSurfaceFreezer;
- final SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class);
- spyOn(container);
- spyOn(surfaceAnimator);
- mockSurfaceFreezerSnapshot(surfaceFreezer);
- doReturn(t).when(container).getPendingTransaction();
- doReturn(t).when(container).getSyncTransaction();
-
- // Leash and snapshot created for change transition.
- container.initializeChangeTransition(new Rect(0, 0, 1000, 2000));
-
- assertNotNull(surfaceFreezer.mLeash);
- assertNotNull(surfaceFreezer.mSnapshot);
- assertEquals(surfaceFreezer.mLeash, container.getAnimationLeash());
-
- // Start animation: surfaceAnimator take over the leash and snapshot from surfaceFreezer.
- container.applyAnimationUnchecked(null /* lp */, true /* enter */,
- TRANSIT_OLD_TASK_FRAGMENT_CHANGE, false /* isVoiceInteraction */,
- null /* sources */);
-
- assertNull(surfaceFreezer.mLeash);
- assertNull(surfaceFreezer.mSnapshot);
- assertNotNull(surfaceAnimator.mLeash);
- assertNotNull(surfaceAnimator.mSnapshot);
- final SurfaceControl prevLeash = surfaceAnimator.mLeash;
- final SurfaceFreezer.Snapshot prevSnapshot = surfaceAnimator.mSnapshot;
-
- // Prepare another change transition.
- container.initializeChangeTransition(new Rect(0, 0, 1000, 2000));
-
- assertNotNull(surfaceFreezer.mLeash);
- assertNotNull(surfaceFreezer.mSnapshot);
- assertEquals(surfaceFreezer.mLeash, container.getAnimationLeash());
- assertNotEquals(prevLeash, container.getAnimationLeash());
-
- // Start another animation before the previous one is finished, it should reset the previous
- // one, but not change the current one.
- container.applyAnimationUnchecked(null /* lp */, true /* enter */,
- TRANSIT_OLD_TASK_FRAGMENT_CHANGE, false /* isVoiceInteraction */,
- null /* sources */);
-
- verify(container, never()).onAnimationLeashLost(any());
- verify(surfaceFreezer, never()).unfreeze(any());
- assertNotNull(surfaceAnimator.mLeash);
- assertNotNull(surfaceAnimator.mSnapshot);
- assertEquals(surfaceAnimator.mLeash, container.getAnimationLeash());
- assertNotEquals(prevLeash, surfaceAnimator.mLeash);
- assertNotEquals(prevSnapshot, surfaceAnimator.mSnapshot);
-
- // Clean up after animation finished.
- surfaceAnimator.mInnerAnimationFinishedCallback.onAnimationFinished(
- ANIMATION_TYPE_APP_TRANSITION, surfaceAnimator.getAnimation());
-
- verify(container).onAnimationLeashLost(any());
- assertNull(surfaceAnimator.mLeash);
- assertNull(surfaceAnimator.mSnapshot);
- }
-
- @Test
- public void testUnfreezeWindow_removeWindowFromChanging() {
- final WindowContainer container = createTaskFragmentWithActivity(
- createTask(mDisplayContent));
- mockSurfaceFreezerSnapshot(container.mSurfaceFreezer);
- final SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class);
-
- container.initializeChangeTransition(new Rect(0, 0, 1000, 2000));
-
- assertTrue(mDisplayContent.mChangingContainers.contains(container));
-
- container.mSurfaceFreezer.unfreeze(t);
-
- assertFalse(mDisplayContent.mChangingContainers.contains(container));
- }
-
- @Test
- public void testFailToTaskSnapshot_unfreezeWindow() {
- final WindowContainer container = createTaskFragmentWithActivity(
- createTask(mDisplayContent));
- final SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class);
- spyOn(container.mSurfaceFreezer);
-
- container.initializeChangeTransition(new Rect(0, 0, 1000, 2000));
-
- verify(container.mSurfaceFreezer).freeze(any(), any(), any(), any());
- verify(container.mSurfaceFreezer).unfreeze(any());
- assertTrue(mDisplayContent.mChangingContainers.isEmpty());
- }
-
- @Test
- public void testRemoveUnstartedFreezeSurfaceWhenFreezeAgain() {
- final WindowContainer container = createTaskFragmentWithActivity(
- createTask(mDisplayContent));
- container.mSurfaceControl = mock(SurfaceControl.class);
- final SurfaceFreezer surfaceFreezer = container.mSurfaceFreezer;
- mockSurfaceFreezerSnapshot(surfaceFreezer);
- final SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class);
- spyOn(container);
- doReturn(t).when(container).getPendingTransaction();
- doReturn(t).when(container).getSyncTransaction();
-
- // Leash and snapshot created for change transition.
- container.initializeChangeTransition(new Rect(0, 0, 1000, 2000));
-
- assertNotNull(surfaceFreezer.mLeash);
- assertNotNull(surfaceFreezer.mSnapshot);
-
- final SurfaceControl prevLeash = surfaceFreezer.mLeash;
- final SurfaceFreezer.Snapshot prevSnapshot = surfaceFreezer.mSnapshot;
- spyOn(prevSnapshot);
-
- container.initializeChangeTransition(new Rect(0, 0, 1500, 2500));
-
- verify(t).remove(prevLeash);
- verify(prevSnapshot).destroy(t);
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index 2c390c504e9f..b16f5283d532 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -78,7 +78,6 @@ import android.content.pm.ApplicationInfo;
import android.content.res.Configuration;
import android.graphics.Insets;
import android.graphics.Rect;
-import android.hardware.HardwareBuffer;
import android.hardware.display.DisplayManager;
import android.os.Binder;
import android.os.Build;
@@ -113,7 +112,6 @@ import android.window.ActivityWindowInfo;
import android.window.ClientWindowFrames;
import android.window.ITaskFragmentOrganizer;
import android.window.ITransitionPlayer;
-import android.window.ScreenCapture;
import android.window.StartingWindowInfo;
import android.window.StartingWindowRemovalInfo;
import android.window.TaskFragmentOrganizer;
@@ -1112,21 +1110,6 @@ public class WindowTestsBase extends SystemServiceTestsBase {
displayContent -> displayContent.mMinSizeOfResizeableTaskDp = 1);
}
- /** Mocks the behavior of taking a snapshot. */
- void mockSurfaceFreezerSnapshot(SurfaceFreezer surfaceFreezer) {
- final ScreenCapture.ScreenshotHardwareBuffer screenshotBuffer =
- mock(ScreenCapture.ScreenshotHardwareBuffer.class);
- final HardwareBuffer hardwareBuffer = mock(HardwareBuffer.class);
- spyOn(surfaceFreezer);
- doReturn(screenshotBuffer).when(surfaceFreezer)
- .createSnapshotBufferInner(any(), any());
- doReturn(null).when(surfaceFreezer)
- .createFromHardwareBufferInner(any());
- doReturn(hardwareBuffer).when(screenshotBuffer).getHardwareBuffer();
- doReturn(100).when(hardwareBuffer).getWidth();
- doReturn(100).when(hardwareBuffer).getHeight();
- }
-
static ComponentName getUniqueComponentName() {
return getUniqueComponentName(DEFAULT_COMPONENT_PACKAGE_NAME);
}
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
index 7e7a53148603..cd1c48554be9 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
@@ -2747,8 +2747,7 @@ public class VoiceInteractionManagerService extends SystemService {
isManagedProfileVisible = true;
}
}
- final ScreenCapture.ScreenshotHardwareBuffer shb =
- mWmInternal.takeAssistScreenshot(/* windowTypesToExclude= */ Set.of());
+ final ScreenCapture.ScreenshotHardwareBuffer shb = mWmInternal.takeAssistScreenshot();
final Bitmap bm = shb != null ? shb.asBitmap() : null;
// Now that everything is fetched, putting it in the launchIntent.
if (bm != null) {
diff --git a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt
index 9e9d014c622d..55d6fd9b4a73 100644
--- a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt
+++ b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt
@@ -87,14 +87,18 @@ open class DesktopModeAppHelper(private val innerHelper: IStandardAppHelper) :
wmHelper: WindowManagerStateHelper,
device: UiDevice,
motionEventHelper: MotionEventHelper = MotionEventHelper(getInstrumentation(), TOUCH),
+ shouldUseDragToDesktop: Boolean = false,
) {
innerHelper.launchViaIntent(wmHelper)
- if (!isInDesktopWindowingMode(wmHelper)) {
+ if (isInDesktopWindowingMode(wmHelper)) return
+ if (shouldUseDragToDesktop) {
enterDesktopModeWithDrag(
wmHelper = wmHelper,
device = device,
motionEventHelper = motionEventHelper
)
+ } else {
+ enterDesktopModeFromAppHandleMenu(wmHelper, device)
}
}
diff --git a/tests/utils/testutils/java/android/os/test/TestLooper.java b/tests/utils/testutils/java/android/os/test/TestLooper.java
index 61fa7b542bc0..83d22d923c78 100644
--- a/tests/utils/testutils/java/android/os/test/TestLooper.java
+++ b/tests/utils/testutils/java/android/os/test/TestLooper.java
@@ -18,24 +18,18 @@ package android.os.test;
import static org.junit.Assert.assertTrue;
-import android.os.Build;
import android.os.Handler;
import android.os.HandlerExecutor;
import android.os.Looper;
import android.os.Message;
import android.os.MessageQueue;
import android.os.SystemClock;
-import android.os.TestLooperManager;
import android.util.Log;
-import androidx.test.InstrumentationRegistry;
-
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
-import java.util.ArrayDeque;
-import java.util.Queue;
import java.util.concurrent.Executor;
/**
@@ -50,9 +44,7 @@ import java.util.concurrent.Executor;
* The Robolectric class also allows advancing time.
*/
public class TestLooper {
- private final Looper mLooper;
- private final TestLooperManager mTestLooperManager;
- private final Clock mClock;
+ protected final Looper mLooper;
private static final Constructor<Looper> LOOPER_CONSTRUCTOR;
private static final Field THREAD_LOCAL_LOOPER_FIELD;
@@ -62,14 +54,9 @@ public class TestLooper {
private static final Method MESSAGE_MARK_IN_USE_METHOD;
private static final String TAG = "TestLooper";
- private AutoDispatchThread mAutoDispatchThread;
+ private final Clock mClock;
- /**
- * Baklava introduces new {@link TestLooperManager} APIs that we can use instead of reflection.
- */
- private static boolean isAtLeastBaklava() {
- return Build.VERSION.SDK_INT >= Build.VERSION_CODES.BAKLAVA;
- }
+ private AutoDispatchThread mAutoDispatchThread;
static {
try {
@@ -77,22 +64,14 @@ public class TestLooper {
LOOPER_CONSTRUCTOR.setAccessible(true);
THREAD_LOCAL_LOOPER_FIELD = Looper.class.getDeclaredField("sThreadLocal");
THREAD_LOCAL_LOOPER_FIELD.setAccessible(true);
-
- if (isAtLeastBaklava()) {
- MESSAGE_QUEUE_MESSAGES_FIELD = null;
- MESSAGE_NEXT_FIELD = null;
- MESSAGE_WHEN_FIELD = null;
- MESSAGE_MARK_IN_USE_METHOD = null;
- } else {
- MESSAGE_QUEUE_MESSAGES_FIELD = MessageQueue.class.getDeclaredField("mMessages");
- MESSAGE_QUEUE_MESSAGES_FIELD.setAccessible(true);
- MESSAGE_NEXT_FIELD = Message.class.getDeclaredField("next");
- MESSAGE_NEXT_FIELD.setAccessible(true);
- MESSAGE_WHEN_FIELD = Message.class.getDeclaredField("when");
- MESSAGE_WHEN_FIELD.setAccessible(true);
- MESSAGE_MARK_IN_USE_METHOD = Message.class.getDeclaredMethod("markInUse");
- MESSAGE_MARK_IN_USE_METHOD.setAccessible(true);
- }
+ MESSAGE_QUEUE_MESSAGES_FIELD = MessageQueue.class.getDeclaredField("mMessages");
+ MESSAGE_QUEUE_MESSAGES_FIELD.setAccessible(true);
+ MESSAGE_NEXT_FIELD = Message.class.getDeclaredField("next");
+ MESSAGE_NEXT_FIELD.setAccessible(true);
+ MESSAGE_WHEN_FIELD = Message.class.getDeclaredField("when");
+ MESSAGE_WHEN_FIELD.setAccessible(true);
+ MESSAGE_MARK_IN_USE_METHOD = Message.class.getDeclaredMethod("markInUse");
+ MESSAGE_MARK_IN_USE_METHOD.setAccessible(true);
} catch (NoSuchFieldException | NoSuchMethodException e) {
throw new RuntimeException("Failed to initialize TestLooper", e);
}
@@ -127,13 +106,6 @@ public class TestLooper {
throw new RuntimeException("Reflection error constructing or accessing looper", e);
}
- if (isAtLeastBaklava()) {
- mTestLooperManager =
- InstrumentationRegistry.getInstrumentation().acquireLooperManager(mLooper);
- } else {
- mTestLooperManager = null;
- }
-
mClock = clock;
}
@@ -145,72 +117,19 @@ public class TestLooper {
return new HandlerExecutor(new Handler(getLooper()));
}
- private Message getMessageLinkedListLegacy() {
+ private Message getMessageLinkedList() {
try {
MessageQueue queue = mLooper.getQueue();
return (Message) MESSAGE_QUEUE_MESSAGES_FIELD.get(queue);
} catch (IllegalAccessException e) {
throw new RuntimeException("Access failed in TestLooper: get - MessageQueue.mMessages",
- e);
+ e);
}
}
public void moveTimeForward(long milliSeconds) {
- if (isAtLeastBaklava()) {
- moveTimeForwardBaklava(milliSeconds);
- } else {
- moveTimeForwardLegacy(milliSeconds);
- }
- }
-
- private void moveTimeForwardBaklava(long milliSeconds) {
- // Drain all Messages from the queue.
- Queue<Message> messages = new ArrayDeque<>();
- while (true) {
- Message message = mTestLooperManager.poll();
- if (message == null) {
- break;
- }
-
- // Adjust the Message's delivery time.
- long newWhen = message.when - milliSeconds;
- if (newWhen < 0) {
- newWhen = 0;
- }
- message.when = newWhen;
- messages.add(message);
- }
-
- // Repost all Messages back to the queuewith a new time.
- while (true) {
- Message message = messages.poll();
- if (message == null) {
- break;
- }
-
- Runnable callback = message.getCallback();
- Handler handler = message.getTarget();
- long when = message.getWhen();
-
- // The Message cannot be re-enqueued because it is marked in use.
- // Make a copy of the Message and recycle the original.
- // This resets {@link Message#isInUse()} but retains all other content.
- {
- Message newMessage = Message.obtain();
- newMessage.copyFrom(message);
- newMessage.setCallback(callback);
- mTestLooperManager.recycle(message);
- message = newMessage;
- }
-
- // Send the Message back to its Handler to be re-enqueued.
- handler.sendMessageAtTime(message, when);
- }
- }
-
- private void moveTimeForwardLegacy(long milliSeconds) {
try {
- Message msg = getMessageLinkedListLegacy();
+ Message msg = getMessageLinkedList();
while (msg != null) {
long updatedWhen = msg.getWhen() - milliSeconds;
if (updatedWhen < 0) {
@@ -228,12 +147,12 @@ public class TestLooper {
return mClock.uptimeMillis();
}
- private Message messageQueueNextLegacy() {
+ private Message messageQueueNext() {
try {
long now = currentTime();
Message prevMsg = null;
- Message msg = getMessageLinkedListLegacy();
+ Message msg = getMessageLinkedList();
if (msg != null && msg.getTarget() == null) {
// Stalled by a barrier. Find the next asynchronous message in
// the queue.
@@ -266,46 +185,18 @@ public class TestLooper {
/**
* @return true if there are pending messages in the message queue
*/
- public boolean isIdle() {
- if (isAtLeastBaklava()) {
- return isIdleBaklava();
- } else {
- return isIdleLegacy();
- }
- }
+ public synchronized boolean isIdle() {
+ Message messageList = getMessageLinkedList();
- private boolean isIdleBaklava() {
- Long when = mTestLooperManager.peekWhen();
- return when != null && currentTime() >= when;
- }
-
- private synchronized boolean isIdleLegacy() {
- Message messageList = getMessageLinkedListLegacy();
return messageList != null && currentTime() >= messageList.getWhen();
}
/**
* @return the next message in the Looper's message queue or null if there is none
*/
- public Message nextMessage() {
- if (isAtLeastBaklava()) {
- return nextMessageBaklava();
- } else {
- return nextMessageLegacy();
- }
- }
-
- private Message nextMessageBaklava() {
+ public synchronized Message nextMessage() {
if (isIdle()) {
- return mTestLooperManager.poll();
- } else {
- return null;
- }
- }
-
- private synchronized Message nextMessageLegacy() {
- if (isIdle()) {
- return messageQueueNextLegacy();
+ return messageQueueNext();
} else {
return null;
}
@@ -315,26 +206,9 @@ public class TestLooper {
* Dispatch the next message in the queue
* Asserts that there is a message in the queue
*/
- public void dispatchNext() {
- if (isAtLeastBaklava()) {
- dispatchNextBaklava();
- } else {
- dispatchNextLegacy();
- }
- }
-
- private void dispatchNextBaklava() {
- assertTrue(isIdle());
- Message msg = mTestLooperManager.poll();
- if (msg == null) {
- return;
- }
- msg.getTarget().dispatchMessage(msg);
- }
-
- private synchronized void dispatchNextLegacy() {
+ public synchronized void dispatchNext() {
assertTrue(isIdle());
- Message msg = messageQueueNextLegacy();
+ Message msg = messageQueueNext();
if (msg == null) {
return;
}
diff --git a/tools/localedata/extract_icu_data.py b/tools/localedata/extract_icu_data.py
index ec531275af1c..899cd7f9ce5e 100755
--- a/tools/localedata/extract_icu_data.py
+++ b/tools/localedata/extract_icu_data.py
@@ -180,7 +180,14 @@ def pack_script_to_uint32(script):
def dump_representative_locales(representative_locales):
"""Dump the set of representative locales."""
- print()
+ print('''
+/*
+ * TODO: Consider turning the below switch statement into binary search
+ * to save the disk space when the table is larger in the future.
+ * Disassembled code shows that the jump table emitted by clang can be
+ * 4x larger than the data in disk size, but it depends on the optimization option.
+ * However, a switch statement will benefit from the future of compiler improvement.
+ */''')
print('bool isLocaleRepresentative(uint32_t language_and_region, const char* script) {')
print(' const uint64_t packed_locale =')
print(' ((static_cast<uint64_t>(language_and_region)) << 32u) |')