summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--boot/boot-image-profile-extra.txt21
-rw-r--r--core/java/android/app/INotificationManager.aidl1
-rw-r--r--core/java/android/app/IUiModeManager.aidl54
-rw-r--r--core/java/android/app/IUiModeManagerCallback.aidl1
-rw-r--r--core/java/android/app/Notification.java12
-rw-r--r--core/java/android/app/UiModeManager.java137
-rw-r--r--core/java/android/app/contextualsearch/flags.aconfig7
-rw-r--r--core/java/android/hardware/biometrics/BiometricConstants.java20
-rw-r--r--core/java/android/os/ChildZygoteProcess.java3
-rw-r--r--core/java/android/os/CombinedMessageQueue/MessageQueue.java62
-rw-r--r--core/java/android/os/Looper.java8
-rw-r--r--core/java/android/os/Message.java16
-rw-r--r--core/java/android/os/PerfettoTrace.java41
-rw-r--r--core/java/android/os/PerfettoTrackEventExtra.java20
-rw-r--r--core/java/android/os/TestLooperManager.java28
-rw-r--r--core/java/android/os/Trace.java1
-rw-r--r--core/java/android/os/UserManager.java46
-rw-r--r--core/java/android/view/ViewRootImpl.java69
-rw-r--r--core/java/android/view/inputmethod/ImeTracker.java1
-rw-r--r--core/java/android/widget/RemoteViews.java10
-rw-r--r--core/java/android/window/DesktopExperienceFlags.java12
-rw-r--r--core/java/android/window/DesktopModeFlags.java94
-rw-r--r--core/java/android/window/flags/lse_desktop_experience.aconfig14
-rw-r--r--core/proto/android/server/windowmanagerservice.proto1
-rw-r--r--core/proto/android/widget/remoteviews.proto1
-rw-r--r--core/res/res/drawable/accessibility_autoclick_button_rounded_background.xml20
-rw-r--r--core/res/res/drawable/accessibility_autoclick_left_click.xml25
-rw-r--r--core/res/res/drawable/accessibility_autoclick_pause.xml26
-rw-r--r--core/res/res/drawable/accessibility_autoclick_position.xml26
-rw-r--r--core/res/res/drawable/accessibility_autoclick_type_panel_rounded_background.xml20
-rw-r--r--core/res/res/layout/accessibility_autoclick_type_panel.xml78
-rw-r--r--core/res/res/layout/notification_2025_template_compact_heads_up_base.xml89
-rw-r--r--core/res/res/layout/notification_2025_template_compact_heads_up_messaging.xml116
-rw-r--r--core/res/res/values/config.xml3
-rw-r--r--core/res/res/values/dimens.xml12
-rw-r--r--core/res/res/values/strings.xml10
-rw-r--r--core/res/res/values/styles.xml16
-rw-r--r--core/res/res/values/symbols.xml21
-rw-r--r--core/tests/coretests/src/android/os/PerfettoTraceTest.java87
-rw-r--r--core/tests/coretests/src/android/view/ViewRootImplTest.java115
-rw-r--r--core/tests/overlaytests/host/src/com/android/server/om/hosttest/InstallOverlayTests.java11
-rw-r--r--libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleControllerBubbleBarTest.kt1
-rw-r--r--libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubblePositionerTest.kt1
-rw-r--r--libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelperTest.kt2
-rw-r--r--libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewTest.kt2
-rw-r--r--libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleExpandedViewPinControllerTest.kt2
-rw-r--r--libs/WindowManager/Shell/shared/Android.bp1
-rw-r--r--libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DeviceConfig.kt (renamed from libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/DeviceConfig.kt)2
-rw-r--r--libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java7
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java1
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java1
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java1
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipDesktopState.java159
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/OWNERS3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java29
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java21
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt30
-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/pip2/phone/PipScheduler.java78
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTaskListener.java3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java57
-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.java7
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt1
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleViewHolder.kt2
-rw-r--r--libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/DesktopModeFlickerScenarios.kt23
-rw-r--r--libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/OpenAppWithExternalDisplayConnectedTest.kt27
-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/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/OpenAppWithExternalDisplayConnected.kt103
-rw-r--r--libs/WindowManager/Shell/tests/e2e/utils/src/com/android/wm/shell/ExtendedDisplaySettingsSession.kt34
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/pip/PipDesktopStateTest.java204
-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/DesktopModeVisualIndicatorTest.kt56
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt49
-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/pip2/phone/PipSchedulerTest.java21
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipTaskListenerTest.java19
-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.kt26
-rw-r--r--libs/androidfw/Android.bp1
-rw-r--r--libs/androidfw/LocaleDataLookup.cpp7
-rw-r--r--libs/androidfw/TypeWrappers.cpp17
-rw-r--r--libs/androidfw/include/androidfw/TypeWrappers.h13
-rw-r--r--libs/androidfw/tests/LocaleDataLookup_bench.cpp57
-rw-r--r--libs/androidfw/tests/TypeWrappers_test.cpp12
-rw-r--r--media/java/android/media/AudioManager.java17
-rw-r--r--media/java/android/media/IAudioService.aidl4
-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/SystemUI/Android.bp1
-rw-r--r--packages/SystemUI/aconfig/systemui.aconfig7
-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/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/CommunalSettingsRepositoryImplTest.kt63
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt33
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/complication/DreamClockTimeComplicationTest.java7
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/dump/DumpHandlerTest.kt4
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorParameterizedTest.kt1
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorTest.kt1
-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/notification/promoted/PromotedNotificationContentExtractorImplTest.kt46
-rw-r--r--packages/SystemUI/src/com/android/systemui/BootCompleteCacheImpl.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewController.kt2
-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/CommunalSettingsRepository.kt30
-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/controls/management/ControlsListingControllerImpl.kt2
-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/KeyguardQuickAffordanceRepository.kt2
-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/media/controls/domain/pipeline/LegacyMediaDataManagerImpl.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/domain/resume/MediaResumeListener.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt14
-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/privacy/PrivacyConfig.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt2
-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/KeyguardlessSceneContainerFrameworkModule.kt26
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt17
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImpl.kt62
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/transition/ScrimShadeTransitionController.kt2
-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/layout/StatusBarContentInsetsProvider.kt2
-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/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/ClockEventControllerTest.kt1
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/accessibility/data/repository/AccessibilityQsShortcutsRepositoryImplForDeviceTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingButtonViewModelTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingDeviceItemActionInteractorTest.kt2
-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.kt88
-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/graphics/ImageLoaderContentProviderTest.kt1
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/graphics/ImageLoaderTest.kt1
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorSceneContainerTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/lifecycle/RepeatWhenAttachedTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImplTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImplTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManagerTest.kt3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfigTest.kt3
-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/src/com/android/systemui/reardisplay/RearDisplayCoreStartableTest.kt1
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/SplitShadeLockScreenOverScrollerTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/IconManagerTest.kt3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerWithScenesTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/airplane/ui/viewmodel/AirplaneModeViewModelImplTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/model/SystemUiCarrierConfigTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/CarrierConfigRepositoryImplTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionTelephonySmokeTests.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileViewTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImplTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepositoryImplTest.kt2
-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/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--ravenwood/texts/ravenwood-annotation-allowed-classes.txt4
-rw-r--r--services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickController.java10
-rw-r--r--services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickTypePanel.java84
-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/StorageManagerService.java1
-rw-r--r--services/core/java/com/android/server/UiModeManagerService.java131
-rw-r--r--services/core/java/com/android/server/ZramMaintenance.java100
-rw-r--r--services/core/java/com/android/server/am/SettingsToPropertiesMapper.java2
-rw-r--r--services/core/java/com/android/server/appop/AppOpsService.java22
-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/input/KeyGestureController.java2
-rw-r--r--services/core/java/com/android/server/notification/NotificationManagerService.java84
-rw-r--r--services/core/java/com/android/server/om/OverlayManagerService.java156
-rw-r--r--services/core/java/com/android/server/om/OverlayManagerServiceImpl.java11
-rw-r--r--services/core/java/com/android/server/om/OverlayManagerSettings.java26
-rw-r--r--services/core/java/com/android/server/om/OverlayManagerShellCommand.java22
-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/power/stats/WakelockStatsFrameworkEvents.java12
-rw-r--r--services/core/java/com/android/server/wallpaper/WallpaperManagerService.java112
-rw-r--r--services/core/java/com/android/server/wm/ActivityRecord.java3
-rw-r--r--services/core/java/com/android/server/wm/ActivityTaskSupervisor.java5
-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/ImeInsetsSourceProvider.java16
-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/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java148
-rw-r--r--services/tests/powerstatstests/src/com/android/server/power/stats/WakelockStatsFrameworkEventsTest.java18
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickControllerTest.java121
-rw-r--r--services/tests/servicestests/src/com/android/server/audio/VolumeHelperTest.java52
-rw-r--r--services/tests/servicestests/src/com/android/server/zram/ZramMaintenanceTest.kt155
-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.java10
-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/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/Input/src/com/android/server/input/KeyGestureControllerTests.kt4
-rw-r--r--tests/utils/testutils/java/android/os/test/TestLooper.java170
-rwxr-xr-xtools/localedata/extract_icu_data.py9
293 files changed, 6704 insertions, 2705 deletions
diff --git a/boot/boot-image-profile-extra.txt b/boot/boot-image-profile-extra.txt
index fd51f9cbcee1..ced0d176f174 100644
--- a/boot/boot-image-profile-extra.txt
+++ b/boot/boot-image-profile-extra.txt
@@ -45,3 +45,24 @@ HSPLandroid/os/MessageQueue$OnFileDescriptorEventListener;->*
HSPLandroid/os/MessageQueue$StackNodeType;->*
HSPLandroid/os/MessageQueue$StateNode;->*
HSPLandroid/os/MessageQueue$TimedParkStateNode;->*
+HSPLandroid/os/PerfettoTrace$Category;->*
+HSPLandroid/os/PerfettoTrace;->*
+HSPLandroid/os/PerfettoTrackEventExtra;->*
+HSPLandroid/os/PerfettoTrackEventExtra$BuilderImpl;->*
+HSPLandroid/os/PerfettoTrackEventExtra$NoOpBuilder;->*
+HSPLandroid/os/PerfettoTrackEventExtra$ArgBool;->*
+HSPLandroid/os/PerfettoTrackEventExtra$ArgInt64;->*
+HSPLandroid/os/PerfettoTrackEventExtra$ArgDouble;->*
+HSPLandroid/os/PerfettoTrackEventExtra$ArgString;->*
+HSPLandroid/os/PerfettoTrackEventExtra$CounterInt64;->*
+HSPLandroid/os/PerfettoTrackEventExtra$CounterDouble;->*
+HSPLandroid/os/PerfettoTrackEventExtra$CounterTrack;->*
+HSPLandroid/os/PerfettoTrackEventExtra$NamedTrack;->*
+HSPLandroid/os/PerfettoTrackEventExtra$Flow;->*
+HSPLandroid/os/PerfettoTrackEventExtra$Proto;->*
+HSPLandroid/os/PerfettoTrackEventExtra$FieldInt64;->*
+HSPLandroid/os/PerfettoTrackEventExtra$FieldDouble;->*
+HSPLandroid/os/PerfettoTrackEventExtra$FieldString;->*
+HSPLandroid/os/PerfettoTrackEventExtra$FieldNested;->*
+HSPLandroid/os/PerfettoTrackEventExtra$Pool;->*
+HSPLandroid/os/PerfettoTrackEventExtra$RingBuffer;->*
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/IUiModeManager.aidl b/core/java/android/app/IUiModeManager.aidl
index 3b83024d536b..6d4ad7518546 100644
--- a/core/java/android/app/IUiModeManager.aidl
+++ b/core/java/android/app/IUiModeManager.aidl
@@ -51,7 +51,7 @@ interface IUiModeManager {
* Return the current running mode.
*/
int getCurrentModeType();
-
+
/**
* Sets the night mode.
* <p>
@@ -161,61 +161,69 @@ interface IUiModeManager {
boolean setNightModeActivated(boolean active);
/**
- * Returns custom start clock time
- */
+ * Returns custom start clock time
+ */
long getCustomNightModeStart();
/**
- * Sets custom start clock time
- */
+ * Sets custom start clock time
+ */
void setCustomNightModeStart(long time);
/**
- * Returns custom end clock time
- */
+ * Returns custom end clock time
+ */
long getCustomNightModeEnd();
/**
- * Sets custom end clock time
- */
+ * Sets custom end clock time
+ */
void setCustomNightModeEnd(long time);
/**
- * Sets projection state for the caller for the given projection type.
- */
+ * Sets projection state for the caller for the given projection type.
+ */
boolean requestProjection(in IBinder binder, int projectionType, String callingPackage);
/**
- * Releases projection state for the caller for the given projection type.
- */
+ * Releases projection state for the caller for the given projection type.
+ */
boolean releaseProjection(int projectionType, String callingPackage);
/**
- * Registers a listener for changes to projection state.
- */
+ * Registers a listener for changes to projection state.
+ */
@EnforcePermission("READ_PROJECTION_STATE")
void addOnProjectionStateChangedListener(in IOnProjectionStateChangedListener listener, int projectionType);
/**
- * Unregisters a listener for changes to projection state.
- */
+ * Unregisters a listener for changes to projection state.
+ */
@EnforcePermission("READ_PROJECTION_STATE")
void removeOnProjectionStateChangedListener(in IOnProjectionStateChangedListener listener);
/**
- * Returns packages that have currently set the given projection type.
- */
+ * Returns packages that have currently set the given projection type.
+ */
@EnforcePermission("READ_PROJECTION_STATE")
List<String> getProjectingPackages(int projectionType);
/**
- * Returns currently set projection types.
- */
+ * Returns currently set projection types.
+ */
@EnforcePermission("READ_PROJECTION_STATE")
int getActiveProjectionTypes();
/**
- * Returns the contrast for the current user
- */
+ * Returns the contrast for the current user.
+ */
float getContrast();
+
+
+ /**
+ * Returns the force invert state.
+ *
+ * @hide
+ */
+ int getForceInvertState();
}
diff --git a/core/java/android/app/IUiModeManagerCallback.aidl b/core/java/android/app/IUiModeManagerCallback.aidl
index 47c18a8bd3cb..550779d14bab 100644
--- a/core/java/android/app/IUiModeManagerCallback.aidl
+++ b/core/java/android/app/IUiModeManagerCallback.aidl
@@ -24,4 +24,5 @@ package android.app;
*/
oneway interface IUiModeManagerCallback {
void notifyContrastChanged(float contrast);
+ void notifyForceInvertStateChanged(int forceInvertState);
}
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index c6de9a37f168..a6338fbd6b77 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -7599,11 +7599,19 @@ public class Notification implements Parcelable
}
private int getCompactHeadsUpBaseLayoutResource() {
- return R.layout.notification_template_material_compact_heads_up_base;
+ if (Flags.notificationsRedesignTemplates()) {
+ return R.layout.notification_2025_template_compact_heads_up_base;
+ } else {
+ return R.layout.notification_template_material_compact_heads_up_base;
+ }
}
private int getMessagingCompactHeadsUpLayoutResource() {
- return R.layout.notification_template_material_messaging_compact_heads_up;
+ if (Flags.notificationsRedesignTemplates()) {
+ return R.layout.notification_2025_template_compact_heads_up_messaging;
+ } else {
+ return R.layout.notification_template_material_messaging_compact_heads_up;
+ }
}
private int getExpandedBaseLayoutResource() {
diff --git a/core/java/android/app/UiModeManager.java b/core/java/android/app/UiModeManager.java
index 57549847f05d..f6c789d51aee 100644
--- a/core/java/android/app/UiModeManager.java
+++ b/core/java/android/app/UiModeManager.java
@@ -121,6 +121,24 @@ public class UiModeManager {
}
/**
+ * Listener for the force invert state. To listen for changes to
+ * the force invert state on the device, implement this interface and
+ * register it with the system by calling {@link #addForceInvertStateChangeListener}.
+ *
+ * @hide
+ */
+ public interface ForceInvertStateChangeListener {
+
+ /**
+ * Called when the force invert state changes.
+ *
+ * @param forceInvertState The force invert state in {@link #getForceInvertState}
+ * @hide
+ */
+ void onForceInvertStateChanged(@ForceInvertType int forceInvertState);
+ }
+
+ /**
* Broadcast sent when the device's UI has switched to car mode, either
* by being placed in a car dock or explicit action of the user. After
* sending the broadcast, the system will start the intent
@@ -374,6 +392,36 @@ public class UiModeManager {
@SystemApi
public static final int MODE_NIGHT_CUSTOM_TYPE_BEDTIME = 1;
+ /** @hide */
+ @IntDef(prefix = {"Force_Invert_Type_"}, value = {
+ FORCE_INVERT_TYPE_OFF,
+ FORCE_INVERT_TYPE_DARK,
+ FORCE_INVERT_TYPE_LIGHT,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ForceInvertType {}
+
+ /**
+ * Constant for {@link #getForceInvertState()}: Do not force invert.
+ *
+ * @hide
+ */
+ public static final int FORCE_INVERT_TYPE_OFF = 0;
+
+ /**
+ * Constant for {@link #getForceInvertState()}: Force apps to be dark.
+ *
+ * @hide
+ */
+ public static final int FORCE_INVERT_TYPE_DARK = 1;
+
+ /**
+ * Constant for {@link #getForceInvertState()}: Force apps to be light.
+ *
+ * @hide
+ */
+ public static final int FORCE_INVERT_TYPE_LIGHT = 2;
+
private static Globals sGlobals;
/**
@@ -405,6 +453,8 @@ public class UiModeManager {
private final IUiModeManager mService;
private final Object mGlobalsLock = new Object();
+ @ForceInvertType
+ private int mForceInvertState = FORCE_INVERT_TYPE_OFF;
private float mContrast = ContrastUtils.CONTRAST_DEFAULT_VALUE;
/**
@@ -414,16 +464,63 @@ public class UiModeManager {
private final ArrayMap<ContrastChangeListener, Executor>
mContrastChangeListeners = new ArrayMap<>();
+ private final ArrayMap<ForceInvertStateChangeListener, Executor>
+ mForceInvertStateChangeListeners = new ArrayMap<>();
+
Globals(IUiModeManager service) {
mService = service;
try {
mService.addCallback(this);
mContrast = mService.getContrast();
+ mForceInvertState = mService.getForceInvertState();
} catch (RemoteException e) {
Log.e(TAG, "Setup failed: UiModeManagerService is dead", e);
}
}
+ @ForceInvertType
+ private int getForceInvertState() {
+ synchronized (mGlobalsLock) {
+ return mForceInvertState;
+ }
+ }
+
+ private void addForceInvertStateChangeListener(ForceInvertStateChangeListener listener,
+ Executor executor) {
+ synchronized (mGlobalsLock) {
+ mForceInvertStateChangeListeners.put(listener, executor);
+ }
+ }
+
+ private void removeForceInvertStateChangeListener(ForceInvertStateChangeListener listener) {
+ synchronized (mGlobalsLock) {
+ mForceInvertStateChangeListeners.remove(listener);
+ }
+ }
+
+ @Override
+ public void notifyForceInvertStateChanged(@ForceInvertType int forceInvertState) {
+ final Map<ForceInvertStateChangeListener, Executor> listeners = new ArrayMap<>();
+ synchronized (mGlobalsLock) {
+ // if value changed in the settings, update the cached value and notify listeners
+ if (mForceInvertState == forceInvertState) {
+ return;
+ }
+
+ mForceInvertState = forceInvertState;
+ listeners.putAll(mForceInvertStateChangeListeners);
+ }
+
+ listeners.forEach((listener, executor) -> {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ executor.execute(() -> listener.onForceInvertStateChanged(forceInvertState));
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ });
+ }
+
private float getContrast() {
synchronized (mGlobalsLock) {
return mContrast;
@@ -1453,4 +1550,44 @@ public class UiModeManager {
Objects.requireNonNull(listener);
sGlobals.removeContrastChangeListener(listener);
}
+
+ /**
+ * Returns the force invert state for the user.
+ *
+ * @hide
+ */
+ @ForceInvertType
+ public int getForceInvertState() {
+ return sGlobals.getForceInvertState();
+ }
+
+ /**
+ * Registers a {@link ForceInvertStateChangeListener} for the current user.
+ *
+ * @param executor The executor on which the listener should be called back.
+ * @param listener The listener.
+ *
+ * @hide
+ */
+ public void addForceInvertStateChangeListener(
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull ForceInvertStateChangeListener listener) {
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(listener);
+ sGlobals.addForceInvertStateChangeListener(listener, executor);
+ }
+
+ /**
+ * Unregisters a {@link ForceInvertStateChangeListener} for the current user.
+ * If the listener was not registered, does nothing and returns.
+ *
+ * @param listener The listener to unregister.
+ *
+ * @hide
+ */
+ public void removeForceInvertStateChangeListener(
+ @NonNull ForceInvertStateChangeListener listener) {
+ Objects.requireNonNull(listener);
+ sGlobals.removeForceInvertStateChangeListener(listener);
+ }
}
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/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/os/ChildZygoteProcess.java b/core/java/android/os/ChildZygoteProcess.java
index d8f825a2ee60..84fd0ca4cce9 100644
--- a/core/java/android/os/ChildZygoteProcess.java
+++ b/core/java/android/os/ChildZygoteProcess.java
@@ -67,12 +67,15 @@ public class ChildZygoteProcess extends ZygoteProcess {
if (mDead.get()) {
return true;
}
+ StrictMode.ThreadPolicy oldStrictModeThreadPolicy = StrictMode.allowThreadDiskReads();
try {
if (Os.stat("/proc/" + mPid).st_uid == mUid) {
return false;
}
} catch (ErrnoException e) {
// Do nothing, it's dead.
+ } finally {
+ StrictMode.setThreadPolicy(oldStrictModeThreadPolicy);
}
mDead.set(true);
return true;
diff --git a/core/java/android/os/CombinedMessageQueue/MessageQueue.java b/core/java/android/os/CombinedMessageQueue/MessageQueue.java
index 877f130a8b5a..349a2f0a181d 100644
--- a/core/java/android/os/CombinedMessageQueue/MessageQueue.java
+++ b/core/java/android/os/CombinedMessageQueue/MessageQueue.java
@@ -24,8 +24,6 @@ import android.annotation.TestApi;
import android.app.ActivityThread;
import android.app.Instrumentation;
import android.compat.annotation.UnsupportedAppUsage;
-import android.os.Process;
-import android.os.UserHandle;
import android.ravenwood.annotation.RavenwoodKeepWholeClass;
import android.ravenwood.annotation.RavenwoodRedirect;
import android.ravenwood.annotation.RavenwoodRedirectionClass;
@@ -95,6 +93,13 @@ public final class MessageQueue {
private int mAsyncMessageCount;
/**
+ * @hide
+ */
+ private final AtomicLong mMessageCount = new AtomicLong();
+ private final Thread mThread;
+ private final long mTid;
+
+ /**
* Select between two implementations of message queue. The legacy implementation is used
* by default as it provides maximum compatibility with applications and tests that
* reach into MessageQueue via the mMessages field. The concurrent implemmentation is used for
@@ -128,6 +133,8 @@ public final class MessageQueue {
mUseConcurrent = sIsProcessAllowedToUseConcurrent && !isInstrumenting();
mQuitAllowed = quitAllowed;
mPtr = nativeInit();
+ mThread = Thread.currentThread();
+ mTid = Process.myTid();
}
private static void initIsProcessAllowedToUseConcurrent() {
@@ -218,6 +225,32 @@ public final class MessageQueue {
}
}
+ private void decAndTraceMessageCount() {
+ mMessageCount.decrementAndGet();
+ traceMessageCount();
+ }
+
+ private void incAndTraceMessageCount(Message msg, long when) {
+ mMessageCount.incrementAndGet();
+ msg.mSendingThreadName = Thread.currentThread().getName();
+ msg.mEventId.set(PerfettoTrace.getFlowId());
+
+ traceMessageCount();
+ PerfettoTrace.instant(PerfettoTrace.MQ_CATEGORY, "message_queue_send")
+ .addFlow(msg.mEventId.get())
+ .addArg("receiving_thread", mThread.getName())
+ .addArg("delay", when - SystemClock.uptimeMillis())
+ .addArg("what", msg.what)
+ .emit();
+ }
+
+ /** @hide */
+ private void traceMessageCount() {
+ PerfettoTrace.counter(PerfettoTrace.MQ_CATEGORY, mMessageCount.get())
+ .usingThreadCounterTrack(mTid, mThread.getName())
+ .emit();
+ }
+
// Disposes of the underlying message queue.
// Must only be called on the looper thread or the finalizer.
private void dispose() {
@@ -800,6 +833,7 @@ public final class MessageQueue {
Message msg = nextMessage(false, false);
if (msg != null) {
msg.markInUse();
+ decAndTraceMessageCount();
return msg;
}
@@ -909,6 +943,7 @@ public final class MessageQueue {
if (msg.isAsynchronous()) {
mAsyncMessageCount--;
}
+ decAndTraceMessageCount();
if (TRACE) {
Trace.setCounter("MQ.Delivered", mMessagesDelivered.incrementAndGet());
}
@@ -1075,6 +1110,7 @@ public final class MessageQueue {
msg.markInUse();
msg.arg1 = token;
+ incAndTraceMessageCount(msg, when);
if (!enqueueMessageUnchecked(msg, when)) {
Log.wtf(TAG_C, "Unexpected error while adding sync barrier!");
@@ -1090,6 +1126,7 @@ public final class MessageQueue {
msg.markInUse();
msg.when = when;
msg.arg1 = token;
+ incAndTraceMessageCount(msg, when);
if (Flags.messageQueueTailTracking() && mLast != null && mLast.when <= when) {
/* Message goes to tail of list */
@@ -1196,6 +1233,7 @@ public final class MessageQueue {
needWake = mMessages == null || mMessages.target != null;
}
p.recycleUnchecked();
+ decAndTraceMessageCount();
// If the loop is quitting then it is already awake.
// We can assume mPtr != 0 when mQuitting is false.
@@ -1252,6 +1290,8 @@ public final class MessageQueue {
msg.markInUse();
msg.when = when;
+ incAndTraceMessageCount(msg, when);
+
Message p = mMessages;
boolean needWake;
if (p == null || when == 0 || when < p.when) {
@@ -1391,6 +1431,7 @@ public final class MessageQueue {
if (msg.isAsynchronous()) {
mAsyncMessageCount--;
}
+ decAndTraceMessageCount();
if (TRACE) {
Trace.setCounter("MQ.Delivered", mMessagesDelivered.incrementAndGet());
}
@@ -1642,6 +1683,7 @@ public final class MessageQueue {
mAsyncMessageCount--;
}
p.recycleUnchecked();
+ decAndTraceMessageCount();
p = n;
}
@@ -1660,6 +1702,7 @@ public final class MessageQueue {
mAsyncMessageCount--;
}
n.recycleUnchecked();
+ decAndTraceMessageCount();
p.next = nn;
if (p.next == null) {
mLast = p;
@@ -1718,6 +1761,7 @@ public final class MessageQueue {
mAsyncMessageCount--;
}
n.recycleUnchecked();
+ decAndTraceMessageCount();
p.next = nn;
if (p.next == null) {
mLast = p;
@@ -1759,6 +1803,7 @@ public final class MessageQueue {
mAsyncMessageCount--;
}
p.recycleUnchecked();
+ decAndTraceMessageCount();
p = n;
}
@@ -1777,6 +1822,7 @@ public final class MessageQueue {
mAsyncMessageCount--;
}
n.recycleUnchecked();
+ decAndTraceMessageCount();
p.next = nn;
if (p.next == null) {
mLast = p;
@@ -1832,6 +1878,7 @@ public final class MessageQueue {
mAsyncMessageCount--;
}
p.recycleUnchecked();
+ decAndTraceMessageCount();
p = n;
}
@@ -1850,6 +1897,7 @@ public final class MessageQueue {
mAsyncMessageCount--;
}
n.recycleUnchecked();
+ decAndTraceMessageCount();
p.next = nn;
if (p.next == null) {
mLast = p;
@@ -1904,6 +1952,7 @@ public final class MessageQueue {
mAsyncMessageCount--;
}
p.recycleUnchecked();
+ decAndTraceMessageCount();
p = n;
}
@@ -1921,6 +1970,7 @@ public final class MessageQueue {
mAsyncMessageCount--;
}
n.recycleUnchecked();
+ decAndTraceMessageCount();
p.next = nn;
if (p.next == null) {
mLast = p;
@@ -1976,6 +2026,7 @@ public final class MessageQueue {
mAsyncMessageCount--;
}
p.recycleUnchecked();
+ decAndTraceMessageCount();
p = n;
}
@@ -1993,6 +2044,7 @@ public final class MessageQueue {
mAsyncMessageCount--;
}
n.recycleUnchecked();
+ decAndTraceMessageCount();
p.next = nn;
if (p.next == null) {
mLast = p;
@@ -2027,6 +2079,8 @@ public final class MessageQueue {
mMessages = null;
mLast = null;
mAsyncMessageCount = 0;
+ mMessageCount.set(0);
+ traceMessageCount();
}
private void removeAllFutureMessagesLocked() {
@@ -2057,6 +2111,7 @@ public final class MessageQueue {
mAsyncMessageCount--;
}
p.recycleUnchecked();
+ decAndTraceMessageCount();
} while (n != null);
}
}
@@ -2701,6 +2756,7 @@ public final class MessageQueue {
MessageNode node = new MessageNode(msg, seq);
msg.when = when;
msg.markInUse();
+ incAndTraceMessageCount(msg, when);
if (DEBUG) {
Log.d(TAG_C, "Insert message what: " + msg.what + " when: " + msg.when + " seq: "
@@ -2828,6 +2884,7 @@ public final class MessageQueue {
if (removeMatches) {
if (p.removeFromStack()) {
p.mMessage.recycleUnchecked();
+ decAndTraceMessageCount();
if (mMessageCounts.incrementCancelled()) {
nativeWake(mPtr);
}
@@ -2870,6 +2927,7 @@ public final class MessageQueue {
found = true;
if (queue.remove(msg)) {
msg.mMessage.recycleUnchecked();
+ decAndTraceMessageCount();
}
} else {
return true;
diff --git a/core/java/android/os/Looper.java b/core/java/android/os/Looper.java
index 2fe4871e08dd..d16e4473d55f 100644
--- a/core/java/android/os/Looper.java
+++ b/core/java/android/os/Looper.java
@@ -199,7 +199,12 @@ public final class Looper {
return false;
}
- // This must be in a local variable, in case a UI event sets the logger
+ PerfettoTrace.begin(PerfettoTrace.MQ_CATEGORY, "message_queue_receive")
+ .addArg("sending_thread", msg.mSendingThreadName)
+ .addTerminatingFlow(msg.mEventId.get())
+ .emit();
+
+ // This must be in a local variabe, in case a UI event sets the logger
final Printer logging = me.mLogging;
if (logging != null) {
logging.println(">>>>> Dispatching to " + msg.target + " "
@@ -289,6 +294,7 @@ public final class Looper {
+ msg.callback + " what=" + msg.what);
}
+ PerfettoTrace.end(PerfettoTrace.MQ_CATEGORY).emit();
msg.recycleUnchecked();
return true;
diff --git a/core/java/android/os/Message.java b/core/java/android/os/Message.java
index 702fdc2bbaa6..b22d1774d967 100644
--- a/core/java/android/os/Message.java
+++ b/core/java/android/os/Message.java
@@ -23,6 +23,8 @@ import android.util.proto.ProtoOutputStream;
import com.android.internal.annotations.VisibleForTesting;
+import java.util.concurrent.atomic.AtomicInteger;
+
/**
*
* Defines a message containing a description and arbitrary data object that can be
@@ -37,6 +39,13 @@ import com.android.internal.annotations.VisibleForTesting;
@android.ravenwood.annotation.RavenwoodKeepWholeClass
public final class Message implements Parcelable {
/**
+ * For tracing
+ *
+ * @hide Only for use within the system server.
+ */
+ public final AtomicInteger mEventId = new AtomicInteger();
+
+ /**
* User-defined message code so that the recipient can identify
* what this message is about. Each {@link Handler} has its own name-space
* for message codes, so you do not need to worry about yours conflicting
@@ -101,6 +110,13 @@ public final class Message implements Parcelable {
*/
public int workSourceUid = UID_NONE;
+ /**
+ * Sending thread
+ *
+ * @hide
+ */
+ public String mSendingThreadName;
+
/** If set message is in use.
* This flag is set when the message is enqueued and remains set while it
* is delivered and afterwards when it is recycled. The flag is only cleared
diff --git a/core/java/android/os/PerfettoTrace.java b/core/java/android/os/PerfettoTrace.java
index 68f1570154ff..741d542ecb3b 100644
--- a/core/java/android/os/PerfettoTrace.java
+++ b/core/java/android/os/PerfettoTrace.java
@@ -16,6 +16,8 @@
package android.os;
+import com.android.internal.ravenwood.RavenwoodEnvironment;
+
import dalvik.annotation.optimization.CriticalNative;
import dalvik.annotation.optimization.FastNative;
@@ -32,6 +34,7 @@ import java.util.concurrent.atomic.AtomicInteger;
*
* @hide
*/
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
public final class PerfettoTrace {
private static final String TAG = "PerfettoTrace";
@@ -48,11 +51,14 @@ public final class PerfettoTrace {
*/
private static final AtomicInteger sFlowEventId = new AtomicInteger();
+ public static final PerfettoTrace.Category MQ_CATEGORY = new PerfettoTrace.Category("mq");
+
/**
* Perfetto category a trace event belongs to.
* Registering a category is not sufficient to capture events within the category, it must
* also be enabled in the trace config.
*/
+ @android.ravenwood.annotation.RavenwoodKeepWholeClass
public static final class Category implements PerfettoTrackEventExtra.PerfettoPointer {
private static final NativeAllocationRegistry sRegistry =
NativeAllocationRegistry.createMalloced(
@@ -97,12 +103,16 @@ public final class PerfettoTrace {
mSeverity = severity;
mPtr = native_init(name, tag, severity);
mExtraPtr = native_get_extra_ptr(mPtr);
- sRegistry.registerNativeAllocation(this, mPtr);
+ if (!RavenwoodEnvironment.getInstance().isRunningOnRavenwood()) {
+ sRegistry.registerNativeAllocation(this, mPtr);
+ }
}
@FastNative
+ @android.ravenwood.annotation.RavenwoodReplace
private static native long native_init(String name, String tag, String severity);
@CriticalNative
+ @android.ravenwood.annotation.RavenwoodReplace
private static native long native_delete();
@CriticalNative
private static native void native_register(long ptr);
@@ -111,8 +121,24 @@ public final class PerfettoTrace {
@CriticalNative
private static native boolean native_is_enabled(long ptr);
@CriticalNative
+ @android.ravenwood.annotation.RavenwoodReplace
private static native long native_get_extra_ptr(long ptr);
+ private static long native_init$ravenwood(String name, String tag, String severity) {
+ // Tracing currently completely disabled under Ravenwood
+ return 0;
+ }
+
+ private static long native_delete$ravenwood() {
+ // Tracing currently completely disabled under Ravenwood
+ return 0;
+ }
+
+ private static long native_get_extra_ptr$ravenwood(long ptr) {
+ // Tracing currently completely disabled under Ravenwood
+ return 0;
+ }
+
/**
* Register the category.
*/
@@ -134,10 +160,16 @@ public final class PerfettoTrace {
/**
* Whether the category is enabled or not.
*/
+ @android.ravenwood.annotation.RavenwoodReplace
public boolean isEnabled() {
return IS_FLAG_ENABLED && native_is_enabled(mPtr);
}
+ public boolean isEnabled$ravenwood() {
+ // Tracing currently completely disabled under Ravenwood
+ return false;
+ }
+
/**
* Whether the category is registered or not.
*/
@@ -340,4 +372,11 @@ public final class PerfettoTrace {
public static void register(boolean isBackendInProcess) {
native_register(isBackendInProcess);
}
+
+ /**
+ * Registers categories with Perfetto.
+ */
+ public static void registerCategories() {
+ MQ_CATEGORY.register();
+ }
}
diff --git a/core/java/android/os/PerfettoTrackEventExtra.java b/core/java/android/os/PerfettoTrackEventExtra.java
index e034fb3726e3..72b909e64c70 100644
--- a/core/java/android/os/PerfettoTrackEventExtra.java
+++ b/core/java/android/os/PerfettoTrackEventExtra.java
@@ -16,6 +16,8 @@
package android.os;
+import com.android.internal.ravenwood.RavenwoodEnvironment;
+
import dalvik.annotation.optimization.CriticalNative;
import dalvik.annotation.optimization.FastNative;
@@ -29,6 +31,7 @@ import java.util.function.Supplier;
*
* @hide
*/
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
public final class PerfettoTrackEventExtra {
private static final int DEFAULT_EXTRA_CACHE_SIZE = 5;
private static final Builder NO_OP_BUILDER = new NoOpBuilder();
@@ -305,6 +308,7 @@ public final class PerfettoTrackEventExtra {
Builder endNested();
}
+ @android.ravenwood.annotation.RavenwoodKeepWholeClass
public static final class NoOpBuilder implements Builder {
@Override
public void emit() {}
@@ -759,7 +763,9 @@ public final class PerfettoTrackEventExtra {
private PerfettoTrackEventExtra() {
mPtr = native_init();
- sRegistry.registerNativeAllocation(this, mPtr);
+ if (!RavenwoodEnvironment.getInstance().isRunningOnRavenwood()) {
+ sRegistry.registerNativeAllocation(this, mPtr);
+ }
}
/**
@@ -1342,8 +1348,10 @@ public final class PerfettoTrackEventExtra {
}
@CriticalNative
+ @android.ravenwood.annotation.RavenwoodReplace
private static native long native_init();
@CriticalNative
+ @android.ravenwood.annotation.RavenwoodReplace
private static native long native_delete();
@CriticalNative
private static native void native_add_arg(long ptr, long extraPtr);
@@ -1351,4 +1359,14 @@ public final class PerfettoTrackEventExtra {
private static native void native_clear_args(long ptr);
@FastNative
private static native void native_emit(int type, long tag, String name, long ptr);
+
+ private static long native_init$ravenwood() {
+ // Tracing currently completely disabled under Ravenwood
+ return 0;
+ }
+
+ private static long native_delete$ravenwood() {
+ // Tracing currently completely disabled under Ravenwood
+ return 0;
+ }
}
diff --git a/core/java/android/os/TestLooperManager.java b/core/java/android/os/TestLooperManager.java
index ddfa3799706e..82bdb2280c35 100644
--- a/core/java/android/os/TestLooperManager.java
+++ b/core/java/android/os/TestLooperManager.java
@@ -18,6 +18,7 @@ import android.annotation.FlaggedApi;
import android.annotation.Nullable;
import android.util.ArraySet;
+import java.util.concurrent.CountDownLatch;
import java.util.concurrent.LinkedBlockingQueue;
/**
@@ -38,10 +39,13 @@ public class TestLooperManager {
private final MessageQueue mQueue;
private final Looper mLooper;
private final LinkedBlockingQueue<MessageExecution> mExecuteQueue = new LinkedBlockingQueue<>();
+ private final boolean mLooperIsMyLooper;
+
+ // When this latch is zero, it's guaranteed that the LooperHolder Message
+ // is not in the underlying queue.
+ private final CountDownLatch mLooperHolderLatch = new CountDownLatch(1);
private boolean mReleased;
- private boolean mLooperBlocked;
- private final boolean mLooperIsMyLooper;
/**
* @hide
@@ -59,6 +63,8 @@ public class TestLooperManager {
if (!mLooperIsMyLooper) {
// Post a message that will keep the looper blocked as long as we are dispatching.
new Handler(looper).post(new LooperHolder());
+ } else {
+ mLooperHolderLatch.countDown();
}
}
@@ -222,23 +228,16 @@ public class TestLooperManager {
* is not in the underlying queue.
*/
private void waitForLooperHolder() {
- while (!mLooperIsMyLooper && !mLooperBlocked) {
- synchronized (this) {
- try {
- wait();
- } catch (InterruptedException e) {
- }
- }
+ try {
+ mLooperHolderLatch.await();
+ } catch (InterruptedException e) {
}
}
private class LooperHolder implements Runnable {
@Override
public void run() {
- synchronized (TestLooperManager.this) {
- mLooperBlocked = true;
- TestLooperManager.this.notify();
- }
+ mLooperHolderLatch.countDown();
while (!mReleased) {
try {
final MessageExecution take = mExecuteQueue.take();
@@ -248,9 +247,6 @@ public class TestLooperManager {
} catch (InterruptedException e) {
}
}
- synchronized (TestLooperManager.this) {
- mLooperBlocked = false;
- }
}
private void processMessage(MessageExecution mex) {
diff --git a/core/java/android/os/Trace.java b/core/java/android/os/Trace.java
index 09e6a45dc294..bf4c4d16fe19 100644
--- a/core/java/android/os/Trace.java
+++ b/core/java/android/os/Trace.java
@@ -544,5 +544,6 @@ public final class Trace {
*/
public static void registerWithPerfetto() {
PerfettoTrace.register(false /* isBackendInProcess */);
+ PerfettoTrace.registerCategories();
}
}
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/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index bf34069f9445..23c43f56f2bc 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -149,6 +149,8 @@ import android.annotation.UiContext;
import android.app.ActivityManager;
import android.app.ActivityThread;
import android.app.ResourcesManager;
+import android.app.UiModeManager;
+import android.app.UiModeManager.ForceInvertStateChangeListener;
import android.app.WindowConfiguration;
import android.app.compat.CompatChanges;
import android.app.servertransaction.WindowStateTransactionItem;
@@ -164,7 +166,6 @@ import android.content.res.CompatibilityInfo;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.content.res.TypedArray;
-import android.database.ContentObserver;
import android.graphics.BLASTBufferQueue;
import android.graphics.Canvas;
import android.graphics.Color;
@@ -210,7 +211,6 @@ import android.os.SystemProperties;
import android.os.Trace;
import android.os.UserHandle;
import android.os.Vibrator;
-import android.provider.Settings;
import android.sysprop.DisplayProperties;
import android.sysprop.ViewProperties;
import android.text.TextUtils;
@@ -463,10 +463,8 @@ public final class ViewRootImpl implements ViewParent,
private CompatOnBackInvokedCallback mCompatOnBackInvokedCallback;
@Nullable
- private ContentObserver mForceInvertObserver;
+ private ForceInvertStateChangeListener mForceInvertStateChangeListener;
- private static final int INVALID_VALUE = Integer.MIN_VALUE;
- private int mForceInvertEnabled = INVALID_VALUE;
/**
* Callback for notifying about global configuration changes.
*/
@@ -543,6 +541,8 @@ public final class ViewRootImpl implements ViewParent,
@UiContext
public final Context mContext;
+ private UiModeManager mUiModeManager;
+
@UnsupportedAppUsage
final IWindowSession mWindowSession;
@NonNull Display mDisplay;
@@ -1238,6 +1238,7 @@ public final class ViewRootImpl implements ViewParent,
public ViewRootImpl(@UiContext Context context, Display display, IWindowSession session,
WindowLayout windowLayout) {
mContext = context;
+ mUiModeManager = context.getSystemService(UiModeManager.class);
mWindowSession = session;
mWindowLayout = windowLayout;
mDisplay = display;
@@ -1781,23 +1782,6 @@ public final class ViewRootImpl implements ViewParent,
}
}
- private boolean isForceInvertEnabled() {
- if (mForceInvertEnabled == INVALID_VALUE) {
- reloadForceInvertEnabled();
- }
- return mForceInvertEnabled == 1;
- }
-
- private void reloadForceInvertEnabled() {
- if (forceInvertColor()) {
- mForceInvertEnabled = Settings.Secure.getIntForUser(
- mContext.getContentResolver(),
- Settings.Secure.ACCESSIBILITY_FORCE_INVERT_COLOR_ENABLED,
- /* def= */ 0,
- UserHandle.myUserId());
- }
- }
-
/**
* Register any kind of listeners if setView was success.
*/
@@ -1829,21 +1813,11 @@ public final class ViewRootImpl implements ViewParent,
mBasePackageName);
if (forceInvertColor()) {
- if (mForceInvertObserver == null) {
- mForceInvertObserver = new ContentObserver(mHandler) {
- @Override
- public void onChange(boolean selfChange) {
- reloadForceInvertEnabled();
- updateForceDarkMode();
- }
- };
- mContext.getContentResolver().registerContentObserver(
- Settings.Secure.getUriFor(
- Settings.Secure.ACCESSIBILITY_FORCE_INVERT_COLOR_ENABLED
- ),
- false,
- mForceInvertObserver,
- UserHandle.myUserId());
+ if (mForceInvertStateChangeListener == null) {
+ mForceInvertStateChangeListener =
+ forceInvertState -> updateForceDarkMode();
+ mUiModeManager.addForceInvertStateChangeListener(mExecutor,
+ mForceInvertStateChangeListener);
}
}
}
@@ -1861,9 +1835,10 @@ public final class ViewRootImpl implements ViewParent,
.unregisterDisplayListener(mDisplayListener);
if (forceInvertColor()) {
- if (mForceInvertObserver != null) {
- mContext.getContentResolver().unregisterContentObserver(mForceInvertObserver);
- mForceInvertObserver = null;
+ if (mForceInvertStateChangeListener != null) {
+ mUiModeManager.removeForceInvertStateChangeListener(
+ mForceInvertStateChangeListener);
+ mForceInvertStateChangeListener = null;
}
}
@@ -2050,21 +2025,25 @@ public final class ViewRootImpl implements ViewParent,
return getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
}
- /** Returns true if force dark should be enabled according to various settings */
+ /**
+ * Determines the type of force dark to apply, considering force inversion, system night mode,
+ * and app-specific settings (including developer opt-outs).
+ *
+ * @return A {@link ForceDarkType.ForceDarkTypeDef} constant indicating the force dark type.
+ */
@VisibleForTesting
public @ForceDarkType.ForceDarkTypeDef int determineForceDarkType() {
if (forceInvertColor()) {
// Force invert ignores all developer opt-outs.
// We also ignore dark theme, since the app developer can override the user's preference
- // for dark mode in configuration.uiMode. Instead, we assume that the force invert
- // setting will be enabled at the same time dark theme is in the Settings app.
- if (isForceInvertEnabled()) {
+ // for dark mode in configuration.uiMode. Instead, we assume that both force invert and
+ // the system's dark theme are enabled.
+ if (mUiModeManager.getForceInvertState() == UiModeManager.FORCE_INVERT_TYPE_DARK) {
return ForceDarkType.FORCE_INVERT_COLOR_DARK;
}
}
boolean useAutoDark = getNightMode() == Configuration.UI_MODE_NIGHT_YES;
-
if (useAutoDark) {
boolean forceDarkAllowedDefault =
SystemProperties.getBoolean(ThreadedRenderer.DEBUG_FORCE_DARK, false);
diff --git a/core/java/android/view/inputmethod/ImeTracker.java b/core/java/android/view/inputmethod/ImeTracker.java
index 4d354e0655f0..aa0111a13b8e 100644
--- a/core/java/android/view/inputmethod/ImeTracker.java
+++ b/core/java/android/view/inputmethod/ImeTracker.java
@@ -134,6 +134,7 @@ public interface ImeTracker {
ORIGIN_CLIENT,
ORIGIN_SERVER,
ORIGIN_IME,
+ ORIGIN_WM_SHELL,
})
@Retention(RetentionPolicy.SOURCE)
@interface Origin {}
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/DesktopExperienceFlags.java b/core/java/android/window/DesktopExperienceFlags.java
index 7758dea3ea8a..e0c48b03dad8 100644
--- a/core/java/android/window/DesktopExperienceFlags.java
+++ b/core/java/android/window/DesktopExperienceFlags.java
@@ -40,9 +40,7 @@ import java.util.function.BooleanSupplier;
* @hide
*/
public enum DesktopExperienceFlags {
- ENABLE_DISPLAY_CONTENT_MODE_MANAGEMENT(
- com.android.server.display.feature.flags.Flags::enableDisplayContentModeManagement,
- true),
+ // go/keep-sorted start
ACTIVITY_EMBEDDING_SUPPORT_FOR_CONNECTED_DISPLAYS(
Flags::activityEmbeddingSupportForConnectedDisplays, false),
BASE_DENSITY_FOR_EXTERNAL_DISPLAYS(
@@ -53,6 +51,9 @@ public enum DesktopExperienceFlags {
ENABLE_CONNECTED_DISPLAYS_DND(Flags::enableConnectedDisplaysDnd, false),
ENABLE_CONNECTED_DISPLAYS_PIP(Flags::enableConnectedDisplaysPip, false),
ENABLE_CONNECTED_DISPLAYS_WINDOW_DRAG(Flags::enableConnectedDisplaysWindowDrag, false),
+ ENABLE_DISPLAY_CONTENT_MODE_MANAGEMENT(
+ com.android.server.display.feature.flags.Flags::enableDisplayContentModeManagement,
+ true),
ENABLE_DISPLAY_FOCUS_IN_SHELL_TRANSITIONS(Flags::enableDisplayFocusInShellTransitions, false),
ENABLE_DISPLAY_WINDOWING_MODE_SWITCHING(Flags::enableDisplayWindowingModeSwitching, false),
ENABLE_DRAG_TO_MAXIMIZE(Flags::enableDragToMaximize, true),
@@ -63,9 +64,12 @@ public enum DesktopExperienceFlags {
false),
ENABLE_PER_DISPLAY_PACKAGE_CONTEXT_CACHE_IN_STATUSBAR_NOTIF(
Flags::enablePerDisplayPackageContextCacheInStatusbarNotif, false),
+ ENABLE_TASKBAR_CONNECTED_DISPLAYS(Flags::enableTaskbarConnectedDisplays, false),
ENTER_DESKTOP_BY_DEFAULT_ON_FREEFORM_DISPLAYS(Flags::enterDesktopByDefaultOnFreeformDisplays,
false),
- REPARENT_WINDOW_TOKEN_API(Flags::reparentWindowTokenApi, true);
+ REPARENT_WINDOW_TOKEN_API(Flags::reparentWindowTokenApi, true)
+ // go/keep-sorted end
+ ;
/**
* Flag class, to be used in case the enum cannot be used because the flag is not accessible.
diff --git a/core/java/android/window/DesktopModeFlags.java b/core/java/android/window/DesktopModeFlags.java
index 9468301ac996..082bf5dc5a1c 100644
--- a/core/java/android/window/DesktopModeFlags.java
+++ b/core/java/android/window/DesktopModeFlags.java
@@ -42,62 +42,68 @@ import java.util.function.BooleanSupplier;
*/
public enum DesktopModeFlags {
// All desktop mode related flags to be overridden by developer option toggle will be added here
- ENABLE_DESKTOP_WINDOWING_MODE(
- Flags::enableDesktopWindowingMode, /* shouldOverrideByDevOption= */ true),
- ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS(Flags::enableWindowingDynamicInitialBounds, true),
- ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION(
- Flags::enableCaptionCompatInsetForceConsumption, true),
+ // go/keep-sorted start
+ DISABLE_NON_RESIZABLE_APP_SNAP_RESIZE(Flags::disableNonResizableAppSnapResizing, true),
+ ENABLE_APP_HEADER_WITH_TASK_DENSITY(Flags::enableAppHeaderWithTaskDensity, true),
+ ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION(Flags::enableCaptionCompatInsetForceConsumption,
+ true),
ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION_ALWAYS(
Flags::enableCaptionCompatInsetForceConsumptionAlways, true),
ENABLE_CASCADING_WINDOWS(Flags::enableCascadingWindows, true),
- ENABLE_TILE_RESIZING(Flags::enableTileResizing, true),
- ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY(
- Flags::enableDesktopWindowingWallpaperActivity, true),
- ENABLE_DESKTOP_WINDOWING_MODALS_POLICY(Flags::enableDesktopWindowingModalsPolicy, true),
- ENABLE_THEMED_APP_HEADERS(Flags::enableThemedAppHeaders, true),
- ENABLE_HOLD_TO_DRAG_APP_HANDLE(Flags::enableHoldToDragAppHandle, true),
- ENABLE_DESKTOP_WINDOWING_QUICK_SWITCH(Flags::enableDesktopWindowingQuickSwitch, true),
- ENABLE_APP_HEADER_WITH_TASK_DENSITY(Flags::enableAppHeaderWithTaskDensity, true),
- ENABLE_TASK_STACK_OBSERVER_IN_SHELL(Flags::enableTaskStackObserverInShell, true),
- ENABLE_DESKTOP_WINDOWING_SIZE_CONSTRAINTS(Flags::enableDesktopWindowingSizeConstraints, true),
- DISABLE_NON_RESIZABLE_APP_SNAP_RESIZE(Flags::disableNonResizableAppSnapResizing, true),
- ENABLE_WINDOWING_SCALED_RESIZING(Flags::enableWindowingScaledResizing, true),
- ENABLE_DESKTOP_WINDOWING_TASK_LIMIT(Flags::enableDesktopWindowingTaskLimit, true),
+ ENABLE_DESKTOP_APP_LAUNCH_ALTTAB_TRANSITIONS_BUGFIX(
+ Flags::enableDesktopAppLaunchAlttabTransitionsBugfix, true),
+ ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS_BUGFIX(Flags::enableDesktopAppLaunchTransitionsBugfix,
+ true),
+ ENABLE_DESKTOP_COMPAT_UI_VISIBILITY_STATUS(Flags::enableCompatUiVisibilityStatus, true),
+ ENABLE_DESKTOP_RECENTS_TRANSITIONS_CORNERS_BUGFIX(
+ Flags::enableDesktopRecentsTransitionsCornersBugfix, false),
+ ENABLE_DESKTOP_SKIP_COMPAT_UI_EDUCATION_IN_DESKTOP_MODE_BUGFIX(
+ Flags::skipCompatUiEducationInDesktopMode, true),
+ ENABLE_DESKTOP_SYSTEM_DIALOGS_TRANSITIONS(Flags::enableDesktopSystemDialogsTransitions, true),
+ ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER(
+ Flags::enableDesktopWallpaperActivityForSystemUser, true),
+ ENABLE_DESKTOP_WINDOWING_APP_TO_WEB(Flags::enableDesktopWindowingAppToWeb, true),
+ ENABLE_DESKTOP_WINDOWING_APP_TO_WEB_EDUCATION(Flags::enableDesktopWindowingAppToWebEducation,
+ true),
ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION(Flags::enableDesktopWindowingBackNavigation, true),
- ENABLE_WINDOWING_EDGE_DRAG_RESIZE(Flags::enableWindowingEdgeDragResize, true),
- ENABLE_DESKTOP_WINDOWING_TASKBAR_RUNNING_APPS(
- Flags::enableDesktopWindowingTaskbarRunningApps, true),
- ENABLE_WINDOWING_TRANSITION_HANDLERS_OBSERVERS(
- Flags::enableWindowingTransitionHandlersObservers, false),
- ENABLE_DESKTOP_WINDOWING_PERSISTENCE(Flags::enableDesktopWindowingPersistence, false),
- ENABLE_HANDLE_INPUT_FIX(Flags::enableHandleInputFix, true),
ENABLE_DESKTOP_WINDOWING_ENTER_TRANSITIONS_BUGFIX(
Flags::enableDesktopWindowingEnterTransitionBugfix, true),
+ ENABLE_DESKTOP_WINDOWING_EXIT_BY_MINIMIZE_TRANSITION_BUGFIX(
+ Flags::enableDesktopWindowingExitByMinimizeTransitionBugfix, false),
ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS_BUGFIX(
Flags::enableDesktopWindowingExitTransitionsBugfix, true),
- ENABLE_DESKTOP_APP_LAUNCH_ALTTAB_TRANSITIONS_BUGFIX(
- Flags::enableDesktopAppLaunchAlttabTransitionsBugfix, true),
- ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS_BUGFIX(
- Flags::enableDesktopAppLaunchTransitionsBugfix, true),
- ENABLE_DESKTOP_COMPAT_UI_VISIBILITY_STATUS(
- Flags::enableCompatUiVisibilityStatus, true),
- ENABLE_DESKTOP_SKIP_COMPAT_UI_EDUCATION_IN_DESKTOP_MODE_BUGFIX(
- Flags::skipCompatUiEducationInDesktopMode, true),
- INCLUDE_TOP_TRANSPARENT_FULLSCREEN_TASK_IN_DESKTOP_HEURISTIC(
- Flags::includeTopTransparentFullscreenTaskInDesktopHeuristic, true),
ENABLE_DESKTOP_WINDOWING_HSUM(Flags::enableDesktopWindowingHsum, true),
+ ENABLE_DESKTOP_WINDOWING_MODALS_POLICY(Flags::enableDesktopWindowingModalsPolicy, true),
+ ENABLE_DESKTOP_WINDOWING_MODE(Flags::enableDesktopWindowingMode, true),
+ ENABLE_DESKTOP_WINDOWING_MULTI_INSTANCE_FEATURES(
+ Flags::enableDesktopWindowingMultiInstanceFeatures, true),
+ 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,
+ true),
+ ENABLE_DESKTOP_WINDOWING_TASK_LIMIT(Flags::enableDesktopWindowingTaskLimit, true),
+ ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY(Flags::enableDesktopWindowingWallpaperActivity,
+ true),
+ ENABLE_HANDLE_INPUT_FIX(Flags::enableHandleInputFix, true),
+ ENABLE_HOLD_TO_DRAG_APP_HANDLE(Flags::enableHoldToDragAppHandle, true),
ENABLE_MINIMIZE_BUTTON(Flags::enableMinimizeButton, true),
ENABLE_RESIZING_METRICS(Flags::enableResizingMetrics, true),
ENABLE_TASK_RESIZING_KEYBOARD_SHORTCUTS(Flags::enableTaskResizingKeyboardShortcuts, true),
- ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER(
- Flags::enableDesktopWallpaperActivityForSystemUser, true),
- ENABLE_TOP_VISIBLE_ROOT_TASK_PER_USER_TRACKING(
- Flags::enableTopVisibleRootTaskPerUserTracking, true),
- ENABLE_DESKTOP_RECENTS_TRANSITIONS_CORNERS_BUGFIX(
- Flags::enableDesktopRecentsTransitionsCornersBugfix, false),
- ENABLE_DESKTOP_SYSTEM_DIALOGS_TRANSITIONS(Flags::enableDesktopSystemDialogsTransitions, true),
- ENABLE_DESKTOP_WINDOWING_MULTI_INSTANCE_FEATURES(
- Flags::enableDesktopWindowingMultiInstanceFeatures, true);
+ ENABLE_TASK_STACK_OBSERVER_IN_SHELL(Flags::enableTaskStackObserverInShell, true),
+ ENABLE_THEMED_APP_HEADERS(Flags::enableThemedAppHeaders, true),
+ ENABLE_TILE_RESIZING(Flags::enableTileResizing, true),
+ ENABLE_TOP_VISIBLE_ROOT_TASK_PER_USER_TRACKING(Flags::enableTopVisibleRootTaskPerUserTracking,
+ true),
+ ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS(Flags::enableWindowingDynamicInitialBounds, true),
+ ENABLE_WINDOWING_EDGE_DRAG_RESIZE(Flags::enableWindowingEdgeDragResize, true),
+ ENABLE_WINDOWING_SCALED_RESIZING(Flags::enableWindowingScaledResizing, true),
+ ENABLE_WINDOWING_TRANSITION_HANDLERS_OBSERVERS(
+ Flags::enableWindowingTransitionHandlersObservers, false),
+ INCLUDE_TOP_TRANSPARENT_FULLSCREEN_TASK_IN_DESKTOP_HEURISTIC(
+ Flags::includeTopTransparentFullscreenTaskInDesktopHeuristic, true)
+ // go/keep-sorted end
+ ;
/**
* Flag class, to be used in case the enum cannot be used because the flag is not accessible.
diff --git a/core/java/android/window/flags/lse_desktop_experience.aconfig b/core/java/android/window/flags/lse_desktop_experience.aconfig
index caccc3c42ce8..ed22ec73aac8 100644
--- a/core/java/android/window/flags/lse_desktop_experience.aconfig
+++ b/core/java/android/window/flags/lse_desktop_experience.aconfig
@@ -513,6 +513,13 @@ flag {
}
flag {
+ name: "enable_taskbar_connected_displays"
+ namespace: "lse_desktop_experience"
+ description: "Enables connected displays in taskbar."
+ bug: "393398093"
+}
+
+flag {
name: "enable_bug_fixes_for_secondary_display"
namespace: "lse_desktop_experience"
description: "Bugfixes / papercuts to bring Desktop Windowing to secondary displays."
@@ -617,3 +624,10 @@ flag {
description: "Forces pinned taskbar with desktop tasks on freeform displays"
bug: "390665752"
}
+
+flag {
+ name: "enable_presentation_for_connected_displays"
+ namespace: "lse_desktop_experience"
+ description: "Enables full support of presentation API for connected displays."
+ bug: "378503083"
+}
diff --git a/core/proto/android/server/windowmanagerservice.proto b/core/proto/android/server/windowmanagerservice.proto
index 407790c89202..a673ad7dfb34 100644
--- a/core/proto/android/server/windowmanagerservice.proto
+++ b/core/proto/android/server/windowmanagerservice.proto
@@ -399,6 +399,7 @@ message ActivityRecordProto {
optional bool should_override_force_resize_app = 44;
optional bool should_enable_user_aspect_ratio_settings = 45;
optional bool is_user_fullscreen_override_enabled = 46;
+ optional int64 request_open_in_browser_education_timestamp = 47;
}
/* represents WindowToken */
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/drawable/accessibility_autoclick_button_rounded_background.xml b/core/res/res/drawable/accessibility_autoclick_button_rounded_background.xml
new file mode 100644
index 000000000000..6d0c2653e5f2
--- /dev/null
+++ b/core/res/res/drawable/accessibility_autoclick_button_rounded_background.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2025 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+ <solid android:color="@color/materialColorSurfaceContainer" />
+ <corners android:radius="24dp" />
+</shape>
diff --git a/core/res/res/drawable/accessibility_autoclick_left_click.xml b/core/res/res/drawable/accessibility_autoclick_left_click.xml
new file mode 100644
index 000000000000..64c8efbe24b3
--- /dev/null
+++ b/core/res/res/drawable/accessibility_autoclick_left_click.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2025 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="18dp"
+ android:height="18dp"
+ android:viewportWidth="18"
+ android:viewportHeight="18">
+ <path
+ android:pathData="M5.7 12C4.1 11.9167 2.75 11.3 1.65 10.15C0.55 9 0 7.61667 0 6C0 4.33333 0.583333 2.91667 1.75 1.75C2.91667 0.583332 4.33333 -0.00000143051 6 -0.00000143051C7.61667 -0.00000143051 9 0.549999 10.15 1.65C11.3 2.75 11.9167 4.1 12 5.7L9.9 5.075C9.68333 4.175 9.21667 3.44167 8.5 2.875C7.78333 2.29167 6.95 2 6 2C4.9 2 3.95833 2.39167 3.175 3.175C2.39167 3.95833 2 4.9 2 6C2 6.95 2.28333 7.78333 2.85 8.5C3.43333 9.21667 4.175 9.68333 5.075 9.9L5.7 12ZM14.525 16.5L10.25 12.225L9 16L6 6L16 9L12.225 10.25L16.5 14.525L14.525 16.5Z"
+ android:fillColor="@color/materialColorPrimary" />
+</vector>
diff --git a/core/res/res/drawable/accessibility_autoclick_pause.xml b/core/res/res/drawable/accessibility_autoclick_pause.xml
new file mode 100644
index 000000000000..5251b2afed0d
--- /dev/null
+++ b/core/res/res/drawable/accessibility_autoclick_pause.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2025 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+
+ <path
+ android:fillColor="@color/materialColorPrimary"
+ android:pathData="M6,19h4V5H6v14zm8,-14v14h4V5h-4z" />
+</vector>
diff --git a/core/res/res/drawable/accessibility_autoclick_position.xml b/core/res/res/drawable/accessibility_autoclick_position.xml
new file mode 100644
index 000000000000..8c98235e7d99
--- /dev/null
+++ b/core/res/res/drawable/accessibility_autoclick_position.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2025 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="21dp"
+ android:height="17dp"
+ android:viewportWidth="21"
+ android:viewportHeight="17">
+ <path
+ android:pathData="M0.400024 2.99961C0.400024 1.67413 1.47454 0.599609 2.80002 0.599609H17.2C18.5255 0.599609 19.6 1.67413 19.6 2.99961V14.9996C19.6 16.3251 18.5255 17.3996 17.2 17.3996H2.80002C1.47454 17.3996 0.400024 16.3251 0.400024 14.9996V2.99961ZM2.80002 2.99961H17.2V14.9996H2.80002V2.99961ZM10.6 10.1996C9.60591 10.1996 8.80002 11.0055 8.80002 11.9996C8.80002 12.9937 9.60591 13.7996 10.6 13.7996H14.2C15.1941 13.7996 16 12.9937 16 11.9996C16 11.0055 15.1941 10.1996 14.2 10.1996H10.6Z"
+ android:fillType="evenOdd"
+ android:fillColor="@color/materialColorPrimary" />
+</vector>
diff --git a/core/res/res/drawable/accessibility_autoclick_type_panel_rounded_background.xml b/core/res/res/drawable/accessibility_autoclick_type_panel_rounded_background.xml
new file mode 100644
index 000000000000..e367ba55ae7d
--- /dev/null
+++ b/core/res/res/drawable/accessibility_autoclick_type_panel_rounded_background.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2025 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+ <solid android:color="@color/materialColorSurface" />
+ <corners android:radius="40dp" />
+</shape>
diff --git a/core/res/res/layout/accessibility_autoclick_type_panel.xml b/core/res/res/layout/accessibility_autoclick_type_panel.xml
new file mode 100644
index 000000000000..9aa47cc8d68b
--- /dev/null
+++ b/core/res/res/layout/accessibility_autoclick_type_panel.xml
@@ -0,0 +1,78 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2025, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/accessibility_autoclick_type_panel"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_horizontal"
+ android:background="@drawable/accessibility_autoclick_type_panel_rounded_background"
+ android:orientation="vertical"
+ android:padding="16dp">
+
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:gravity="center"
+ android:orientation="horizontal">
+
+ <LinearLayout
+ android:id="@+id/accessibility_autoclick_left_click_layout"
+ style="@style/AccessibilityAutoclickPanelButtonLayoutStyle"
+ android:layout_marginEnd="@dimen/accessibility_autoclick_type_panel_button_spacing">
+
+ <ImageButton
+ android:id="@+id/accessibility_autoclick_left_click_button"
+ style="@style/AccessibilityAutoclickPanelImageButtonStyle"
+ android:contentDescription="@string/accessibility_autoclick_left_click"
+ android:src="@drawable/accessibility_autoclick_left_click" />
+ </LinearLayout>
+
+ <View
+ android:layout_width="@dimen/accessibility_autoclick_type_panel_divider_width"
+ android:layout_height="@dimen/accessibility_autoclick_type_panel_divider_height"
+ android:layout_marginEnd="@dimen/accessibility_autoclick_type_panel_button_spacing"
+ android:background="@color/materialColorSurfaceContainer" />
+
+ <LinearLayout
+ android:id="@+id/accessibility_autoclick_pause_layout"
+ style="@style/AccessibilityAutoclickPanelButtonLayoutStyle"
+ android:layout_marginEnd="@dimen/accessibility_autoclick_type_panel_button_spacing">
+
+ <ImageButton
+ android:id="@+id/accessibility_autoclick_pause_button"
+ style="@style/AccessibilityAutoclickPanelImageButtonStyle"
+ android:contentDescription="@string/accessibility_autoclick_pause"
+ android:src="@drawable/accessibility_autoclick_pause" />
+ </LinearLayout>
+
+ <LinearLayout
+ android:id="@+id/accessibility_autoclick_position_layout"
+ style="@style/AccessibilityAutoclickPanelButtonLayoutStyle">
+
+ <ImageButton
+ android:id="@+id/accessibility_autoclick_position_button"
+ style="@style/AccessibilityAutoclickPanelImageButtonStyle"
+ android:contentDescription="@string/accessibility_autoclick_position"
+ android:src="@drawable/accessibility_autoclick_position" />
+ </LinearLayout>
+
+ </LinearLayout>
+
+</LinearLayout>
diff --git a/core/res/res/layout/notification_2025_template_compact_heads_up_base.xml b/core/res/res/layout/notification_2025_template_compact_heads_up_base.xml
new file mode 100644
index 000000000000..11fc48668ad7
--- /dev/null
+++ b/core/res/res/layout/notification_2025_template_compact_heads_up_base.xml
@@ -0,0 +1,89 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright (C) 2025 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/status_bar_latest_event_content"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/notification_header_height"
+ android:clipChildren="false"
+ android:tag="compactHUN"
+ android:gravity="center_vertical"
+ android:theme="@style/Theme.DeviceDefault.Notification"
+ android:importantForAccessibility="no">
+ <com.android.internal.widget.NotificationRowIconView
+ android:id="@+id/icon"
+ android:layout_width="@dimen/notification_icon_circle_size"
+ android:layout_height="@dimen/notification_icon_circle_size"
+ android:layout_gravity="center_vertical|start"
+ android:layout_marginStart="@dimen/notification_icon_circle_start"
+ android:background="@drawable/notification_icon_circle"
+ android:padding="@dimen/notification_icon_circle_padding"
+ android:maxDrawableWidth="@dimen/notification_icon_circle_size"
+ android:maxDrawableHeight="@dimen/notification_icon_circle_size"
+ />
+ <FrameLayout
+ android:id="@+id/alternate_expand_target"
+ android:layout_width="@dimen/notification_content_margin_start"
+ android:layout_height="match_parent"
+ android:layout_gravity="start"
+ android:importantForAccessibility="no"
+ android:focusable="false"
+ />
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_marginStart="@dimen/notification_content_margin_start"
+ android:orientation="horizontal"
+ >
+ <NotificationTopLineView
+ android:id="@+id/notification_top_line"
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_centerVertical="true"
+ android:layout_weight="1"
+ android:clipChildren="false"
+ android:gravity="center_vertical"
+ android:theme="@style/Theme.DeviceDefault.Notification"
+ >
+ <TextView
+ android:id="@+id/title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginEnd="@dimen/notification_header_separating_margin"
+ android:ellipsize="end"
+ android:fadingEdge="horizontal"
+ android:singleLine="true"
+ android:textAlignment="viewStart"
+ android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Title"
+ android:textSize="@dimen/notification_2025_title_text_size"
+ />
+ <include layout="@layout/notification_2025_top_line_views" />
+ </NotificationTopLineView>
+ <FrameLayout
+ android:id="@+id/expand_button_touch_container"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:minWidth="@dimen/notification_content_margin_end"
+ >
+ <include layout="@layout/notification_2025_expand_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical|end"
+ />
+ </FrameLayout>
+ </LinearLayout>
+</FrameLayout>
diff --git a/core/res/res/layout/notification_2025_template_compact_heads_up_messaging.xml b/core/res/res/layout/notification_2025_template_compact_heads_up_messaging.xml
new file mode 100644
index 000000000000..dd70087f7785
--- /dev/null
+++ b/core/res/res/layout/notification_2025_template_compact_heads_up_messaging.xml
@@ -0,0 +1,116 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright (C) 2025 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+
+<com.android.internal.widget.CompactMessagingLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/status_bar_latest_event_content"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/notification_header_height"
+ android:clipChildren="false"
+ android:tag="compactMessagingHUN"
+ android:gravity="center_vertical"
+ android:theme="@style/Theme.DeviceDefault.Notification"
+ android:importantForAccessibility="no">
+ <com.android.internal.widget.NotificationRowIconView
+ android:id="@+id/icon"
+ android:layout_width="@dimen/notification_icon_circle_size"
+ android:layout_height="@dimen/notification_icon_circle_size"
+ android:layout_gravity="center_vertical|start"
+ android:layout_marginStart="@dimen/notification_icon_circle_start"
+ android:background="@drawable/notification_icon_circle"
+ android:padding="@dimen/notification_icon_circle_padding"
+ android:maxDrawableWidth="@dimen/notification_icon_circle_size"
+ android:maxDrawableHeight="@dimen/notification_icon_circle_size"
+ />
+ <com.android.internal.widget.CachingIconView
+ android:id="@+id/conversation_icon"
+ android:layout_width="@dimen/notification_icon_circle_size"
+ android:layout_height="@dimen/notification_icon_circle_size"
+ android:layout_gravity="center_vertical|start"
+ android:layout_marginStart="@dimen/notification_icon_circle_start"
+ android:background="@drawable/notification_icon_circle"
+ android:clipToOutline="true"
+ android:maxDrawableWidth="@dimen/notification_icon_circle_size"
+ android:maxDrawableHeight="@dimen/notification_icon_circle_size"
+ android:scaleType="centerCrop"
+ android:importantForAccessibility="no"
+ />
+ <ViewStub
+ android:layout="@layout/conversation_face_pile_layout"
+ android:layout_gravity="center_vertical|start"
+ android:layout_width="@dimen/conversation_compact_face_pile_size"
+ android:layout_height="@dimen/conversation_compact_face_pile_size"
+ android:layout_marginStart="@dimen/notification_icon_circle_start"
+ android:id="@+id/conversation_face_pile"
+ />
+ <FrameLayout
+ android:id="@+id/alternate_expand_target"
+ android:layout_width="@dimen/notification_content_margin_start"
+ android:layout_height="match_parent"
+ android:layout_gravity="start"
+ android:importantForAccessibility="no"
+ android:focusable="false"
+ />
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_marginStart="@dimen/notification_content_margin_start"
+ android:orientation="horizontal"
+ >
+ <NotificationTopLineView
+ android:id="@+id/notification_top_line"
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_centerVertical="true"
+ android:layout_weight="1"
+ android:clipChildren="false"
+ android:gravity="center_vertical"
+ android:theme="@style/Theme.DeviceDefault.Notification"
+ >
+ <TextView
+ android:id="@+id/title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginEnd="@dimen/notification_header_separating_margin"
+ android:ellipsize="end"
+ android:fadingEdge="horizontal"
+ android:singleLine="true"
+ android:textAlignment="viewStart"
+ android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Title"
+ android:textSize="@dimen/notification_2025_title_text_size"
+ />
+ <include layout="@layout/notification_2025_top_line_views" />
+ </NotificationTopLineView>
+ <FrameLayout
+ android:id="@+id/reply_action_container"
+ android:layout_width="wrap_content"
+ android:layout_height="@dimen/notification_action_list_height"
+ android:gravity="center_vertical"
+ android:orientation="horizontal" />
+ <FrameLayout
+ android:id="@+id/expand_button_touch_container"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:minWidth="@dimen/notification_content_margin_end"
+ >
+ <include layout="@layout/notification_2025_expand_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical|end"
+ />
+ </FrameLayout>
+ </LinearLayout>
+</com.android.internal.widget.CompactMessagingLayout>
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/dimens.xml b/core/res/res/values/dimens.xml
index 5644cf9dd61d..484e8ef1e049 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -714,6 +714,18 @@
<!-- The minimum window size of the accessibility window magnifier -->
<dimen name="accessibility_window_magnifier_min_size">122dp</dimen>
+ <!-- The accessibility autoclick panel button spacing -->
+ <dimen name="accessibility_autoclick_type_panel_button_spacing">12dp</dimen>
+
+ <!-- The accessibility autoclick panel button width and height -->
+ <dimen name="accessibility_autoclick_type_panel_button_size">36dp</dimen>
+
+ <!-- The accessibility autoclick panel divider width -->
+ <dimen name="accessibility_autoclick_type_panel_divider_width">1dp</dimen>
+
+ <!-- The accessibility autoclick panel divider height -->
+ <dimen name="accessibility_autoclick_type_panel_divider_height">24dp</dimen>
+
<!-- Margin around the various security views -->
<dimen name="keyguard_muliuser_selector_margin">8dp</dimen>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index fa4c21de682e..9399e2b9824e 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -6149,6 +6149,16 @@
<!-- Label for Dpad center action [CHAR LIMIT=NONE] -->
<string name="accessibility_system_action_dpad_center_label">Dpad Center</string>
+ <!-- Accessibility autoclick related strings -->
+ <!-- Label for autoclick type settings panel [CHAR LIMIT=NONE] -->
+ <string name="accessibility_autoclick_type_settings_panel_title">Autoclick type settings panel</string>
+ <!-- Label for autoclick left click button [CHAR LIMIT=NONE] -->
+ <string name="accessibility_autoclick_left_click">Left click</string>
+ <!-- Label for autoclick pause button [CHAR LIMIT=NONE] -->
+ <string name="accessibility_autoclick_pause">Pause</string>
+ <!-- Label for autoclick position button [CHAR LIMIT=NONE] -->
+ <string name="accessibility_autoclick_position">Position</string>
+
<!-- Text to tell the user that a package has been forced by themselves in the RESTRICTED bucket. [CHAR LIMIT=NONE] -->
<string name="as_app_forced_to_restricted_bucket">
<xliff:g id="package_name" example="com.android.example">%1$s</xliff:g> has been put into the RESTRICTED bucket</string>
diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml
index 579dc91d2ca1..ee1edda838fd 100644
--- a/core/res/res/values/styles.xml
+++ b/core/res/res/values/styles.xml
@@ -1745,6 +1745,22 @@ please see styles_device_defaults.xml.
<item name="android:windowOptOutEdgeToEdgeEnforcement">true</item>
</style>
+ <style name="AccessibilityAutoclickPanelButtonLayoutStyle">
+ <item name="android:gravity">center</item>
+ <item name="android:background">@drawable/accessibility_autoclick_button_rounded_background</item>
+ <item name="android:layout_width">@dimen/accessibility_autoclick_type_panel_button_size</item>
+ <item name="android:layout_height">@dimen/accessibility_autoclick_type_panel_button_size</item>
+ </style>
+
+ <style name="AccessibilityAutoclickPanelImageButtonStyle">
+ <item name="android:gravity">center</item>
+ <item name="android:layout_width">wrap_content</item>
+ <item name="android:layout_height">wrap_content</item>
+ <item name="android:scaleType">center</item>
+ <item name="android:background">@android:color/transparent</item>
+ <item name="android:tint">@color/materialColorPrimary</item>
+ </style>
+
<!--
TODO(b/309578419): Make activities go edge-to-edge properly and then remove this.
-->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 8bb3c995cf9f..f4004fa70623 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2412,6 +2412,8 @@
<java-symbol type="layout" name="notification_2025_template_collapsed_base" />
<java-symbol type="layout" name="notification_2025_template_expanded_base" />
<java-symbol type="layout" name="notification_2025_template_heads_up_base" />
+ <java-symbol type="layout" name="notification_2025_template_compact_heads_up_base" />
+ <java-symbol type="layout" name="notification_2025_template_compact_heads_up_messaging" />
<java-symbol type="layout" name="notification_2025_template_header" />
<java-symbol type="layout" name="notification_2025_template_collapsed_messaging" />
<java-symbol type="layout" name="notification_2025_template_collapsed_media" />
@@ -4615,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" />
@@ -5605,6 +5608,24 @@
<java-symbol type="bool" name="config_enable_a11y_fullscreen_magnification_overscroll_handler" />
<java-symbol type="dimen" name="accessibility_fullscreen_magnification_gesture_edge_slop" />
+ <!-- Accessibility autoclick related -->
+ <java-symbol type="layout" name="accessibility_autoclick_type_panel" />
+ <java-symbol type="string" name="accessibility_autoclick_type_settings_panel_title" />
+ <java-symbol type="string" name="accessibility_autoclick_left_click" />
+ <java-symbol type="string" name="accessibility_autoclick_pause" />
+ <java-symbol type="string" name="accessibility_autoclick_position" />
+ <java-symbol type="dimen" name="accessibility_autoclick_type_panel_button_spacing" />
+ <java-symbol type="dimen" name="accessibility_autoclick_type_panel_button_size" />
+ <java-symbol type="dimen" name="accessibility_autoclick_type_panel_divider_height" />
+ <java-symbol type="dimen" name="accessibility_autoclick_type_panel_divider_width" />
+ <java-symbol type="id" name="accessibility_autoclick_type_panel" />
+ <java-symbol type="id" name="accessibility_autoclick_left_click_layout" />
+ <java-symbol type="id" name="accessibility_autoclick_left_click_button" />
+ <java-symbol type="id" name="accessibility_autoclick_pause_layout" />
+ <java-symbol type="id" name="accessibility_autoclick_pause_button" />
+ <java-symbol type="id" name="accessibility_autoclick_position_layout" />
+ <java-symbol type="id" name="accessibility_autoclick_position_button" />
+
<!-- For HapticFeedbackConstants configurability defined at HapticFeedbackCustomization -->
<java-symbol type="string" name="config_hapticFeedbackCustomizationFile" />
<java-symbol type="xml" name="haptic_feedback_customization" />
diff --git a/core/tests/coretests/src/android/os/PerfettoTraceTest.java b/core/tests/coretests/src/android/os/PerfettoTraceTest.java
index 0b5a44665d2b..91efacf0dcc3 100644
--- a/core/tests/coretests/src/android/os/PerfettoTraceTest.java
+++ b/core/tests/coretests/src/android/os/PerfettoTraceTest.java
@@ -59,6 +59,8 @@ import perfetto.protos.TrackEventOuterClass.TrackEvent;
import java.util.List;
import java.util.Set;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
/**
* This class is used to test the native tracing support. Run this test
@@ -77,6 +79,8 @@ public class PerfettoTraceTest {
private static final String BAR = "bar";
private static final Category FOO_CATEGORY = new Category(FOO);
+ private static final int MESSAGE = 1234567;
+ private static final int MESSAGE_COUNT = 3;
private final Set<String> mCategoryNames = new ArraySet<>();
private final Set<String> mEventNames = new ArraySet<>();
@@ -592,6 +596,89 @@ public class PerfettoTraceTest {
assertThat(mDebugAnnotationNames).doesNotContain("before");
}
+ @Test
+ @RequiresFlagsEnabled(android.os.Flags.FLAG_PERFETTO_SDK_TRACING_V2)
+ public void testMessageQueue() throws Exception {
+ TraceConfig traceConfig = getTraceConfig("mq");
+
+ PerfettoTrace.MQ_CATEGORY.register();
+ final HandlerThread thread = new HandlerThread("test");
+ thread.start();
+ final Handler handler = thread.getThreadHandler();
+ final CountDownLatch latch = new CountDownLatch(1);
+
+ PerfettoTrace.Session session = new PerfettoTrace.Session(true,
+ getTraceConfig("mq").toByteArray());
+
+ handler.sendEmptyMessage(MESSAGE);
+ handler.sendEmptyMessageDelayed(MESSAGE, 10);
+ handler.sendEmptyMessage(MESSAGE);
+ handler.postDelayed(() -> {
+ latch.countDown();
+ }, 10);
+ assertThat(latch.await(100, TimeUnit.MILLISECONDS)).isTrue();
+
+ byte[] traceBytes = session.close();
+
+ Trace trace = Trace.parseFrom(traceBytes);
+
+ boolean hasTrackEvent = false;
+ int instantCount = 0;
+ int counterCount = 0;
+ int beginCount = 0;
+ int endCount = 0;
+
+ Set<Long> flowIds = new ArraySet<>();
+ for (TracePacket packet: trace.getPacketList()) {
+ TrackEvent event;
+ if (packet.hasTrackEvent()) {
+ hasTrackEvent = true;
+ event = packet.getTrackEvent();
+ } else {
+ continue;
+ }
+
+ List<DebugAnnotation> annotations = event.getDebugAnnotationsList();
+ switch (event.getType()) {
+ case TrackEvent.Type.TYPE_INSTANT:
+ if (annotations.get(2).getIntValue() == MESSAGE) {
+ instantCount++;
+ assertThat(annotations.get(0).getStringValue()).isEqualTo("test");
+ assertThat(event.getFlowIdsCount()).isEqualTo(1);
+ flowIds.addAll(event.getFlowIdsList());
+ }
+ break;
+ case TrackEvent.Type.TYPE_COUNTER:
+ counterCount++;
+ break;
+ case TrackEvent.Type.TYPE_SLICE_BEGIN:
+ annotations = event.getDebugAnnotationsList();
+ if (flowIds.containsAll(event.getTerminatingFlowIdsList())) {
+ beginCount++;
+ assertThat(event.getTerminatingFlowIdsCount()).isEqualTo(1);
+ }
+ break;
+ case TrackEvent.Type.TYPE_SLICE_END:
+ endCount++;
+ break;
+ default:
+ break;
+ }
+ collectInternedData(packet);
+ }
+
+ assertThat(hasTrackEvent).isTrue();
+ assertThat(mCategoryNames).contains("mq");
+ assertThat(mEventNames).contains("message_queue_send");
+ assertThat(mEventNames).contains("message_queue_receive");
+ assertThat(mDebugAnnotationNames).contains("what");
+ assertThat(mDebugAnnotationNames).contains("delay");
+ assertThat(instantCount).isEqualTo(MESSAGE_COUNT);
+ assertThat(beginCount).isEqualTo(MESSAGE_COUNT);
+ assertThat(endCount).isAtLeast(MESSAGE_COUNT);
+ assertThat(counterCount).isAtLeast(MESSAGE_COUNT);
+ }
+
private TrackEvent getTrackEvent(Trace trace, int idx) {
int curIdx = 0;
for (TracePacket packet: trace.getPacketList()) {
diff --git a/core/tests/coretests/src/android/view/ViewRootImplTest.java b/core/tests/coretests/src/android/view/ViewRootImplTest.java
index c40137f1bd34..a289df0441e5 100644
--- a/core/tests/coretests/src/android/view/ViewRootImplTest.java
+++ b/core/tests/coretests/src/android/view/ViewRootImplTest.java
@@ -16,6 +16,8 @@
package android.view;
+import static android.app.UiModeManager.MODE_NIGHT_NO;
+import static android.app.UiModeManager.MODE_NIGHT_YES;
import static android.util.SequenceUtils.getInitSeq;
import static android.view.HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING;
import static android.view.InputDevice.SOURCE_ROTARY_ENCODER;
@@ -67,8 +69,10 @@ import static org.junit.Assume.assumeTrue;
import android.annotation.NonNull;
import android.app.Instrumentation;
import android.app.UiModeManager;
+import android.app.UiModeManager.ForceInvertType;
import android.content.Context;
import android.graphics.ForceDarkType;
+import android.graphics.ForceDarkType.ForceDarkTypeDef;
import android.graphics.Rect;
import android.hardware.display.DisplayManagerGlobal;
import android.os.Binder;
@@ -93,9 +97,12 @@ import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;
import com.android.compatibility.common.util.ShellIdentityUtils;
+import com.android.compatibility.common.util.TestUtils;
import com.android.cts.input.BlockingQueueEventVerifier;
import com.android.window.flags.Flags;
+import com.google.common.truth.Expect;
+
import org.hamcrest.Matcher;
import org.junit.After;
import org.junit.AfterClass;
@@ -124,6 +131,8 @@ public class ViewRootImplTest {
@Rule
public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+ @Rule
+ public final Expect mExpect = Expect.create();
private ViewRootImpl mViewRootImpl;
private View mView;
@@ -1507,49 +1516,34 @@ public class ViewRootImplTest {
}
@Test
- public void forceInvertOffDarkThemeOff_forceDarkModeDisabled() {
- mSetFlagsRule.enableFlags(FLAG_FORCE_INVERT_COLOR);
- ShellIdentityUtils.invokeWithShellPermissions(() -> {
- Settings.Secure.putInt(
- sContext.getContentResolver(),
- Settings.Secure.ACCESSIBILITY_FORCE_INVERT_COLOR_ENABLED,
- /* value= */ 0
- );
- var uiModeManager = sContext.getSystemService(UiModeManager.class);
- uiModeManager.setNightMode(UiModeManager.MODE_NIGHT_NO);
- });
-
- sInstrumentation.runOnMainSync(() ->
- mViewRootImpl.updateConfiguration(sContext.getDisplayNoVerify().getDisplayId())
- );
-
- assertThat(mViewRootImpl.determineForceDarkType()).isEqualTo(ForceDarkType.NONE);
- }
-
- @Test
- public void forceInvertOnDarkThemeOff_forceDarkModeEnabled() {
- mSetFlagsRule.enableFlags(FLAG_FORCE_INVERT_COLOR);
- ShellIdentityUtils.invokeWithShellPermissions(() -> {
- Settings.Secure.putInt(
- sContext.getContentResolver(),
- Settings.Secure.ACCESSIBILITY_FORCE_INVERT_COLOR_ENABLED,
- /* value= */ 1
- );
- var uiModeManager = sContext.getSystemService(UiModeManager.class);
- uiModeManager.setNightMode(UiModeManager.MODE_NIGHT_NO);
- });
-
- sInstrumentation.runOnMainSync(() ->
- mViewRootImpl.updateConfiguration(sContext.getDisplayNoVerify().getDisplayId())
- );
-
- assertThat(mViewRootImpl.determineForceDarkType())
- .isEqualTo(ForceDarkType.FORCE_INVERT_COLOR_DARK);
+ @RequiresFlagsEnabled(FLAG_FORCE_INVERT_COLOR)
+ public void updateConfiguration_returnsExpectedForceDarkMode() {
+ waitForSystemNightModeActivated(true);
+
+ verifyForceDarkType(/* isAppInNightMode= */ true, /* isForceInvertEnabled= */ true,
+ UiModeManager.FORCE_INVERT_TYPE_DARK, ForceDarkType.FORCE_INVERT_COLOR_DARK);
+ verifyForceDarkType(/* isAppInNightMode= */ true, /* isForceInvertEnabled= */ false,
+ UiModeManager.FORCE_INVERT_TYPE_OFF, ForceDarkType.NONE);
+ verifyForceDarkType(/* isAppInNightMode= */ false, /* isForceInvertEnabled= */ true,
+ UiModeManager.FORCE_INVERT_TYPE_DARK, ForceDarkType.FORCE_INVERT_COLOR_DARK);
+ verifyForceDarkType(/* isAppInNightMode= */ false, /* isForceInvertEnabled= */ false,
+ UiModeManager.FORCE_INVERT_TYPE_OFF, ForceDarkType.NONE);
+
+ waitForSystemNightModeActivated(false);
+
+ verifyForceDarkType(/* isAppInNightMode= */ true, /* isForceInvertEnabled= */ true,
+ UiModeManager.FORCE_INVERT_TYPE_OFF, ForceDarkType.NONE);
+ verifyForceDarkType(/* isAppInNightMode= */ true, /* isForceInvertEnabled= */ false,
+ UiModeManager.FORCE_INVERT_TYPE_OFF, ForceDarkType.NONE);
+ verifyForceDarkType(/* isAppInNightMode= */ false, /* isForceInvertEnabled= */ true,
+ UiModeManager.FORCE_INVERT_TYPE_OFF, ForceDarkType.NONE);
+ verifyForceDarkType(/* isAppInNightMode= */ false, /* isForceInvertEnabled= */ false,
+ UiModeManager.FORCE_INVERT_TYPE_OFF, ForceDarkType.NONE);
}
@Test
+ @EnableFlags(FLAG_FORCE_INVERT_COLOR)
public void forceInvertOffForceDarkOff_forceDarkModeDisabled() {
- mSetFlagsRule.enableFlags(FLAG_FORCE_INVERT_COLOR);
ShellIdentityUtils.invokeWithShellPermissions(() -> {
Settings.Secure.putInt(
sContext.getContentResolver(),
@@ -1562,15 +1556,14 @@ public class ViewRootImplTest {
});
sInstrumentation.runOnMainSync(() ->
- mViewRootImpl.updateConfiguration(sContext.getDisplayNoVerify().getDisplayId())
- );
+ mViewRootImpl.updateConfiguration(sContext.getDisplayNoVerify().getDisplayId()));
assertThat(mViewRootImpl.determineForceDarkType()).isEqualTo(ForceDarkType.NONE);
}
@Test
+ @EnableFlags(FLAG_FORCE_INVERT_COLOR)
public void forceInvertOffForceDarkOn_forceDarkModeEnabled() {
- mSetFlagsRule.enableFlags(FLAG_FORCE_INVERT_COLOR);
ShellIdentityUtils.invokeWithShellPermissions(() -> {
Settings.Secure.putInt(
sContext.getContentResolver(),
@@ -1582,8 +1575,7 @@ public class ViewRootImplTest {
});
sInstrumentation.runOnMainSync(() ->
- mViewRootImpl.updateConfiguration(sContext.getDisplayNoVerify().getDisplayId())
- );
+ mViewRootImpl.updateConfiguration(sContext.getDisplayNoVerify().getDisplayId()));
assertThat(mViewRootImpl.determineForceDarkType()).isEqualTo(ForceDarkType.FORCE_DARK);
}
@@ -1790,4 +1782,39 @@ public class ViewRootImplTest {
() -> view.getViewTreeObserver().removeOnDrawListener(listener));
}
}
+
+ private void waitForSystemNightModeActivated(boolean active) {
+ ShellIdentityUtils.invokeWithShellPermissions(() ->
+ sInstrumentation.runOnMainSync(() -> {
+ var uiModeManager = sContext.getSystemService(UiModeManager.class);
+ uiModeManager.setNightModeActivated(active);
+ }));
+ sInstrumentation.waitForIdleSync();
+ }
+
+ private void verifyForceDarkType(boolean isAppInNightMode, boolean isForceInvertEnabled,
+ @ForceInvertType int expectedForceInvertType,
+ @ForceDarkTypeDef int expectedForceDarkType) {
+ var uiModeManager = sContext.getSystemService(UiModeManager.class);
+ ShellIdentityUtils.invokeWithShellPermissions(() -> {
+ uiModeManager.setApplicationNightMode(
+ isAppInNightMode ? MODE_NIGHT_YES : MODE_NIGHT_NO);
+ Settings.Secure.putInt(
+ sContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_FORCE_INVERT_COLOR_ENABLED,
+ isForceInvertEnabled ? 1 : 0);
+ });
+
+ sInstrumentation.runOnMainSync(() ->
+ mViewRootImpl.updateConfiguration(sContext.getDisplayNoVerify().getDisplayId()));
+ try {
+ TestUtils.waitUntil("Waiting for force invert state changed",
+ () -> (uiModeManager.getForceInvertState() == expectedForceInvertType));
+ } catch (Exception e) {
+ Log.e(TAG, "Unexpected error trying to apply force invert state. " + e);
+ e.printStackTrace();
+ }
+
+ mExpect.that(mViewRootImpl.determineForceDarkType()).isEqualTo(expectedForceDarkType);
+ }
}
diff --git a/core/tests/overlaytests/host/src/com/android/server/om/hosttest/InstallOverlayTests.java b/core/tests/overlaytests/host/src/com/android/server/om/hosttest/InstallOverlayTests.java
index 36c73e2e979e..c42ddd3412be 100644
--- a/core/tests/overlaytests/host/src/com/android/server/om/hosttest/InstallOverlayTests.java
+++ b/core/tests/overlaytests/host/src/com/android/server/om/hosttest/InstallOverlayTests.java
@@ -44,8 +44,11 @@ public class InstallOverlayTests extends BaseHostJUnit4Test {
"com.android.server.om.hosttest.update_overlay_test";
private static final String DEVICE_TEST_CLS = DEVICE_TEST_PKG + ".UpdateOverlayTest";
+ private int mCurrentUserid;
+
@Before
public void ensureNoOverlays() throws Exception {
+ mCurrentUserid = getDevice().getCurrentUser();
// Make sure we're starting with a clean slate.
for (String pkg : ALL_PACKAGES) {
assertFalse(pkg + " should not be installed", isPackageInstalled(pkg));
@@ -62,7 +65,7 @@ public class InstallOverlayTests extends BaseHostJUnit4Test {
@After
public void uninstallOverlays() throws Exception {
for (String pkg : ALL_PACKAGES) {
- uninstallPackage(pkg);
+ getDevice().uninstallPackageForUser(pkg, mCurrentUserid);
}
}
@@ -166,7 +169,7 @@ public class InstallOverlayTests extends BaseHostJUnit4Test {
installPackage("OverlayHostTests_AppOverlayV1.apk");
assertTrue(getDevice().executeShellCommand("cat /data/system/overlays.xml")
.contains(APP_OVERLAY_PACKAGE_NAME));
- uninstallPackage(APP_OVERLAY_PACKAGE_NAME);
+ getDevice().uninstallPackageForUser(APP_OVERLAY_PACKAGE_NAME, mCurrentUserid);
delay();
assertFalse(getDevice().executeShellCommand("cat /data/system/overlays.xml")
.contains(APP_OVERLAY_PACKAGE_NAME));
@@ -200,12 +203,12 @@ public class InstallOverlayTests extends BaseHostJUnit4Test {
}
private void installPackage(String pkg) throws Exception {
- super.installPackage(pkg);
+ super.installPackageAsUser(pkg, true /* grantPermission */, mCurrentUserid);
delay();
}
private void installInstantPackage(String pkg) throws Exception {
- super.installPackage(pkg, "--instant");
+ super.installPackageAsUser(pkg, true /* grantPermission */, mCurrentUserid, "--instant");
delay();
}
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleControllerBubbleBarTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleControllerBubbleBarTest.kt
index 77acbde7a465..bce6c5999a75 100644
--- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleControllerBubbleBarTest.kt
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleControllerBubbleBarTest.kt
@@ -47,6 +47,7 @@ import com.android.wm.shell.draganddrop.DragAndDropController
import com.android.wm.shell.shared.TransactionPool
import com.android.wm.shell.shared.bubbles.BubbleBarLocation
import com.android.wm.shell.shared.bubbles.BubbleBarUpdate
+import com.android.wm.shell.shared.bubbles.DeviceConfig
import com.android.wm.shell.sysui.ShellCommandHandler
import com.android.wm.shell.sysui.ShellController
import com.android.wm.shell.sysui.ShellInit
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubblePositionerTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubblePositionerTest.kt
index 9711889ad028..ec1add21ebf8 100644
--- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubblePositionerTest.kt
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubblePositionerTest.kt
@@ -31,6 +31,7 @@ import com.android.internal.protolog.ProtoLog
import com.android.wm.shell.R
import com.android.wm.shell.bubbles.BubblePositioner.MAX_HEIGHT
import com.android.wm.shell.shared.bubbles.BubbleBarLocation
+import com.android.wm.shell.shared.bubbles.DeviceConfig
import com.google.common.truth.Truth.assertThat
import com.google.common.util.concurrent.MoreExecutors.directExecutor
import org.junit.Before
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelperTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelperTest.kt
index d3cfbd00c4a3..68b3d8822525 100644
--- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelperTest.kt
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelperTest.kt
@@ -42,10 +42,10 @@ import com.android.wm.shell.bubbles.BubbleLogger
import com.android.wm.shell.bubbles.BubbleOverflow
import com.android.wm.shell.bubbles.BubblePositioner
import com.android.wm.shell.bubbles.BubbleTaskView
-import com.android.wm.shell.bubbles.DeviceConfig
import com.android.wm.shell.bubbles.FakeBubbleExpandedViewManager
import com.android.wm.shell.bubbles.FakeBubbleFactory
import com.android.wm.shell.common.TestShellExecutor
+import com.android.wm.shell.shared.bubbles.DeviceConfig
import com.android.wm.shell.taskview.TaskView
import com.android.wm.shell.taskview.TaskViewController
import com.android.wm.shell.taskview.TaskViewTaskController
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewTest.kt
index 7f65e22736b3..037bd227d33c 100644
--- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewTest.kt
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewTest.kt
@@ -39,11 +39,11 @@ import com.android.wm.shell.bubbles.BubbleLogger
import com.android.wm.shell.bubbles.BubblePositioner
import com.android.wm.shell.bubbles.BubbleTaskView
import com.android.wm.shell.bubbles.BubbleTaskViewFactory
-import com.android.wm.shell.bubbles.DeviceConfig
import com.android.wm.shell.bubbles.FakeBubbleExpandedViewManager
import com.android.wm.shell.bubbles.RegionSamplingProvider
import com.android.wm.shell.bubbles.UiEventSubject.Companion.assertThat
import com.android.wm.shell.common.TestShellExecutor
+import com.android.wm.shell.shared.bubbles.DeviceConfig
import com.android.wm.shell.shared.handles.RegionSamplingHelper
import com.android.wm.shell.taskview.TaskView
import com.android.wm.shell.taskview.TaskViewController
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleExpandedViewPinControllerTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleExpandedViewPinControllerTest.kt
index d4cbe6e10971..1b0e11f7103c 100644
--- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleExpandedViewPinControllerTest.kt
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleExpandedViewPinControllerTest.kt
@@ -30,13 +30,13 @@ import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
import com.android.internal.protolog.ProtoLog
import com.android.wm.shell.R
import com.android.wm.shell.bubbles.BubblePositioner
-import com.android.wm.shell.bubbles.DeviceConfig
import com.android.wm.shell.shared.bubbles.BaseBubblePinController
import com.android.wm.shell.shared.bubbles.BaseBubblePinController.Companion.DROP_TARGET_ALPHA_IN_DURATION
import com.android.wm.shell.shared.bubbles.BaseBubblePinController.Companion.DROP_TARGET_ALPHA_OUT_DURATION
import com.android.wm.shell.shared.bubbles.BubbleBarLocation
import com.android.wm.shell.shared.bubbles.BubbleBarLocation.LEFT
import com.android.wm.shell.shared.bubbles.BubbleBarLocation.RIGHT
+import com.android.wm.shell.shared.bubbles.DeviceConfig
import com.google.common.truth.Truth.assertThat
import org.junit.After
import org.junit.Before
diff --git a/libs/WindowManager/Shell/shared/Android.bp b/libs/WindowManager/Shell/shared/Android.bp
index 261c63948a94..af46ca298efe 100644
--- a/libs/WindowManager/Shell/shared/Android.bp
+++ b/libs/WindowManager/Shell/shared/Android.bp
@@ -74,6 +74,7 @@ java_library {
"**/desktopmode/*.kt",
],
static_libs: [
+ "WindowManager-Shell-shared-AOSP",
"com.android.window.flags.window-aconfig-java",
"wm_shell-shared-utils",
],
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/DeviceConfig.kt b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DeviceConfig.kt
index 929330918174..f479da051e06 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/DeviceConfig.kt
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DeviceConfig.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.wm.shell.bubbles
+package com.android.wm.shell.shared.bubbles
import android.content.Context
import android.content.res.Configuration
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 e196880aad0f..da62be7f142f 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
@@ -18,6 +18,8 @@ package com.android.wm.shell.shared.desktopmode;
import static android.hardware.display.DisplayManager.DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED;
+import static com.android.wm.shell.shared.bubbles.BubbleAnythingFlagHelper.enableBubbleToFullscreen;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
@@ -230,7 +232,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. */
@@ -270,7 +272,8 @@ public class DesktopModeStatus {
* necessarily enabling desktop mode
*/
public static boolean overridesShowAppHandle(@NonNull Context context) {
- return Flags.showAppHandleLargeScreens() && deviceHasLargeScreen(context);
+ return (Flags.showAppHandleLargeScreens() || enableBubbleToFullscreen())
+ && deviceHasLargeScreen(context);
}
/**
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 1a03eb5e4a42..5aed9e910dd3 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
@@ -115,6 +115,7 @@ import com.android.wm.shell.shared.annotations.ShellMainThread;
import com.android.wm.shell.shared.bubbles.BubbleAnythingFlagHelper;
import com.android.wm.shell.shared.bubbles.BubbleBarLocation;
import com.android.wm.shell.shared.bubbles.BubbleBarUpdate;
+import com.android.wm.shell.shared.bubbles.DeviceConfig;
import com.android.wm.shell.sysui.ConfigurationChangeListener;
import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
index 0e2fc3a77c6d..8cf3f7afd46a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
@@ -33,6 +33,7 @@ import com.android.internal.protolog.ProtoLog;
import com.android.launcher3.icons.IconNormalizer;
import com.android.wm.shell.R;
import com.android.wm.shell.shared.bubbles.BubbleBarLocation;
+import com.android.wm.shell.shared.bubbles.DeviceConfig;
/**
* Keeps track of display size, configuration, and specific bubble sizes. One place for all
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
index 2b2112fd461f..3babc0d801c4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
@@ -91,6 +91,7 @@ import com.android.wm.shell.common.FloatingContentCoordinator;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.shared.animation.Interpolators;
import com.android.wm.shell.shared.animation.PhysicsAnimator;
+import com.android.wm.shell.shared.bubbles.DeviceConfig;
import com.android.wm.shell.shared.bubbles.DismissView;
import com.android.wm.shell.shared.bubbles.RelativeTouchListener;
import com.android.wm.shell.shared.magnetictarget.MagnetizedObject;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
index f3f8d6f96a42..b1035bc177a1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
@@ -45,11 +45,11 @@ import com.android.wm.shell.bubbles.BubbleLogger;
import com.android.wm.shell.bubbles.BubbleOverflow;
import com.android.wm.shell.bubbles.BubblePositioner;
import com.android.wm.shell.bubbles.BubbleViewProvider;
-import com.android.wm.shell.bubbles.DeviceConfig;
import com.android.wm.shell.bubbles.DismissViewUtils;
import com.android.wm.shell.bubbles.bar.BubbleBarExpandedViewDragController.DragListener;
import com.android.wm.shell.shared.bubbles.BaseBubblePinController;
import com.android.wm.shell.shared.bubbles.BubbleBarLocation;
+import com.android.wm.shell.shared.bubbles.DeviceConfig;
import com.android.wm.shell.shared.bubbles.DismissView;
import kotlin.Unit;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipDesktopState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipDesktopState.java
new file mode 100644
index 000000000000..c10c2c905c97
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipDesktopState.java
@@ -0,0 +1,159 @@
+/*
+ * 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.common.pip;
+
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+
+import android.app.ActivityManager;
+import android.window.DisplayAreaInfo;
+import android.window.WindowContainerToken;
+import android.window.WindowContainerTransaction;
+
+import com.android.window.flags.Flags;
+import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
+import com.android.wm.shell.desktopmode.DesktopRepository;
+import com.android.wm.shell.desktopmode.DesktopUserRepositories;
+import com.android.wm.shell.desktopmode.desktopwallpaperactivity.DesktopWallpaperActivityTokenProvider;
+import com.android.wm.shell.pip2.phone.PipTransition;
+
+import java.util.Optional;
+
+/** Helper class for PiP on Desktop Mode. */
+public class PipDesktopState {
+ private final PipDisplayLayoutState mPipDisplayLayoutState;
+ private final Optional<DesktopUserRepositories> mDesktopUserRepositoriesOptional;
+ private final Optional<DesktopWallpaperActivityTokenProvider>
+ mDesktopWallpaperActivityTokenProviderOptional;
+ private final RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer;
+
+ public PipDesktopState(PipDisplayLayoutState pipDisplayLayoutState,
+ Optional<DesktopUserRepositories> desktopUserRepositoriesOptional,
+ Optional<DesktopWallpaperActivityTokenProvider>
+ desktopWallpaperActivityTokenProviderOptional,
+ RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer) {
+ mPipDisplayLayoutState = pipDisplayLayoutState;
+ mDesktopUserRepositoriesOptional = desktopUserRepositoriesOptional;
+ mDesktopWallpaperActivityTokenProviderOptional =
+ desktopWallpaperActivityTokenProviderOptional;
+ mRootTaskDisplayAreaOrganizer = rootTaskDisplayAreaOrganizer;
+ }
+
+ /**
+ * Returns whether PiP in Desktop Windowing is enabled by checking the following:
+ * - Desktop Windowing in PiP flag is enabled
+ * - DesktopWallpaperActivityTokenProvider is injected
+ * - DesktopUserRepositories is injected
+ */
+ public boolean isDesktopWindowingPipEnabled() {
+ return Flags.enableDesktopWindowingPip()
+ && mDesktopWallpaperActivityTokenProviderOptional.isPresent()
+ && mDesktopUserRepositoriesOptional.isPresent();
+ }
+
+ /** Returns whether PiP in Connected Displays is enabled by checking the flag. */
+ public boolean isConnectedDisplaysPipEnabled() {
+ return Flags.enableConnectedDisplaysPip();
+ }
+
+ /** Returns whether the display with the PiP task is in freeform windowing mode. */
+ private boolean isDisplayInFreeform() {
+ final DisplayAreaInfo tdaInfo = mRootTaskDisplayAreaOrganizer.getDisplayAreaInfo(
+ mPipDisplayLayoutState.getDisplayId());
+ if (tdaInfo != null) {
+ return tdaInfo.configuration.windowConfiguration.getWindowingMode()
+ == WINDOWING_MODE_FREEFORM;
+ }
+ return false;
+ }
+
+ /** Returns whether PiP is exiting while we're in a Desktop Mode session. */
+ private boolean isPipExitingToDesktopMode() {
+ // Early return if PiP in Desktop Windowing is not supported.
+ if (!isDesktopWindowingPipEnabled()) {
+ return false;
+ }
+ final int displayId = mPipDisplayLayoutState.getDisplayId();
+ return getDesktopRepository().getVisibleTaskCount(displayId) > 0
+ || getDesktopWallpaperActivityTokenProvider().isWallpaperActivityVisible(displayId)
+ || isDisplayInFreeform();
+ }
+
+ /** Returns whether {@param pipTask} would be entering in a Desktop Mode session. */
+ public boolean isPipEnteringInDesktopMode(ActivityManager.RunningTaskInfo pipTask) {
+ // Early return if PiP in Desktop Windowing is not supported.
+ if (!isDesktopWindowingPipEnabled()) {
+ return false;
+ }
+ final DesktopRepository desktopRepository = getDesktopRepository();
+ return desktopRepository.getVisibleTaskCount(pipTask.getDisplayId()) > 0
+ || desktopRepository.isMinimizedPipPresentInDisplay(pipTask.getDisplayId());
+ }
+
+ /**
+ * Invoked when an EXIT_PiP transition is detected in {@link PipTransition}.
+ * Returns whether the PiP exiting should also trigger the active Desktop Mode session to exit.
+ */
+ public boolean shouldExitPipExitDesktopMode() {
+ // Early return if PiP in Desktop Windowing is not supported.
+ if (!isDesktopWindowingPipEnabled()) {
+ return false;
+ }
+ final int displayId = mPipDisplayLayoutState.getDisplayId();
+ return getDesktopRepository().getVisibleTaskCount(displayId) == 0
+ && getDesktopWallpaperActivityTokenProvider().isWallpaperActivityVisible(displayId);
+ }
+
+ /**
+ * Returns a {@link WindowContainerTransaction} that reorders the {@link WindowContainerToken}
+ * of the DesktopWallpaperActivity for the display with the given {@param displayId}.
+ */
+ public WindowContainerTransaction getWallpaperActivityTokenWct(int displayId) {
+ return new WindowContainerTransaction().reorder(
+ getDesktopWallpaperActivityTokenProvider().getToken(displayId), /* onTop= */ false);
+ }
+
+ /**
+ * The windowing mode to restore to when resizing out of PIP direction.
+ * Defaults to undefined and can be overridden to restore to an alternate windowing mode.
+ */
+ public int getOutPipWindowingMode() {
+ // If we are exiting PiP while the device is in Desktop mode (the task should expand to
+ // freeform windowing mode):
+ // 1) If the display windowing mode is freeform, set windowing mode to UNDEFINED so it will
+ // resolve the windowing mode to the display's windowing mode.
+ // 2) If the display windowing mode is not FREEFORM, set windowing mode to FREEFORM.
+ if (isPipExitingToDesktopMode()) {
+ if (isDisplayInFreeform()) {
+ return WINDOWING_MODE_UNDEFINED;
+ } else {
+ return WINDOWING_MODE_FREEFORM;
+ }
+ }
+
+ // By default, or if the task is going to fullscreen, reset the windowing mode to undefined.
+ return WINDOWING_MODE_UNDEFINED;
+ }
+
+ private DesktopRepository getDesktopRepository() {
+ return mDesktopUserRepositoriesOptional.get().getCurrent();
+ }
+
+ private DesktopWallpaperActivityTokenProvider getDesktopWallpaperActivityTokenProvider() {
+ return mDesktopWallpaperActivityTokenProviderOptional.get();
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/OWNERS
index 752d2fd721a5..8ab53eaa5eea 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/OWNERS
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/OWNERS
@@ -1,2 +1,3 @@
# WM Shell sub-module dagger owners
-jorgegil@google.com \ No newline at end of file
+jorgegil@google.com
+madym@google.com \ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
index 413300612f7d..e7c76bbd91b2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
@@ -31,6 +31,7 @@ import com.android.wm.shell.common.TaskStackListenerImpl;
import com.android.wm.shell.common.pip.PipAppOpsListener;
import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
import com.android.wm.shell.common.pip.PipBoundsState;
+import com.android.wm.shell.common.pip.PipDesktopState;
import com.android.wm.shell.common.pip.PipDisplayLayoutState;
import com.android.wm.shell.common.pip.PipMediaController;
import com.android.wm.shell.common.pip.PipPerfHintController;
@@ -84,14 +85,11 @@ public abstract class Pip2Module {
@NonNull PipDisplayLayoutState pipDisplayLayoutState,
@NonNull PipUiStateChangeController pipUiStateChangeController,
DisplayController displayController,
- Optional<DesktopUserRepositories> desktopUserRepositoriesOptional,
- Optional<DesktopWallpaperActivityTokenProvider>
- desktopWallpaperActivityTokenProviderOptional) {
+ PipDesktopState pipDesktopState) {
return new PipTransition(context, shellInit, shellTaskOrganizer, transitions,
pipBoundsState, null, pipBoundsAlgorithm, pipTaskListener,
pipScheduler, pipStackListenerController, pipDisplayLayoutState,
- pipUiStateChangeController, displayController, desktopUserRepositoriesOptional,
- desktopWallpaperActivityTokenProviderOptional);
+ pipUiStateChangeController, displayController, pipDesktopState);
}
@WMSingleton
@@ -142,13 +140,9 @@ public abstract class Pip2Module {
PipBoundsState pipBoundsState,
@ShellMainThread ShellExecutor mainExecutor,
PipTransitionState pipTransitionState,
- Optional<DesktopUserRepositories> desktopUserRepositoriesOptional,
- Optional<DesktopWallpaperActivityTokenProvider>
- desktopWallpaperActivityTokenProviderOptional,
- RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer) {
+ PipDesktopState pipDesktopState) {
return new PipScheduler(context, pipBoundsState, mainExecutor, pipTransitionState,
- desktopUserRepositoriesOptional, desktopWallpaperActivityTokenProviderOptional,
- rootTaskDisplayAreaOrganizer);
+ pipDesktopState);
}
@WMSingleton
@@ -233,4 +227,17 @@ public abstract class Pip2Module {
return new PipTaskListener(context, shellTaskOrganizer, pipTransitionState,
pipScheduler, pipBoundsState, pipBoundsAlgorithm, mainExecutor);
}
+
+ @WMSingleton
+ @Provides
+ static PipDesktopState providePipDesktopState(
+ PipDisplayLayoutState pipDisplayLayoutState,
+ Optional<DesktopUserRepositories> desktopUserRepositoriesOptional,
+ Optional<DesktopWallpaperActivityTokenProvider>
+ desktopWallpaperActivityTokenProviderOptional,
+ RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer
+ ) {
+ return new PipDesktopState(pipDisplayLayoutState, desktopUserRepositoriesOptional,
+ desktopWallpaperActivityTokenProviderOptional, rootTaskDisplayAreaOrganizer);
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java
index 32ee319a053b..621ccba40db2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java
@@ -59,6 +59,8 @@ import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.shared.bubbles.BubbleAnythingFlagHelper;
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
/**
* Animated visual indicator for Desktop Mode windowing transitions.
@@ -149,12 +151,19 @@ public class DesktopModeVisualIndicator {
// left, and split right for the right edge. This is universal across all drag event types.
if (inputCoordinates.x < 0) return TO_SPLIT_LEFT_INDICATOR;
if (inputCoordinates.x > layout.width()) return TO_SPLIT_RIGHT_INDICATOR;
- // If we are in freeform, we don't want a visible indicator in the "freeform" drag zone.
- // In drags not originating on a freeform caption, we should default to a TO_DESKTOP
- // indicator.
- IndicatorType result = mDragStartState == DragStartState.FROM_FREEFORM
- ? NO_INDICATOR
- : TO_DESKTOP_INDICATOR;
+ IndicatorType result;
+ if (BubbleAnythingFlagHelper.enableBubbleToFullscreen()
+ && !DesktopModeStatus.canEnterDesktopMode(mContext)) {
+ // If desktop is not available, default to "no indicator"
+ result = NO_INDICATOR;
+ } else {
+ // If we are in freeform, we don't want a visible indicator in the "freeform" drag zone.
+ // In drags not originating on a freeform caption, we should default to a TO_DESKTOP
+ // indicator.
+ result = mDragStartState == DragStartState.FROM_FREEFORM
+ ? NO_INDICATOR
+ : TO_DESKTOP_INDICATOR;
+ }
final int transitionAreaWidth = mContext.getResources().getDimensionPixelSize(
com.android.wm.shell.R.dimen.desktop_mode_transition_region_thickness);
// Because drags in freeform use task position for indicator calculation, we need to
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 3f88e7bddd34..53f37659cf49 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
@@ -1018,6 +1018,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()) {
@@ -1528,16 +1545,11 @@ class DesktopTasksController(
private fun addWallpaperActivity(displayId: Int, wct: WindowContainerTransaction) {
logV("addWallpaperActivity")
if (ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER.isTrue()) {
-
- // If the wallpaper activity for this display already exists, let's reorder it to top.
- val wallpaperActivityToken = desktopWallpaperActivityTokenProvider.getToken(displayId)
- if (wallpaperActivityToken != null) {
- wct.reorder(wallpaperActivityToken, /* onTop= */ true)
- return
- }
-
val intent = Intent(context, DesktopWallpaperActivity::class.java)
- if (Flags.enablePerDisplayDesktopWallpaperActivity()) {
+ if (
+ desktopWallpaperActivityTokenProvider.getToken(displayId) == null &&
+ Flags.enablePerDisplayDesktopWallpaperActivity()
+ ) {
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
intent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK)
}
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/pip2/phone/PipScheduler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java
index 21b0820f523a..e17587ff18bc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java
@@ -16,14 +16,10 @@
package com.android.wm.shell.pip2.phone;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
-import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
-
import android.content.Context;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.view.SurfaceControl;
-import android.window.DisplayAreaInfo;
import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
@@ -32,20 +28,14 @@ import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.ProtoLog;
-import com.android.window.flags.Flags;
-import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.pip.PipBoundsState;
-import com.android.wm.shell.desktopmode.DesktopUserRepositories;
-import com.android.wm.shell.desktopmode.desktopwallpaperactivity.DesktopWallpaperActivityTokenProvider;
+import com.android.wm.shell.common.pip.PipDesktopState;
import com.android.wm.shell.pip.PipTransitionController;
import com.android.wm.shell.pip2.PipSurfaceTransactionHelper;
import com.android.wm.shell.pip2.animation.PipAlphaAnimator;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
-import java.util.Objects;
-import java.util.Optional;
-
/**
* Scheduler for Shell initiated PiP transitions and animations.
*/
@@ -56,10 +46,7 @@ public class PipScheduler {
private final PipBoundsState mPipBoundsState;
private final ShellExecutor mMainExecutor;
private final PipTransitionState mPipTransitionState;
- private final Optional<DesktopUserRepositories> mDesktopUserRepositoriesOptional;
- private final Optional<DesktopWallpaperActivityTokenProvider>
- mDesktopWallpaperActivityTokenProviderOptional;
- private final RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer;
+ private final PipDesktopState mPipDesktopState;
private PipTransitionController mPipTransitionController;
private PipSurfaceTransactionHelper.SurfaceControlTransactionFactory
mSurfaceControlTransactionFactory;
@@ -72,18 +59,12 @@ public class PipScheduler {
PipBoundsState pipBoundsState,
ShellExecutor mainExecutor,
PipTransitionState pipTransitionState,
- Optional<DesktopUserRepositories> desktopUserRepositoriesOptional,
- Optional<DesktopWallpaperActivityTokenProvider>
- desktopWallpaperActivityTokenProviderOptional,
- RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer) {
+ PipDesktopState pipDesktopState) {
mContext = context;
mPipBoundsState = pipBoundsState;
mMainExecutor = mainExecutor;
mPipTransitionState = pipTransitionState;
- mDesktopUserRepositoriesOptional = desktopUserRepositoriesOptional;
- mDesktopWallpaperActivityTokenProviderOptional =
- desktopWallpaperActivityTokenProviderOptional;
- mRootTaskDisplayAreaOrganizer = rootTaskDisplayAreaOrganizer;
+ mPipDesktopState = pipDesktopState;
mSurfaceControlTransactionFactory =
new PipSurfaceTransactionHelper.VsyncSurfaceControlTransactionFactory();
@@ -105,7 +86,7 @@ public class PipScheduler {
wct.setBounds(pipTaskToken, null);
// if we are hitting a multi-activity case
// windowing mode change will reparent to original host task
- wct.setWindowingMode(pipTaskToken, getOutPipWindowingMode());
+ wct.setWindowingMode(pipTaskToken, mPipDesktopState.getOutPipWindowingMode());
return wct;
}
@@ -235,55 +216,6 @@ public class PipScheduler {
maybeUpdateMovementBounds();
}
- /** Returns whether the display is in freeform windowing mode. */
- private boolean isDisplayInFreeform() {
- final DisplayAreaInfo tdaInfo = mRootTaskDisplayAreaOrganizer.getDisplayAreaInfo(
- Objects.requireNonNull(mPipTransitionState.getPipTaskInfo()).displayId);
- if (tdaInfo != null) {
- return tdaInfo.configuration.windowConfiguration.getWindowingMode()
- == WINDOWING_MODE_FREEFORM;
- }
- return false;
- }
-
- /** Returns whether PiP is exiting while we're in desktop mode. */
- private boolean isPipExitingToDesktopMode() {
- // Early return if PiP in Desktop Windowing is not supported.
- if (!Flags.enableDesktopWindowingPip() || mDesktopUserRepositoriesOptional.isEmpty()
- || mDesktopWallpaperActivityTokenProviderOptional.isEmpty()) {
- return false;
- }
- final int displayId = Objects.requireNonNull(
- mPipTransitionState.getPipTaskInfo()).displayId;
- return mDesktopUserRepositoriesOptional.get().getCurrent().getVisibleTaskCount(displayId)
- > 0
- || mDesktopWallpaperActivityTokenProviderOptional.get().isWallpaperActivityVisible(
- displayId)
- || isDisplayInFreeform();
- }
-
- /**
- * The windowing mode to restore to when resizing out of PIP direction. Defaults to undefined
- * and can be overridden to restore to an alternate windowing mode.
- */
- private int getOutPipWindowingMode() {
- // If we are exiting PiP while the device is in Desktop mode (the task should expand to
- // freeform windowing mode):
- // 1) If the display windowing mode is freeform, set windowing mode to undefined so it will
- // resolve the windowing mode to the display's windowing mode.
- // 2) If the display windowing mode is not freeform, set windowing mode to freeform.
- if (isPipExitingToDesktopMode()) {
- if (isDisplayInFreeform()) {
- return WINDOWING_MODE_UNDEFINED;
- } else {
- return WINDOWING_MODE_FREEFORM;
- }
- }
-
- // By default, or if the task is going to fullscreen, reset the windowing mode to undefined.
- return WINDOWING_MODE_UNDEFINED;
- }
-
@VisibleForTesting
void setSurfaceControlTransactionFactory(
@NonNull PipSurfaceTransactionHelper.SurfaceControlTransactionFactory factory) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTaskListener.java
index d735375b0fc9..dbcbf3663827 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTaskListener.java
@@ -132,8 +132,9 @@ public class PipTaskListener implements ShellTaskOrganizer.TaskListener,
"onTaskInfoChanged: %s, state=%s oldParams=%s newParams=%s",
taskInfo.topActivity, mPipTransitionState, mPictureInPictureParams, params);
setPictureInPictureParams(params);
+ // Note: params is nullable while mPictureInPictureParams is never null
float newAspectRatio = mPictureInPictureParams.getAspectRatioFloat();
- if (params.hasSetAspectRatio()
+ if (mPictureInPictureParams.hasSetAspectRatio()
&& mPipBoundsAlgorithm.isValidPictureInPictureAspectRatio(newAspectRatio)
&& PipUtils.aspectRatioChanged(newAspectRatio, mPipBoundsState.getAspectRatio())) {
mPipTransitionState.setOnIdlePipTransitionStateRunnable(() -> {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
index 78aa686a3a0e..bb9b479524e5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
@@ -56,19 +56,16 @@ import android.window.WindowContainerTransaction;
import androidx.annotation.Nullable;
import com.android.internal.util.Preconditions;
-import com.android.window.flags.Flags;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.ComponentUtils;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
import com.android.wm.shell.common.pip.PipBoundsState;
+import com.android.wm.shell.common.pip.PipDesktopState;
import com.android.wm.shell.common.pip.PipDisplayLayoutState;
import com.android.wm.shell.common.pip.PipMenuController;
import com.android.wm.shell.common.pip.PipUtils;
-import com.android.wm.shell.desktopmode.DesktopRepository;
-import com.android.wm.shell.desktopmode.DesktopUserRepositories;
-import com.android.wm.shell.desktopmode.desktopwallpaperactivity.DesktopWallpaperActivityTokenProvider;
import com.android.wm.shell.pip.PipTransitionController;
import com.android.wm.shell.pip2.PipSurfaceTransactionHelper;
import com.android.wm.shell.pip2.animation.PipAlphaAnimator;
@@ -79,8 +76,6 @@ import com.android.wm.shell.shared.pip.PipContentOverlay;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
-import java.util.Optional;
-
/**
* Implementation of transitions for PiP on phone.
*/
@@ -117,9 +112,7 @@ public class PipTransition extends PipTransitionController implements
private final PipDisplayLayoutState mPipDisplayLayoutState;
private final DisplayController mDisplayController;
private final PipSurfaceTransactionHelper mPipSurfaceTransactionHelper;
- private final Optional<DesktopUserRepositories> mDesktopUserRepositoriesOptional;
- private final Optional<DesktopWallpaperActivityTokenProvider>
- mDesktopWallpaperActivityTokenProviderOptional;
+ private final PipDesktopState mPipDesktopState;
//
// Transition caches
@@ -159,9 +152,7 @@ public class PipTransition extends PipTransitionController implements
PipDisplayLayoutState pipDisplayLayoutState,
PipUiStateChangeController pipUiStateChangeController,
DisplayController displayController,
- Optional<DesktopUserRepositories> desktopUserRepositoriesOptional,
- Optional<DesktopWallpaperActivityTokenProvider>
- desktopWallpaperActivityTokenProviderOptional) {
+ PipDesktopState pipDesktopState) {
super(shellInit, shellTaskOrganizer, transitions, pipBoundsState, pipMenuController,
pipBoundsAlgorithm);
@@ -174,9 +165,7 @@ public class PipTransition extends PipTransitionController implements
mPipDisplayLayoutState = pipDisplayLayoutState;
mDisplayController = displayController;
mPipSurfaceTransactionHelper = new PipSurfaceTransactionHelper(mContext);
- mDesktopUserRepositoriesOptional = desktopUserRepositoriesOptional;
- mDesktopWallpaperActivityTokenProviderOptional =
- desktopWallpaperActivityTokenProviderOptional;
+ mPipDesktopState = pipDesktopState;
}
@Override
@@ -802,7 +791,7 @@ public class PipTransition extends PipTransitionController implements
&& getFixedRotationDelta(info, pipTaskChange) == ROTATION_90) {
adjustedSourceRectHint.offset(cutoutInsets.left, cutoutInsets.top);
}
- if (Flags.enableDesktopWindowingPip()) {
+ if (mPipDesktopState.isDesktopWindowingPipEnabled()) {
adjustedSourceRectHint.offset(-pipActivityChange.getStartAbsBounds().left,
-pipActivityChange.getStartAbsBounds().top);
}
@@ -862,7 +851,7 @@ public class PipTransition extends PipTransitionController implements
// If PiP is enabled on Connected Displays, update PipDisplayLayoutState to have the correct
// display info that PiP is entering in.
- if (Flags.enableConnectedDisplaysPip()) {
+ if (mPipDesktopState.isConnectedDisplaysPipEnabled()) {
final DisplayLayout displayLayout = mDisplayController.getDisplayLayout(
pipTask.displayId);
if (displayLayout != null) {
@@ -910,12 +899,7 @@ public class PipTransition extends PipTransitionController implements
// Since opening a new task while in Desktop Mode always first open in Fullscreen
// until DesktopMode Shell code resolves it to Freeform, PipTransition will get a
// possibility to handle it also. In this case return false to not have it enter PiP.
- final boolean isInDesktopSession = !mDesktopUserRepositoriesOptional.isEmpty()
- && (mDesktopUserRepositoriesOptional.get().getCurrent().getVisibleTaskCount(
- pipTask.displayId) > 0
- || mDesktopUserRepositoriesOptional.get().getCurrent()
- .isMinimizedPipPresentInDisplay(pipTask.displayId));
- if (isInDesktopSession) {
+ if (mPipDesktopState.isPipEnteringInDesktopMode(pipTask)) {
return false;
}
@@ -1089,26 +1073,13 @@ public class PipTransition extends PipTransitionController implements
"Unexpected bundle for " + mPipTransitionState);
break;
case PipTransitionState.EXITED_PIP:
- final TaskInfo pipTask = mPipTransitionState.getPipTaskInfo();
- final boolean desktopPipEnabled = Flags.enableDesktopWindowingPip()
- && mDesktopUserRepositoriesOptional.isPresent()
- && mDesktopWallpaperActivityTokenProviderOptional.isPresent();
- if (desktopPipEnabled && pipTask != null) {
- final DesktopRepository desktopRepository =
- mDesktopUserRepositoriesOptional.get().getCurrent();
- final boolean wallpaperIsVisible =
- mDesktopWallpaperActivityTokenProviderOptional.get()
- .isWallpaperActivityVisible(pipTask.displayId);
- if (desktopRepository.getVisibleTaskCount(pipTask.displayId) == 0
- && wallpaperIsVisible) {
- mTransitions.startTransition(
- TRANSIT_TO_BACK,
- new WindowContainerTransaction().reorder(
- mDesktopWallpaperActivityTokenProviderOptional.get()
- .getToken(pipTask.displayId), /* onTop= */ false),
- null
- );
- }
+ if (mPipDesktopState.shouldExitPipExitDesktopMode()) {
+ mTransitions.startTransition(
+ TRANSIT_TO_BACK,
+ mPipDesktopState.getWallpaperActivityTokenWct(
+ mPipTransitionState.getPipTaskInfo().getDisplayId()),
+ null /* firstHandler */
+ );
}
mPipTransitionState.setPinnedTaskLeash(null);
mPipTransitionState.setPipTaskInfo(null);
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 6a2a7b6becdc..fb4ce13c441f 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
@@ -131,6 +131,7 @@ import com.android.wm.shell.recents.RecentsTransitionStateListener;
import com.android.wm.shell.shared.FocusTransitionListener;
import com.android.wm.shell.shared.annotations.ShellBackgroundThread;
import com.android.wm.shell.shared.annotations.ShellMainThread;
+import com.android.wm.shell.shared.bubbles.BubbleAnythingFlagHelper;
import com.android.wm.shell.shared.desktopmode.DesktopModeCompatPolicy;
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource;
@@ -1439,13 +1440,17 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
mDragToDesktopAnimationStartBounds.set(
relevantDecor.mTaskInfo.configuration.windowConfiguration.getBounds());
boolean dragFromStatusBarAllowed = false;
+ final int windowingMode = relevantDecor.mTaskInfo.getWindowingMode();
if (DesktopModeStatus.canEnterDesktopMode(mContext)) {
// In proto2 any full screen or multi-window task can be dragged to
// freeform.
- final int windowingMode = relevantDecor.mTaskInfo.getWindowingMode();
dragFromStatusBarAllowed = windowingMode == WINDOWING_MODE_FULLSCREEN
|| windowingMode == WINDOWING_MODE_MULTI_WINDOW;
}
+ if (BubbleAnythingFlagHelper.enableBubbleToFullscreen()) {
+ // TODO(b/388851898): add support for split screen (multi-window wm mode)
+ dragFromStatusBarAllowed = windowingMode == WINDOWING_MODE_FULLSCREEN;
+ }
final boolean shouldStartTransitionDrag =
relevantDecor.checkTouchEventInFocusedCaptionHandle(ev)
|| DesktopModeFlags.ENABLE_HANDLE_INPUT_FIX.isTrue();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
index c1a6240516c1..afb234899339 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
@@ -483,7 +483,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
boolean hasGlobalFocus, @NonNull Region displayExclusionRegion) {
Trace.beginSection("DesktopModeWindowDecoration#relayout");
- if (Flags.enableDesktopWindowingAppToWeb()) {
+ if (DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_APP_TO_WEB.isTrue()) {
setCapturedLink(taskInfo.capturedLink, taskInfo.capturedLinkTimestamp);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt
index 32a2f8294877..1d9564948772 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt
@@ -252,6 +252,7 @@ class HandleMenu(
view = handleMenuView.rootView,
forciblyShownTypes = if (forceShowSystemBars) { systemBars() } else { 0 },
ignoreCutouts = Flags.showAppHandleLargeScreens()
+ || BubbleAnythingFlagHelper.enableBubbleToFullscreen()
)
} else {
parentDecor.addWindow(
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleViewHolder.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleViewHolder.kt
index 1264c013faf5..2948fdaf16af 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleViewHolder.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleViewHolder.kt
@@ -41,6 +41,7 @@ import com.android.internal.policy.SystemBarUtils
import com.android.window.flags.Flags
import com.android.wm.shell.R
import com.android.wm.shell.shared.animation.Interpolators
+import com.android.wm.shell.shared.bubbles.BubbleAnythingFlagHelper
import com.android.wm.shell.windowdecor.WindowManagerWrapper
import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalSystemViewContainer
@@ -146,6 +147,7 @@ internal class AppHandleViewHolder(
taskInfo.taskId, handlePosition.x, handlePosition.y, handleWidth, handleHeight,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
ignoreCutouts = Flags.showAppHandleLargeScreens()
+ || BubbleAnythingFlagHelper.enableBubbleToFullscreen()
)
val view = statusBarInputLayer?.view ?: error("Unable to find statusBarInputLayer View")
val lp = statusBarInputLayer?.lp ?: error("Unable to find statusBarInputLayer " +
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 ab1ac1a0efa3..f767861addf9 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
@@ -19,7 +19,7 @@ package com.android.wm.shell.flicker
import android.tools.PlatformConsts.DESKTOP_MODE_MINIMUM_WINDOW_HEIGHT
import android.tools.PlatformConsts.DESKTOP_MODE_MINIMUM_WINDOW_WIDTH
import android.tools.flicker.AssertionInvocationGroup
-import android.tools.flicker.assertors.assertions.AppLayerIncreasesInSize
+import android.tools.flicker.assertors.assertions.ResizeVeilKeepsIncreasingInSize
import android.tools.flicker.assertors.assertions.AppLayerIsInvisibleAtEnd
import android.tools.flicker.assertors.assertions.AppLayerIsVisibleAlways
import android.tools.flicker.assertors.assertions.AppLayerIsVisibleAtStart
@@ -36,6 +36,7 @@ import android.tools.flicker.assertors.assertions.AppWindowHasMaxDisplayHeight
import android.tools.flicker.assertors.assertions.AppWindowHasMaxDisplayWidth
import android.tools.flicker.assertors.assertions.AppWindowHasSizeOfAtLeast
import android.tools.flicker.assertors.assertions.AppWindowInsideDisplayBoundsAtEnd
+import android.tools.flicker.assertors.assertions.AppWindowIsBiggerThanInitialBoundsAtEnd
import android.tools.flicker.assertors.assertions.AppWindowIsInvisibleAtEnd
import android.tools.flicker.assertors.assertions.AppWindowIsVisibleAlways
import android.tools.flicker.assertors.assertions.AppWindowMaintainsAspectRatioAlways
@@ -168,9 +169,12 @@ class DesktopModeFlickerScenarios {
TaggedCujTransitionMatcher(associatedTransitionRequired = false)
)
.build(),
- // TODO(373638597) Add AppLayerIncreasesInSize assertion
- assertions = AssertionTemplates.DESKTOP_MODE_APP_VISIBILITY_ASSERTIONS
- )
+ assertions = AssertionTemplates.DESKTOP_MODE_APP_VISIBILITY_ASSERTIONS +
+ listOf(
+ ResizeVeilKeepsIncreasingInSize(DESKTOP_MODE_APP),
+ AppWindowIsBiggerThanInitialBoundsAtEnd(DESKTOP_MODE_APP),
+ ).associateBy({ it }, { AssertionInvocationGroup.BLOCKING })
+ )
val EDGE_RESIZE =
FlickerConfigEntry(
@@ -184,7 +188,8 @@ class DesktopModeFlickerScenarios {
.build(),
assertions = AssertionTemplates.DESKTOP_MODE_APP_VISIBILITY_ASSERTIONS +
listOf(
- AppLayerIncreasesInSize(DESKTOP_MODE_APP),
+ ResizeVeilKeepsIncreasingInSize(DESKTOP_MODE_APP),
+ AppWindowIsBiggerThanInitialBoundsAtEnd(DESKTOP_MODE_APP),
).associateBy({ it }, { AssertionInvocationGroup.BLOCKING }),
)
@@ -223,9 +228,9 @@ class DesktopModeFlickerScenarios {
assertions =
AssertionTemplates.DESKTOP_MODE_APP_VISIBILITY_ASSERTIONS +
listOf(
- // TODO(373638597) Add AppLayerIncreasesInSize assertion
AppWindowHasMaxDisplayHeight(DESKTOP_MODE_APP),
- AppWindowHasMaxDisplayWidth(DESKTOP_MODE_APP)
+ AppWindowHasMaxDisplayWidth(DESKTOP_MODE_APP),
+ ResizeVeilKeepsIncreasingInSize(DESKTOP_MODE_APP),
).associateBy({ it }, { AssertionInvocationGroup.BLOCKING }),
)
@@ -368,7 +373,7 @@ class DesktopModeFlickerScenarios {
),
assertions = AssertionTemplates.DESKTOP_MODE_APP_VISIBILITY_ASSERTIONS +
listOf(
- AppLayerIncreasesInSize(DESKTOP_MODE_APP),
+ ResizeVeilKeepsIncreasingInSize(DESKTOP_MODE_APP),
AppWindowHasMaxDisplayHeight(DESKTOP_MODE_APP),
AppWindowHasMaxDisplayWidth(DESKTOP_MODE_APP)
).associateBy({ it }, { AssertionInvocationGroup.BLOCKING }),
@@ -393,7 +398,7 @@ class DesktopModeFlickerScenarios {
assertions =
AssertionTemplates.DESKTOP_MODE_APP_VISIBILITY_ASSERTIONS +
listOf(
- AppLayerIncreasesInSize(DESKTOP_MODE_APP),
+ ResizeVeilKeepsIncreasingInSize(DESKTOP_MODE_APP),
AppWindowMaintainsAspectRatioAlways(DESKTOP_MODE_APP),
AppWindowHasMaxBoundsInOnlyOneDimension(DESKTOP_MODE_APP)
).associateBy({ it }, { AssertionInvocationGroup.BLOCKING }),
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/OpenAppWithExternalDisplayConnectedTest.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/OpenAppWithExternalDisplayConnectedTest.kt
new file mode 100644
index 000000000000..cc9a799fb50c
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/OpenAppWithExternalDisplayConnectedTest.kt
@@ -0,0 +1,27 @@
+/*
+ * 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.functional
+
+import android.platform.test.annotations.Postsubmit
+import com.android.wm.shell.scenarios.OpenAppWithExternalDisplayConnected
+import org.junit.runner.RunWith
+import org.junit.runners.BlockJUnit4ClassRunner
+
+/* Functional test for [OpenAppWithExternalDisplayConnected]. */
+@RunWith(BlockJUnit4ClassRunner::class)
+@Postsubmit
+class OpenAppWithExternalDisplayConnectedTest : OpenAppWithExternalDisplayConnected()
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/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/OpenAppWithExternalDisplayConnected.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/OpenAppWithExternalDisplayConnected.kt
new file mode 100644
index 000000000000..81c46f13b384
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/OpenAppWithExternalDisplayConnected.kt
@@ -0,0 +1,103 @@
+/*
+ * 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.scenarios
+
+import android.app.Instrumentation
+import android.content.Context
+import android.hardware.display.DisplayManager
+import android.hardware.display.VirtualDisplay
+import android.tools.NavBar
+import android.tools.Rotation
+import android.tools.flicker.rules.ChangeDisplayOrientationRule
+import android.tools.traces.parsers.WindowManagerStateHelper
+import android.util.DisplayMetrics
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.server.wm.flicker.helpers.DesktopModeAppHelper
+import com.android.server.wm.flicker.helpers.SimpleAppHelper
+import com.android.window.flags.Flags
+import com.android.wm.shell.ExtendedDisplaySettingsSession
+import com.android.wm.shell.Utils
+import org.junit.After
+import org.junit.Assume
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+
+/**
+ * Base scenario test for launching an app in desktop mode by default when an external display is
+ * connected.
+ */
+@Ignore("Test Base Class")
+abstract class OpenAppWithExternalDisplayConnected
+constructor(private val rotation: Rotation = Rotation.ROTATION_0) {
+ private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+ private val tapl = LauncherInstrumentation()
+ private val wmHelper = WindowManagerStateHelper(instrumentation)
+ private val device = UiDevice.getInstance(instrumentation)
+ private val testApp = DesktopModeAppHelper(SimpleAppHelper(instrumentation))
+ private val displayManager =
+ instrumentation.getContext().getSystemService(Context.DISPLAY_SERVICE) as DisplayManager
+ private var virtualDisplay: VirtualDisplay? = null
+
+ private val extendedDisplaySettingsSession =
+ ExtendedDisplaySettingsSession(instrumentation.context.contentResolver)
+
+ @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation)
+
+ @Before
+ fun setup() {
+ Assume.assumeTrue(Flags.enableDesktopWindowingMode() && tapl.isTablet)
+ Assume.assumeTrue(Flags.enableDisplayWindowingModeSwitching())
+ tapl.setEnableRotation(true)
+ tapl.setExpectedRotation(rotation.value)
+ ChangeDisplayOrientationRule.setRotation(rotation)
+ extendedDisplaySettingsSession.open()
+ virtualDisplay = displayManager.createVirtualDisplay(
+ /* displayName= */ DISPLAY_NAME,
+ /* width= */ DISPLAY_WIDTH,
+ /* height= */ DISPLAY_HEIGHT,
+ /* densityDpi= */ DisplayMetrics.DENSITY_DEFAULT,
+ /* surface= */ null,
+ /* flags= */ DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC or
+ DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY or
+ DisplayManager.VIRTUAL_DISPLAY_FLAG_TRUSTED
+ )
+ }
+
+ @Test
+ open fun openAppWithExternalDisplayConnected() {
+ testApp.open()
+ }
+
+ @After
+ fun teardown() {
+ testApp.exit(wmHelper)
+ virtualDisplay?.let {
+ it.release()
+ }
+ extendedDisplaySettingsSession.close()
+ }
+
+ companion object {
+ const val DISPLAY_NAME = "testVirtualDisplay"
+ const val DISPLAY_HEIGHT = 600
+ const val DISPLAY_WIDTH = 800
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/e2e/utils/src/com/android/wm/shell/ExtendedDisplaySettingsSession.kt b/libs/WindowManager/Shell/tests/e2e/utils/src/com/android/wm/shell/ExtendedDisplaySettingsSession.kt
new file mode 100644
index 000000000000..0b2aacd00aa6
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/utils/src/com/android/wm/shell/ExtendedDisplaySettingsSession.kt
@@ -0,0 +1,34 @@
+/*
+ * 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
+
+import android.content.ContentResolver
+import android.provider.Settings
+import android.provider.Settings.Global.DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS
+
+class ExtendedDisplaySettingsSession(private val contentResolver: ContentResolver) {
+ private val settingName = DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS
+ private val initialValue = Settings.Global.getInt(contentResolver, settingName, 0)
+
+ fun open() {
+ Settings.Global.putInt(contentResolver, settingName, 1)
+ }
+
+ fun close() {
+ Settings.Global.putInt(contentResolver, settingName, initialValue)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/pip/PipDesktopStateTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/pip/PipDesktopStateTest.java
new file mode 100644
index 000000000000..75ad621e1cad
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/pip/PipDesktopStateTest.java
@@ -0,0 +1,204 @@
+/*
+ * 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.common.pip;
+
+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 com.android.window.flags.Flags.FLAG_ENABLE_CONNECTED_DISPLAYS_PIP;
+import static com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_PIP;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
+import static org.mockito.Mockito.when;
+
+import android.app.ActivityManager;
+import android.platform.test.annotations.EnableFlags;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.window.DisplayAreaInfo;
+import android.window.WindowContainerToken;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
+import com.android.wm.shell.desktopmode.DesktopRepository;
+import com.android.wm.shell.desktopmode.DesktopUserRepositories;
+import com.android.wm.shell.desktopmode.desktopwallpaperactivity.DesktopWallpaperActivityTokenProvider;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Optional;
+
+/**
+ * Unit test against {@link PipDesktopState}.
+ */
+@SmallTest
+@TestableLooper.RunWithLooper
+@RunWith(AndroidTestingRunner.class)
+@EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_PIP)
+public class PipDesktopStateTest {
+ @Mock private PipDisplayLayoutState mMockPipDisplayLayoutState;
+ @Mock private Optional<DesktopUserRepositories> mMockDesktopUserRepositoriesOptional;
+ @Mock private Optional<DesktopWallpaperActivityTokenProvider>
+ mMockDesktopWallpaperActivityTokenProviderOptional;
+ @Mock private DesktopUserRepositories mMockDesktopUserRepositories;
+ @Mock private DesktopWallpaperActivityTokenProvider mMockDesktopWallpaperActivityTokenProvider;
+ @Mock private DesktopRepository mMockDesktopRepository;
+ @Mock private RootTaskDisplayAreaOrganizer mMockRootTaskDisplayAreaOrganizer;
+ @Mock private ActivityManager.RunningTaskInfo mMockTaskInfo;
+
+ private static final int DISPLAY_ID = 1;
+ private DisplayAreaInfo mDefaultTda;
+ private PipDesktopState mPipDesktopState;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ when(mMockDesktopUserRepositoriesOptional.get()).thenReturn(mMockDesktopUserRepositories);
+ when(mMockDesktopWallpaperActivityTokenProviderOptional.get()).thenReturn(
+ mMockDesktopWallpaperActivityTokenProvider);
+ when(mMockDesktopUserRepositories.getCurrent()).thenReturn(mMockDesktopRepository);
+ when(mMockDesktopUserRepositoriesOptional.isPresent()).thenReturn(true);
+ when(mMockDesktopWallpaperActivityTokenProviderOptional.isPresent()).thenReturn(true);
+
+ when(mMockTaskInfo.getDisplayId()).thenReturn(DISPLAY_ID);
+ when(mMockPipDisplayLayoutState.getDisplayId()).thenReturn(DISPLAY_ID);
+
+ mDefaultTda = new DisplayAreaInfo(Mockito.mock(WindowContainerToken.class), DISPLAY_ID, 0);
+ when(mMockRootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DISPLAY_ID)).thenReturn(
+ mDefaultTda);
+
+ mPipDesktopState = new PipDesktopState(mMockPipDisplayLayoutState,
+ mMockDesktopUserRepositoriesOptional,
+ mMockDesktopWallpaperActivityTokenProviderOptional,
+ mMockRootTaskDisplayAreaOrganizer);
+ }
+
+ @Test
+ public void isDesktopWindowingPipEnabled_returnsTrue() {
+ assertTrue(mPipDesktopState.isDesktopWindowingPipEnabled());
+ }
+
+ @Test
+ public void isDesktopWindowingPipEnabled_desktopRepositoryEmpty_returnsFalse() {
+ when(mMockDesktopUserRepositoriesOptional.isPresent()).thenReturn(false);
+
+ assertFalse(mPipDesktopState.isDesktopWindowingPipEnabled());
+ }
+
+ @Test
+ public void isDesktopWindowingPipEnabled_desktopWallpaperEmpty_returnsFalse() {
+ when(mMockDesktopWallpaperActivityTokenProviderOptional.isPresent()).thenReturn(false);
+
+ assertFalse(mPipDesktopState.isDesktopWindowingPipEnabled());
+ }
+
+ @Test
+ @EnableFlags(FLAG_ENABLE_CONNECTED_DISPLAYS_PIP)
+ public void isConnectedDisplaysPipEnabled_returnsTrue() {
+ assertTrue(mPipDesktopState.isConnectedDisplaysPipEnabled());
+ }
+
+ @Test
+ public void isPipEnteringInDesktopMode_visibleCountZero_minimizedPipPresent_returnsTrue() {
+ when(mMockDesktopRepository.getVisibleTaskCount(DISPLAY_ID)).thenReturn(0);
+ when(mMockDesktopRepository.isMinimizedPipPresentInDisplay(DISPLAY_ID)).thenReturn(true);
+
+ assertTrue(mPipDesktopState.isPipEnteringInDesktopMode(mMockTaskInfo));
+ }
+
+ @Test
+ public void isPipEnteringInDesktopMode_visibleCountNonzero_minimizedPipAbsent_returnsTrue() {
+ when(mMockDesktopRepository.getVisibleTaskCount(DISPLAY_ID)).thenReturn(1);
+ when(mMockDesktopRepository.isMinimizedPipPresentInDisplay(DISPLAY_ID)).thenReturn(false);
+
+ assertTrue(mPipDesktopState.isPipEnteringInDesktopMode(mMockTaskInfo));
+ }
+
+ @Test
+ public void isPipEnteringInDesktopMode_visibleCountZero_minimizedPipAbsent_returnsFalse() {
+ when(mMockDesktopRepository.getVisibleTaskCount(DISPLAY_ID)).thenReturn(0);
+ when(mMockDesktopRepository.isMinimizedPipPresentInDisplay(DISPLAY_ID)).thenReturn(false);
+
+ assertFalse(mPipDesktopState.isPipEnteringInDesktopMode(mMockTaskInfo));
+ }
+
+ @Test
+ public void shouldExitPipExitDesktopMode_visibleCountZero_wallpaperInvisible_returnsFalse() {
+ when(mMockDesktopRepository.getVisibleTaskCount(DISPLAY_ID)).thenReturn(0);
+ when(mMockDesktopWallpaperActivityTokenProvider.isWallpaperActivityVisible(
+ DISPLAY_ID)).thenReturn(false);
+
+ assertFalse(mPipDesktopState.shouldExitPipExitDesktopMode());
+ }
+
+ @Test
+ public void shouldExitPipExitDesktopMode_visibleCountNonzero_wallpaperVisible_returnsFalse() {
+ when(mMockDesktopRepository.getVisibleTaskCount(DISPLAY_ID)).thenReturn(1);
+ when(mMockDesktopWallpaperActivityTokenProvider.isWallpaperActivityVisible(
+ DISPLAY_ID)).thenReturn(true);
+
+ assertFalse(mPipDesktopState.shouldExitPipExitDesktopMode());
+ }
+
+ @Test
+ public void shouldExitPipExitDesktopMode_visibleCountZero_wallpaperVisible_returnsTrue() {
+ when(mMockDesktopRepository.getVisibleTaskCount(DISPLAY_ID)).thenReturn(0);
+ when(mMockDesktopWallpaperActivityTokenProvider.isWallpaperActivityVisible(
+ DISPLAY_ID)).thenReturn(true);
+
+ assertTrue(mPipDesktopState.shouldExitPipExitDesktopMode());
+ }
+
+ @Test
+ public void getOutPipWindowingMode_exitToDesktop_displayFreeform_returnsUndefined() {
+ // Set visible task count to 1 so isPipExitingToDesktopMode returns true
+ when(mMockDesktopRepository.getVisibleTaskCount(DISPLAY_ID)).thenReturn(1);
+ setDisplayWindowingMode(WINDOWING_MODE_FREEFORM);
+
+ assertEquals(WINDOWING_MODE_UNDEFINED, mPipDesktopState.getOutPipWindowingMode());
+ }
+
+ @Test
+ public void getOutPipWindowingMode_exitToDesktop_displayFullscreen_returnsFreeform() {
+ // Set visible task count to 1 so isPipExitingToDesktopMode returns true
+ when(mMockDesktopRepository.getVisibleTaskCount(DISPLAY_ID)).thenReturn(1);
+ setDisplayWindowingMode(WINDOWING_MODE_FULLSCREEN);
+
+ assertEquals(WINDOWING_MODE_FREEFORM, mPipDesktopState.getOutPipWindowingMode());
+ }
+
+ @Test
+ public void getOutPipWindowingMode_exitToFullscreen_displayFullscreen_returnsUndefined() {
+ setDisplayWindowingMode(WINDOWING_MODE_FULLSCREEN);
+
+ assertEquals(WINDOWING_MODE_UNDEFINED, mPipDesktopState.getOutPipWindowingMode());
+ }
+
+ private void setDisplayWindowingMode(int windowingMode) {
+ mDefaultTda.configuration.windowConfiguration.setWindowingMode(windowingMode);
+ }
+}
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/DesktopModeVisualIndicatorTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicatorTest.kt
index e46d2c7147ed..13b44977e9c7 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicatorTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicatorTest.kt
@@ -19,22 +19,30 @@ package com.android.wm.shell.desktopmode
import android.app.ActivityManager.RunningTaskInfo
import android.graphics.PointF
import android.graphics.Rect
+import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.SetFlagsRule
import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper.RunWithLooper
import android.view.SurfaceControl
import androidx.test.filters.SmallTest
import com.android.internal.policy.SystemBarUtils
+import com.android.modules.utils.testing.ExtendedMockitoRule
+import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE
import com.android.wm.shell.R
import com.android.wm.shell.RootTaskDisplayAreaOrganizer
import com.android.wm.shell.ShellTestCase
import com.android.wm.shell.common.DisplayController
import com.android.wm.shell.common.DisplayLayout
import com.android.wm.shell.common.SyncTransactionQueue
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
import com.google.common.truth.Truth.assertThat
import org.junit.Before
+import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mock
+import org.mockito.kotlin.any
import org.mockito.kotlin.whenever
/**
@@ -43,9 +51,19 @@ import org.mockito.kotlin.whenever
* Usage: atest WMShellUnitTests:DesktopModeVisualIndicatorTest
*/
@SmallTest
+@RunWithLooper
@RunWith(AndroidTestingRunner::class)
+@EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
class DesktopModeVisualIndicatorTest : ShellTestCase() {
- @Mock private lateinit var taskInfo: RunningTaskInfo
+
+ @JvmField @Rule val setFlagsRule = SetFlagsRule()
+
+ @JvmField
+ @Rule
+ val extendedMockitoRule =
+ ExtendedMockitoRule.Builder(this).mockStatic(DesktopModeStatus::class.java).build()!!
+
+ private lateinit var taskInfo: RunningTaskInfo
@Mock private lateinit var syncQueue: SyncTransactionQueue
@Mock private lateinit var displayController: DisplayController
@Mock private lateinit var taskSurface: SurfaceControl
@@ -56,10 +74,13 @@ class DesktopModeVisualIndicatorTest : ShellTestCase() {
@Before
fun setUp() {
+ whenever(DesktopModeStatus.canEnterDesktopMode(any())).thenReturn(true)
whenever(displayLayout.width()).thenReturn(DISPLAY_BOUNDS.width())
whenever(displayLayout.height()).thenReturn(DISPLAY_BOUNDS.height())
whenever(displayLayout.stableInsets()).thenReturn(STABLE_INSETS)
whenever(displayController.getDisplayLayout(anyInt())).thenReturn(displayLayout)
+ whenever(displayController.getDisplay(anyInt())).thenReturn(mContext.display)
+ taskInfo = DesktopTestHelpers.createFullscreenTask()
}
@Test
@@ -190,6 +211,39 @@ class DesktopModeVisualIndicatorTest : ShellTestCase() {
assertThat(result).isEqualTo(DesktopModeVisualIndicator.IndicatorType.NO_INDICATOR)
}
+ @Test
+ @EnableFlags(
+ com.android.wm.shell.Flags.FLAG_ENABLE_BUBBLE_TO_FULLSCREEN,
+ com.android.wm.shell.Flags.FLAG_ENABLE_CREATE_ANY_BUBBLE,
+ )
+ fun testDefaultIndicatorWithNoDesktop() {
+ whenever(DesktopModeStatus.canEnterDesktopMode(any())).thenReturn(false)
+
+ createVisualIndicator(DesktopModeVisualIndicator.DragStartState.FROM_FULLSCREEN)
+ var result = visualIndicator.updateIndicatorType(PointF(500f, 500f))
+ assertThat(result).isEqualTo(DesktopModeVisualIndicator.IndicatorType.NO_INDICATOR)
+
+ result = visualIndicator.updateIndicatorType(PointF(10000f, 500f))
+ assertThat(result)
+ .isEqualTo(DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_RIGHT_INDICATOR)
+
+ result = visualIndicator.updateIndicatorType(PointF(-10000f, 500f))
+ assertThat(result)
+ .isEqualTo(DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_LEFT_INDICATOR)
+
+ createVisualIndicator(DesktopModeVisualIndicator.DragStartState.FROM_SPLIT)
+ result = visualIndicator.updateIndicatorType(PointF(500f, 500f))
+ assertThat(result).isEqualTo(DesktopModeVisualIndicator.IndicatorType.NO_INDICATOR)
+
+ result = visualIndicator.updateIndicatorType(PointF(500f, 0f))
+ assertThat(result)
+ .isEqualTo(DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR)
+
+ createVisualIndicator(DesktopModeVisualIndicator.DragStartState.DRAGGED_INTENT)
+ result = visualIndicator.updateIndicatorType(PointF(500f, 500f))
+ assertThat(result).isEqualTo(DesktopModeVisualIndicator.IndicatorType.NO_INDICATOR)
+ }
+
private fun createVisualIndicator(dragStartState: DesktopModeVisualIndicator.DragStartState) {
visualIndicator =
DesktopModeVisualIndicator(
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 8e7545c2a99c..c10d2afbdc99 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
@@ -290,7 +290,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))
@@ -541,7 +541,6 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
fun showDesktopApps_allAppsInvisible_bringsToFront_desktopWallpaperEnabled() {
- whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(null)
val task1 = setUpFreeformTask()
val task2 = setUpFreeformTask()
markTaskHidden(task1)
@@ -730,7 +729,6 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
fun showDesktopApps_appsAlreadyVisible_bringsToFront_desktopWallpaperEnabled() {
- whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(null)
val task1 = setUpFreeformTask()
val task2 = setUpFreeformTask()
markTaskVisible(task1)
@@ -769,8 +767,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- fun showDesktopApps_someAppsInvisible_desktopWallpaperEnabled_reordersOnlyFreeformTasks() {
- whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(null)
+ fun showDesktopApps_someAppsInvisible_reordersAll_desktopWallpaperEnabled() {
val task1 = setUpFreeformTask()
val task2 = setUpFreeformTask()
markTaskHidden(task1)
@@ -787,24 +784,6 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
wct.assertReorderAt(index = 2, task2)
}
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- fun showDesktopApps_someAppsInvisible_desktopWallpaperEnabled_reordersAll() {
- val task1 = setUpFreeformTask()
- val task2 = setUpFreeformTask()
- markTaskHidden(task1)
- markTaskVisible(task2)
-
- controller.showDesktopApps(DEFAULT_DISPLAY, RemoteTransition(TestRemoteTransition()))
-
- val wct =
- getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java)
- assertThat(wct.hierarchyOps).hasSize(3)
- // Expect order to be from bottom: wallpaper intent, task1, task2
- wct.assertReorderAt(index = 0, wallpaperToken)
- wct.assertReorderAt(index = 1, task1)
- wct.assertReorderAt(index = 2, task2)
- }
-
@Test
@DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
fun showDesktopApps_noActiveTasks_reorderHomeToTop_desktopWallpaperDisabled() {
@@ -820,9 +799,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- fun showDesktopApps_noActiveTasks_desktopWallpaperEnabled_addsDesktopWallpaper() {
- whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(null)
-
+ fun showDesktopApps_noActiveTasks_addDesktopWallpaper_desktopWallpaperEnabled() {
controller.showDesktopApps(DEFAULT_DISPLAY, RemoteTransition(TestRemoteTransition()))
val wct =
@@ -831,16 +808,6 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
}
@Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- fun showDesktopApps_noActiveTasks_desktopWallpaperEnabled_reordersDesktopWallpaper() {
- controller.showDesktopApps(DEFAULT_DISPLAY, RemoteTransition(TestRemoteTransition()))
-
- val wct =
- getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java)
- wct.assertReorderAt(index = 0, wallpaperToken)
- }
-
- @Test
@DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
fun showDesktopApps_twoDisplays_bringsToFrontOnlyOneDisplay_desktopWallpaperDisabled() {
taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY)
@@ -864,7 +831,6 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
fun showDesktopApps_twoDisplays_bringsToFrontOnlyOneDisplay_desktopWallpaperEnabled() {
- whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(null)
taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY)
val homeTaskDefaultDisplay = setUpHomeTask(DEFAULT_DISPLAY)
val taskDefaultDisplay = setUpFreeformTask(DEFAULT_DISPLAY)
@@ -909,7 +875,6 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
fun showDesktopApps_desktopWallpaperEnabled_dontReorderMinimizedTask() {
- whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(null)
val homeTask = setUpHomeTask()
val freeformTask = setUpFreeformTask()
val minimizedTask = setUpFreeformTask()
@@ -1375,7 +1340,6 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
fun moveTaskToDesktop_desktopWallpaperEnabled_nonRunningTask_launchesInFreeform() {
- whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(null)
val task = createTaskInfo(1)
whenever(shellTaskOrganizer.getRunningTaskInfo(anyInt())).thenReturn(null)
whenever(recentTasksController.findTaskInBackground(anyInt())).thenReturn(task)
@@ -1558,7 +1522,6 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
fun moveRunningTaskToDesktop_otherFreeformTasksBroughtToFront_desktopWallpaperEnabled() {
- whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(null)
val freeformTask = setUpFreeformTask()
val fullscreenTask = setUpFullscreenTask()
markTaskHidden(freeformTask)
@@ -1661,7 +1624,6 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
fun moveRunningTaskToDesktop_desktopWallpaperEnabled_bringsTasksOverLimit_dontShowBackTask() {
- whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(null)
val freeformTasks = (1..MAX_TASK_LIMIT).map { _ -> setUpFreeformTask() }
val newTask = setUpFullscreenTask()
val homeTask = setUpHomeTask()
@@ -2682,7 +2644,6 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
fun handleRequest_fullscreenTask_noTasks_enforceDesktop_freeformDisplay_returnFreeformWCT() {
- whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(null)
whenever(DesktopModeStatus.enterDesktopByDefaultOnFreeformDisplay(context)).thenReturn(true)
val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!!
tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FREEFORM
@@ -2814,7 +2775,6 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
fun handleRequest_freeformTask_desktopWallpaperEnabled_freeformNotVisible_reorderedToTop() {
- whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(null)
val freeformTask1 = setUpFreeformTask()
val freeformTask2 = createFreeformTask()
@@ -2849,9 +2809,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
fun handleRequest_freeformTask_desktopWallpaperEnabled_noOtherTasks_reorderedToTop() {
- whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(null)
val task = createFreeformTask()
-
val result = controller.handleRequest(Binder(), createTransition(task))
assertNotNull(result, "Should handle request")
@@ -2879,7 +2837,6 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
fun handleRequest_freeformTask_dskWallpaperEnabled_freeformOnOtherDisplayOnly_reorderedToTop() {
- whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(null)
val taskDefaultDisplay = createFreeformTask(displayId = DEFAULT_DISPLAY)
// Second display task
createFreeformTask(displayId = SECOND_DISPLAY)
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/pip2/phone/PipSchedulerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipSchedulerTest.java
index bd857c7dcd45..8e0381e4f933 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipSchedulerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipSchedulerTest.java
@@ -26,7 +26,6 @@ import static org.mockito.kotlin.MatchersKt.eq;
import static org.mockito.kotlin.VerificationKt.times;
import static org.mockito.kotlin.VerificationKt.verify;
-import android.app.TaskInfo;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Matrix;
@@ -39,12 +38,9 @@ import android.window.WindowContainerTransaction;
import androidx.test.filters.SmallTest;
-import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.pip.PipBoundsState;
-import com.android.wm.shell.desktopmode.DesktopRepository;
-import com.android.wm.shell.desktopmode.DesktopUserRepositories;
-import com.android.wm.shell.desktopmode.desktopwallpaperactivity.DesktopWallpaperActivityTokenProvider;
+import com.android.wm.shell.common.pip.PipDesktopState;
import com.android.wm.shell.pip.PipTransitionController;
import com.android.wm.shell.pip2.PipSurfaceTransactionHelper;
import com.android.wm.shell.pip2.animation.PipAlphaAnimator;
@@ -55,11 +51,8 @@ import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
-import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
-import java.util.Optional;
-
/**
* Unit test against {@link PipScheduler}
*/
@@ -77,16 +70,13 @@ public class PipSchedulerTest {
@Mock private PipBoundsState mMockPipBoundsState;
@Mock private ShellExecutor mMockMainExecutor;
@Mock private PipTransitionState mMockPipTransitionState;
+ @Mock private PipDesktopState mMockPipDesktopState;
@Mock private PipTransitionController mMockPipTransitionController;
@Mock private Runnable mMockUpdateMovementBoundsRunnable;
@Mock private WindowContainerToken mMockPipTaskToken;
@Mock private PipSurfaceTransactionHelper.SurfaceControlTransactionFactory mMockFactory;
@Mock private SurfaceControl.Transaction mMockTransaction;
@Mock private PipAlphaAnimator mMockAlphaAnimator;
- @Mock private DesktopUserRepositories mMockDesktopUserRepositories;
- @Mock private DesktopWallpaperActivityTokenProvider mMockDesktopWallpaperActivityTokenProvider;
- @Mock private RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer;
-
@Captor private ArgumentCaptor<Runnable> mRunnableArgumentCaptor;
@Captor private ArgumentCaptor<WindowContainerTransaction> mWctArgumentCaptor;
@@ -101,14 +91,9 @@ public class PipSchedulerTest {
when(mMockFactory.getTransaction()).thenReturn(mMockTransaction);
when(mMockTransaction.setMatrix(any(SurfaceControl.class), any(Matrix.class), any()))
.thenReturn(mMockTransaction);
- when(mMockDesktopUserRepositories.getCurrent())
- .thenReturn(Mockito.mock(DesktopRepository.class));
- when(mMockPipTransitionState.getPipTaskInfo()).thenReturn(Mockito.mock(TaskInfo.class));
mPipScheduler = new PipScheduler(mMockContext, mMockPipBoundsState, mMockMainExecutor,
- mMockPipTransitionState, Optional.of(mMockDesktopUserRepositories),
- Optional.of(mMockDesktopWallpaperActivityTokenProvider),
- mRootTaskDisplayAreaOrganizer);
+ mMockPipTransitionState, mMockPipDesktopState);
mPipScheduler.setPipTransitionController(mMockPipTransitionController);
mPipScheduler.setSurfaceControlTransactionFactory(mMockFactory);
mPipScheduler.setPipAlphaAnimatorSupplier((context, leash, startTx, finishTx, direction) ->
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipTaskListenerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipTaskListenerTest.java
index 3923a1ef5633..1b462c30e017 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipTaskListenerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipTaskListenerTest.java
@@ -165,6 +165,25 @@ public class PipTaskListenerTest {
}
@Test
+ public void onTaskInfoChanged_withNullPipParams_doNothing() {
+ mPipTaskListener = new PipTaskListener(mMockContext, mMockShellTaskOrganizer,
+ mMockPipTransitionState, mMockPipScheduler, mMockPipBoundsState,
+ mMockPipBoundsAlgorithm, mMockShellExecutor);
+ mPipTaskListener.addParamsChangedListener(mMockPipParamsChangedCallback);
+ Rational aspectRatio = new Rational(4, 3);
+ when(mMockPipBoundsState.getAspectRatio()).thenReturn(aspectRatio.toFloat());
+ String action1 = "action1";
+ mPipTaskListener.onTaskInfoChanged(getTaskInfo(aspectRatio, action1));
+
+ clearInvocations(mMockPipParamsChangedCallback);
+ mPipTaskListener.onTaskInfoChanged(new ActivityManager.RunningTaskInfo());
+
+ verifyZeroInteractions(mMockPipParamsChangedCallback);
+ verify(mMockPipTransitionState, times(0))
+ .setOnIdlePipTransitionStateRunnable(any(Runnable.class));
+ }
+
+ @Test
public void onTaskInfoChanged_withActionsChanged_callbackActionsChanged() {
mPipTaskListener = new PipTaskListener(mMockContext, mMockShellTaskOrganizer,
mMockPipTransitionState, mMockPipScheduler, mMockPipBoundsState,
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..4a91fb429f7b 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
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/TypeWrappers.cpp b/libs/androidfw/TypeWrappers.cpp
index 970463492c1a..2a20106b6ee1 100644
--- a/libs/androidfw/TypeWrappers.cpp
+++ b/libs/androidfw/TypeWrappers.cpp
@@ -18,8 +18,11 @@
namespace android {
-TypeVariant::TypeVariant(const ResTable_type* data) : data(data), mLength(dtohl(data->entryCount)) {
- if (data->flags & ResTable_type::FLAG_SPARSE) {
+TypeVariant::TypeVariant(const ResTable_type* data)
+ : data(data)
+ , mLength(dtohl(data->entryCount))
+ , mSparse(data->flags & ResTable_type::FLAG_SPARSE) {
+ if (mSparse) {
const uint32_t entryCount = dtohl(data->entryCount);
const uintptr_t containerEnd = reinterpret_cast<uintptr_t>(data) + dtohl(data->header.size);
const uint32_t* const entryIndices = reinterpret_cast<const uint32_t*>(
@@ -40,18 +43,18 @@ TypeVariant::iterator& TypeVariant::iterator::operator++() {
mIndex = mTypeVariant->mLength;
}
- const ResTable_type* type = mTypeVariant->data;
- if ((type->flags & ResTable_type::FLAG_SPARSE) == 0) {
+ if (!mTypeVariant->mSparse) {
return *this;
}
// Need to adjust |mSparseIndex| as well if we've passed its current element.
+ const ResTable_type* type = mTypeVariant->data;
const uint32_t entryCount = dtohl(type->entryCount);
- const auto entryIndices = reinterpret_cast<const uint32_t*>(
- reinterpret_cast<uintptr_t>(type) + dtohs(type->header.headerSize));
if (mSparseIndex >= entryCount) {
return *this; // done
}
+ const auto entryIndices = reinterpret_cast<const uint32_t*>(
+ reinterpret_cast<uintptr_t>(type) + dtohs(type->header.headerSize));
const auto element = (const ResTable_sparseTypeEntry*)(entryIndices + mSparseIndex);
if (mIndex > dtohs(element->idx)) {
++mSparseIndex;
@@ -79,7 +82,7 @@ const ResTable_entry* TypeVariant::iterator::operator*() const {
}
uint32_t entryOffset;
- if (type->flags & ResTable_type::FLAG_SPARSE) {
+ if (mTypeVariant->mSparse) {
if (mSparseIndex >= entryCount) {
return nullptr;
}
diff --git a/libs/androidfw/include/androidfw/TypeWrappers.h b/libs/androidfw/include/androidfw/TypeWrappers.h
index db641b78a4e4..d901af3c908b 100644
--- a/libs/androidfw/include/androidfw/TypeWrappers.h
+++ b/libs/androidfw/include/androidfw/TypeWrappers.h
@@ -14,8 +14,7 @@
* limitations under the License.
*/
-#ifndef __TYPE_WRAPPERS_H
-#define __TYPE_WRAPPERS_H
+#pragma once
#include <androidfw/ResourceTypes.h>
#include <utils/ByteOrder.h>
@@ -54,7 +53,7 @@ struct TypeVariant {
enum class Kind { Begin, End };
iterator(const TypeVariant* tv, Kind kind)
: mTypeVariant(tv) {
- mSparseIndex = mIndex = kind == Kind::Begin ? 0 : tv->mLength;
+ mSparseIndex = mIndex = (kind == Kind::Begin ? 0 : tv->mLength);
// mSparseIndex here is technically past the number of sparse entries, but it is still
// ok as it is enough to infer that this is the end iterator.
}
@@ -75,9 +74,11 @@ struct TypeVariant {
const ResTable_type* data;
private:
- size_t mLength;
+ // For a dense table, this is the number of the elements.
+ // For a sparse table, this is the index of the last element + 1.
+ // In both cases, it can be used for iteration as the upper loop bound as in |i < mLength|.
+ uint32_t mLength;
+ bool mSparse;
};
} // namespace android
-
-#endif // __TYPE_WRAPPERS_H
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/libs/androidfw/tests/TypeWrappers_test.cpp b/libs/androidfw/tests/TypeWrappers_test.cpp
index d66e05805484..69c24c5d8956 100644
--- a/libs/androidfw/tests/TypeWrappers_test.cpp
+++ b/libs/androidfw/tests/TypeWrappers_test.cpp
@@ -121,6 +121,7 @@ TEST(TypeVariantIteratorTest, shouldIterateOverTypeWithoutErrors) {
values.push_back(std::nullopt);
values.push_back(std::nullopt);
values.push_back(Res_value{ sizeof(Res_value), 0, Res_value::TYPE_STRING, 0x87654321});
+ values.push_back(std::nullopt);
// test for combinations of compact_entry and short_offsets
for (size_t i = 0; i < 8; i++) {
@@ -191,6 +192,17 @@ TEST(TypeVariantIteratorTest, shouldIterateOverTypeWithoutErrors) {
++iter;
+ ASSERT_EQ(uint32_t(9), iter.index());
+ ASSERT_TRUE(NULL == *iter);
+ if (sparse) {
+ // Sparse iterator doesn't know anything beyond the last entry.
+ ASSERT_EQ(v.endEntries(), iter);
+ } else {
+ ASSERT_NE(v.endEntries(), iter);
+ }
+
+ ++iter;
+
ASSERT_EQ(v.endEntries(), iter);
}
}
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/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/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 0a7d880677d8..1f2890c2052e 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",
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/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/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/CommunalSettingsRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalSettingsRepositoryImplTest.kt
index eb1f1d9c52f4..5c983656225e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalSettingsRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalSettingsRepositoryImplTest.kt
@@ -51,6 +51,7 @@ import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.settings.fakeSettings
import com.google.common.truth.Truth.assertThat
import com.google.common.truth.Truth.assertWithMessage
+import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -78,6 +79,32 @@ class CommunalSettingsRepositoryImplTest(flags: FlagsParameterization?) : SysuiT
setKeyguardFeaturesDisabled(PRIMARY_USER, KEYGUARD_DISABLE_FEATURES_NONE)
setKeyguardFeaturesDisabled(SECONDARY_USER, KEYGUARD_DISABLE_FEATURES_NONE)
setKeyguardFeaturesDisabled(WORK_PROFILE, KEYGUARD_DISABLE_FEATURES_NONE)
+
+ mContext.orCreateTestableResources.addOverride(
+ com.android.internal.R.bool.config_dreamsActivatedOnSleepByDefault,
+ false,
+ )
+ mContext.orCreateTestableResources.addOverride(
+ com.android.internal.R.bool.config_dreamsActivatedOnDockByDefault,
+ false,
+ )
+ mContext.orCreateTestableResources.addOverride(
+ com.android.internal.R.bool.config_dreamsActivatedOnPosturedByDefault,
+ false,
+ )
+ }
+
+ @After
+ fun tearDown() {
+ mContext.orCreateTestableResources.removeOverride(
+ com.android.internal.R.bool.config_dreamsActivatedOnSleepByDefault
+ )
+ mContext.orCreateTestableResources.removeOverride(
+ com.android.internal.R.bool.config_dreamsActivatedOnDockByDefault
+ )
+ mContext.orCreateTestableResources.removeOverride(
+ com.android.internal.R.bool.config_dreamsActivatedOnPosturedByDefault
+ )
}
@EnableFlags(FLAG_COMMUNAL_HUB)
@@ -334,6 +361,18 @@ class CommunalSettingsRepositoryImplTest(flags: FlagsParameterization?) : SysuiT
}
@Test
+ fun whenToDream_charging_defaultValue() =
+ kosmos.runTest {
+ mContext.orCreateTestableResources.addOverride(
+ com.android.internal.R.bool.config_dreamsActivatedOnSleepByDefault,
+ true,
+ )
+
+ val whenToDreamState by collectLastValue(underTest.getWhenToDreamState(PRIMARY_USER))
+ assertThat(whenToDreamState).isEqualTo(WhenToDream.WHILE_CHARGING)
+ }
+
+ @Test
fun whenToDream_docked() =
kosmos.runTest {
val whenToDreamState by collectLastValue(underTest.getWhenToDreamState(PRIMARY_USER))
@@ -348,6 +387,18 @@ class CommunalSettingsRepositoryImplTest(flags: FlagsParameterization?) : SysuiT
}
@Test
+ fun whenToDream_docked_defaultValue() =
+ kosmos.runTest {
+ mContext.orCreateTestableResources.addOverride(
+ com.android.internal.R.bool.config_dreamsActivatedOnDockByDefault,
+ true,
+ )
+
+ val whenToDreamState by collectLastValue(underTest.getWhenToDreamState(PRIMARY_USER))
+ assertThat(whenToDreamState).isEqualTo(WhenToDream.WHILE_DOCKED)
+ }
+
+ @Test
fun whenToDream_postured() =
kosmos.runTest {
val whenToDreamState by collectLastValue(underTest.getWhenToDreamState(PRIMARY_USER))
@@ -362,6 +413,18 @@ class CommunalSettingsRepositoryImplTest(flags: FlagsParameterization?) : SysuiT
}
@Test
+ fun whenToDream_postured_defaultValue() =
+ kosmos.runTest {
+ mContext.orCreateTestableResources.addOverride(
+ com.android.internal.R.bool.config_dreamsActivatedOnPosturedByDefault,
+ true,
+ )
+
+ val whenToDreamState by collectLastValue(underTest.getWhenToDreamState(PRIMARY_USER))
+ assertThat(whenToDreamState).isEqualTo(WhenToDream.WHILE_POSTURED)
+ }
+
+ @Test
fun whenToDream_default() =
kosmos.runTest {
val whenToDreamState by collectLastValue(underTest.getWhenToDreamState(PRIMARY_USER))
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
index ff137b768b65..c3cc3e66f81f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
@@ -21,6 +21,7 @@ import android.app.admin.DevicePolicyManager
import android.app.admin.devicePolicyManager
import android.content.Intent
import android.content.pm.UserInfo
+import android.content.res.mainResources
import android.os.UserHandle
import android.os.UserManager
import android.os.userManager
@@ -85,6 +86,7 @@ import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.test.advanceTimeBy
+import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -106,7 +108,10 @@ class CommunalInteractorTest(flags: FlagsParameterization) : SysuiTestCase() {
UserInfo(/* id= */ 0, /* name= */ "primary user", /* flags= */ UserInfo.FLAG_MAIN)
private val secondaryUser = UserInfo(/* id= */ 1, /* name= */ "secondary user", /* flags= */ 0)
- private val kosmos = testKosmos().useUnconfinedTestDispatcher()
+ private val kosmos =
+ testKosmos()
+ .apply { mainResources = mContext.orCreateTestableResources.resources }
+ .useUnconfinedTestDispatcher()
private val Kosmos.underTest by Kosmos.Fixture { communalInteractor }
@@ -122,6 +127,32 @@ class CommunalInteractorTest(flags: FlagsParameterization) : SysuiTestCase() {
kosmos.fakeFeatureFlagsClassic.set(Flags.COMMUNAL_SERVICE_ENABLED, true)
mSetFlagsRule.enableFlags(FLAG_COMMUNAL_HUB)
+
+ mContext.orCreateTestableResources.addOverride(
+ com.android.internal.R.bool.config_dreamsActivatedOnSleepByDefault,
+ false,
+ )
+ mContext.orCreateTestableResources.addOverride(
+ com.android.internal.R.bool.config_dreamsActivatedOnDockByDefault,
+ false,
+ )
+ mContext.orCreateTestableResources.addOverride(
+ com.android.internal.R.bool.config_dreamsActivatedOnPosturedByDefault,
+ false,
+ )
+ }
+
+ @After
+ fun tearDown() {
+ mContext.orCreateTestableResources.removeOverride(
+ com.android.internal.R.bool.config_dreamsActivatedOnSleepByDefault
+ )
+ mContext.orCreateTestableResources.removeOverride(
+ com.android.internal.R.bool.config_dreamsActivatedOnDockByDefault
+ )
+ mContext.orCreateTestableResources.removeOverride(
+ com.android.internal.R.bool.config_dreamsActivatedOnPosturedByDefault
+ )
}
@Test
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/dump/DumpHandlerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dump/DumpHandlerTest.kt
index 597629219634..09c4231b7178 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dump/DumpHandlerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dump/DumpHandlerTest.kt
@@ -275,7 +275,9 @@ class DumpHandlerTest : SysuiTestCase() {
@Test
fun testDumpAllProtoDumpables() {
+ @Suppress("DEPRECATION")
dumpManager.registerDumpable("protoDumpable1", protoDumpable1)
+ @Suppress("DEPRECATION")
dumpManager.registerDumpable("protoDumpable2", protoDumpable2)
val args = arrayOf(DumpHandler.PROTO)
@@ -287,7 +289,9 @@ class DumpHandlerTest : SysuiTestCase() {
@Test
fun testDumpSingleProtoDumpable() {
+ @Suppress("DEPRECATION")
dumpManager.registerDumpable("protoDumpable1", protoDumpable1)
+ @Suppress("DEPRECATION")
dumpManager.registerDumpable("protoDumpable2", protoDumpable2)
val args = arrayOf(DumpHandler.PROTO, "protoDumpable1")
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorParameterizedTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorParameterizedTest.kt
index 6899d23bcfd7..e126b4d7265c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorParameterizedTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorParameterizedTest.kt
@@ -57,7 +57,6 @@ import platform.test.runner.parameterized.Parameters
@SmallTest
@RunWith(ParameterizedAndroidJunit4::class)
-@kotlinx.coroutines.ExperimentalCoroutinesApi
class KeyboardTouchpadEduInteractorParameterizedTest(private val gestureType: GestureType) :
SysuiTestCase() {
private val kosmos = testKosmos()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorTest.kt
index dc393c01dce1..adcd849db49a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorTest.kt
@@ -50,7 +50,6 @@ import org.mockito.kotlin.verify
@SmallTest
@RunWith(AndroidJUnit4::class)
-@kotlinx.coroutines.ExperimentalCoroutinesApi
class KeyboardTouchpadEduInteractorTest : SysuiTestCase() {
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
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/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/src/com/android/systemui/BootCompleteCacheImpl.kt b/packages/SystemUI/src/com/android/systemui/BootCompleteCacheImpl.kt
index 3a9a238d90a6..cadef52ce4cc 100644
--- a/packages/SystemUI/src/com/android/systemui/BootCompleteCacheImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/BootCompleteCacheImpl.kt
@@ -41,7 +41,7 @@ class BootCompleteCacheImpl @Inject constructor(dumpManager: DumpManager) :
}
init {
- dumpManager.registerDumpable(TAG, this)
+ dumpManager.registerNormalDumpable(TAG, this)
}
@GuardedBy("listeners")
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewController.kt
index 21569c13fab4..64a46c0d7f00 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewController.kt
@@ -131,7 +131,7 @@ abstract class UdfpsAnimationViewController<T : UdfpsAnimationView>(
override fun onViewAttached() {
dialogManager.registerListener(dialogListener)
- dumpManager.registerDumpable(dumpTag, this)
+ dumpManager.registerNormalDumpable(dumpTag, this)
udfpsOverlayInteractor.setHandleTouches(shouldHandle = !shouldPauseAuth())
}
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/CommunalSettingsRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSettingsRepository.kt
index abd101693b43..4c291a0c5a2e 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSettingsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSettingsRepository.kt
@@ -111,6 +111,18 @@ constructor(
@Named(DEFAULT_BACKGROUND_TYPE) private val defaultBackgroundType: CommunalBackgroundType,
) : CommunalSettingsRepository {
+ private val dreamsActivatedOnSleepByDefault by lazy {
+ resources.getBoolean(com.android.internal.R.bool.config_dreamsActivatedOnSleepByDefault)
+ }
+
+ private val dreamsActivatedOnDockByDefault by lazy {
+ resources.getBoolean(com.android.internal.R.bool.config_dreamsActivatedOnDockByDefault)
+ }
+
+ private val dreamsActivatedOnPosturedByDefault by lazy {
+ resources.getBoolean(com.android.internal.R.bool.config_dreamsActivatedOnPosturedByDefault)
+ }
+
override fun getFlagEnabled(): Boolean {
return if (getV2FlagEnabled()) {
true
@@ -178,27 +190,27 @@ constructor(
.emitOnStart()
.map {
if (
- secureSettings.getIntForUser(
+ secureSettings.getBoolForUser(
Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP,
- 0,
+ dreamsActivatedOnSleepByDefault,
user.id,
- ) == 1
+ )
) {
WhenToDream.WHILE_CHARGING
} else if (
- secureSettings.getIntForUser(
+ secureSettings.getBoolForUser(
Settings.Secure.SCREENSAVER_ACTIVATE_ON_DOCK,
- 0,
+ dreamsActivatedOnDockByDefault,
user.id,
- ) == 1
+ )
) {
WhenToDream.WHILE_DOCKED
} else if (
- secureSettings.getIntForUser(
+ secureSettings.getBoolForUser(
Settings.Secure.SCREENSAVER_ACTIVATE_ON_POSTURED,
- 0,
+ dreamsActivatedOnPosturedByDefault,
user.id,
- ) == 1
+ )
) {
WhenToDream.WHILE_POSTURED
} else {
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/controls/management/ControlsListingControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt
index a5f29aa658be..f3eff607dedd 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt
@@ -122,7 +122,7 @@ constructor(
init {
Log.d(TAG, "Initializing")
- dumpManager.registerDumpable(TAG, this)
+ dumpManager.registerNormalDumpable(TAG, this)
serviceListing.addCallback(serviceListingCallback)
serviceListing.setListening(true)
serviceListing.reload()
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/KeyguardQuickAffordanceRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepository.kt
index aa28c5b90090..a9729eaad266 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepository.kt
@@ -124,7 +124,7 @@ constructor(
init {
legacySettingSyncer.startSyncing()
- dumpManager.registerDumpable("KeyguardQuickAffordances", Dumpster())
+ dumpManager.registerNormalDumpable("KeyguardQuickAffordances", Dumpster())
}
/**
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/media/controls/domain/pipeline/LegacyMediaDataManagerImpl.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImpl.kt
index 3eac12d2b24c..ea5f81c75405 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImpl.kt
@@ -306,7 +306,7 @@ class LegacyMediaDataManagerImpl(
}
init {
- dumpManager.registerDumpable(TAG, this)
+ dumpManager.registerNormalDumpable(TAG, this)
// Initialize the internal processing pipeline. The listeners at the front of the pipeline
// are set as internal listeners so that they receive events. From there, events are
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/resume/MediaResumeListener.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/resume/MediaResumeListener.kt
index ad84a5eb74ab..0a70e78bf039 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/resume/MediaResumeListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/resume/MediaResumeListener.kt
@@ -141,7 +141,7 @@ constructor(
init {
if (useMediaResumption) {
- dumpManager.registerDumpable(TAG, this)
+ dumpManager.registerNormalDumpable(TAG, this)
val unlockFilter = IntentFilter()
unlockFilter.addAction(Intent.ACTION_USER_UNLOCKED)
broadcastDispatcher.registerReceiver(
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt
index f05029b2d55f..c604bfb2d5c1 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt
@@ -343,7 +343,7 @@ constructor(
.stateIn(applicationScope, SharingStarted.Eagerly, true)
init {
- dumpManager.registerDumpable(TAG, this)
+ dumpManager.registerNormalDumpable(TAG, this)
mediaFrame = inflateMediaCarousel()
mediaCarousel = mediaFrame.requireViewById(R.id.media_carousel_scroller)
pageIndicator = mediaFrame.requireViewById(R.id.media_page_indicator)
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/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/privacy/PrivacyConfig.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyConfig.kt
index 67d390d4f10d..79a513f24995 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyConfig.kt
@@ -85,7 +85,7 @@ class PrivacyConfig @Inject constructor(
}
init {
- dumpManager.registerDumpable(TAG, this)
+ dumpManager.registerNormalDumpable(TAG, this)
deviceConfigProxy.addOnPropertiesChangedListener(
DeviceConfig.NAMESPACE_PRIVACY,
uiExecutor,
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt
index eb8ef9bf3e50..f9a7205c56f5 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt
+++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt
@@ -98,7 +98,7 @@ class PrivacyItemController @Inject constructor(
}
init {
- dumpManager.registerDumpable(TAG, this)
+ dumpManager.registerNormalDumpable(TAG, this)
privacyConfig.addCallback(optionsCallback)
}
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/KeyguardlessSceneContainerFrameworkModule.kt b/packages/SystemUI/src/com/android/systemui/scene/KeyguardlessSceneContainerFrameworkModule.kt
index e36e40d312b5..3604c4cc4cfd 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/KeyguardlessSceneContainerFrameworkModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/KeyguardlessSceneContainerFrameworkModule.kt
@@ -32,7 +32,6 @@ import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.scene.ui.composable.SceneContainerTransitions
import com.android.systemui.scene.ui.viewmodel.SplitEdgeDetector
import com.android.systemui.shade.domain.interactor.ShadeInteractor
-import com.android.systemui.shade.shared.flag.DualShade
import dagger.Binds
import dagger.Module
import dagger.Provides
@@ -90,29 +89,14 @@ interface KeyguardlessSceneContainerFrameworkModule {
@Provides
fun containerConfig(): SceneContainerConfig {
return SceneContainerConfig(
- // Note that this list is in z-order. The first one is the bottom-most and the
- // last one is top-most.
- sceneKeys =
- listOfNotNull(
- Scenes.Gone,
- Scenes.QuickSettings.takeUnless { DualShade.isEnabled },
- Scenes.Shade.takeUnless { DualShade.isEnabled },
- ),
+ // Note that this list is in z-order. The first one is the bottom-most and the last
+ // one is top-most.
+ sceneKeys = listOf(Scenes.Gone, Scenes.QuickSettings, Scenes.Shade),
initialSceneKey = Scenes.Gone,
transitions = SceneContainerTransitions,
- overlayKeys =
- listOfNotNull(
- Overlays.NotificationsShade.takeIf { DualShade.isEnabled },
- Overlays.QuickSettingsShade.takeIf { DualShade.isEnabled },
- ),
+ overlayKeys = listOf(Overlays.NotificationsShade, Overlays.QuickSettingsShade),
navigationDistances =
- mapOf(
- Scenes.Gone to 0,
- Scenes.Shade to 1.takeUnless { DualShade.isEnabled },
- Scenes.QuickSettings to 2.takeUnless { DualShade.isEnabled },
- )
- .filterValues { it != null }
- .mapValues { checkNotNull(it.value) },
+ mapOf(Scenes.Gone to 0, Scenes.Shade to 1, Scenes.QuickSettings to 2),
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt b/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt
index fe014524e3da..91677b03d589 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt
@@ -32,7 +32,6 @@ import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.scene.ui.composable.SceneContainerTransitions
import com.android.systemui.scene.ui.viewmodel.SplitEdgeDetector
import com.android.systemui.shade.domain.interactor.ShadeInteractor
-import com.android.systemui.shade.shared.flag.DualShade
import dagger.Binds
import dagger.Module
import dagger.Provides
@@ -103,28 +102,24 @@ interface SceneContainerFrameworkModule {
Scenes.Dream,
Scenes.Lockscreen,
Scenes.Bouncer,
- Scenes.QuickSettings.takeUnless { DualShade.isEnabled },
- Scenes.Shade.takeUnless { DualShade.isEnabled },
+ Scenes.QuickSettings,
+ Scenes.Shade,
),
initialSceneKey = Scenes.Lockscreen,
transitions = SceneContainerTransitions,
overlayKeys =
- listOfNotNull(
- Overlays.NotificationsShade.takeIf { DualShade.isEnabled },
- Overlays.QuickSettingsShade.takeIf { DualShade.isEnabled },
- ),
+ listOfNotNull(Overlays.NotificationsShade, Overlays.QuickSettingsShade),
navigationDistances =
mapOf(
Scenes.Gone to 0,
Scenes.Lockscreen to 0,
Scenes.Communal to 1,
Scenes.Dream to 2,
- Scenes.Shade to 3.takeUnless { DualShade.isEnabled },
- Scenes.QuickSettings to 4.takeUnless { DualShade.isEnabled },
+ Scenes.Shade to 3,
+ Scenes.QuickSettings to 4,
Scenes.Bouncer to 5,
)
- .filterValues { it != null }
- .mapValues { checkNotNull(it.value) },
+ .mapValues { it.value },
)
}
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/settings/UserTrackerImpl.kt b/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt
index a48d4d4d3b5f..1f02d5a2d0bc 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt
@@ -151,7 +151,7 @@ internal constructor(
registerUserSwitchObserver()
- dumpManager.registerDumpable(TAG, this)
+ dumpManager.registerNormalDumpable(TAG, this)
}
override fun onReceive(context: Context, intent: Intent) {
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/transition/ScrimShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/shade/transition/ScrimShadeTransitionController.kt
index e38e53d67f61..e5349deaa811 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/transition/ScrimShadeTransitionController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/transition/ScrimShadeTransitionController.kt
@@ -43,7 +43,7 @@ constructor(
shadeExpansionStateManager.addExpansionListener(this::onPanelExpansionChanged)
onPanelExpansionChanged(currentState)
shadeExpansionStateManager.addStateListener(this::onPanelStateChanged)
- dumpManager.registerDumpable(
+ dumpManager.registerNormalDumpable(
ScrimShadeTransitionController::class.java.simpleName,
this::dump
)
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/layout/StatusBarContentInsetsProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/layout/StatusBarContentInsetsProvider.kt
index 7358c513eaff..9d5d87f6db7c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/layout/StatusBarContentInsetsProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/layout/StatusBarContentInsetsProvider.kt
@@ -185,7 +185,7 @@ constructor(
override fun start() {
configurationController.addCallback(this)
- dumpManager.registerDumpable(dumpableName, this)
+ dumpManager.registerNormalDumpable(dumpableName, this)
commandRegistry.registerCommand(commandName) {
StatusBarInsetsCommand(
object : StatusBarInsetsCommand.Callback {
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/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/ClockEventControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
index bac2c47f51c7..f822ee97807e 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
@@ -95,7 +95,6 @@ import org.mockito.kotlin.clearInvocations
@RunWith(AndroidJUnit4::class)
@SmallTest
-@OptIn(kotlinx.coroutines.ExperimentalCoroutinesApi::class)
class ClockEventControllerTest : SysuiTestCase() {
private val kosmos = testKosmos()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/data/repository/AccessibilityQsShortcutsRepositoryImplForDeviceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/accessibility/data/repository/AccessibilityQsShortcutsRepositoryImplForDeviceTest.kt
index 4e0ebae6a902..c6ede0c8c602 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/data/repository/AccessibilityQsShortcutsRepositoryImplForDeviceTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/data/repository/AccessibilityQsShortcutsRepositoryImplForDeviceTest.kt
@@ -36,7 +36,6 @@ import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.settings.FakeSettings
import com.android.systemui.utils.FieldSetter
import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
@@ -55,7 +54,6 @@ import org.mockito.junit.MockitoRule
* can't mock the AccessibilityShortcutInfo for test. MultiValentTest doesn't compile when using
* newly introduced methods and constants.
*/
-@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
class AccessibilityQsShortcutsRepositoryImplForDeviceTest : SysuiTestCase() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
index 4baca713e19f..5249620dbdd0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
@@ -63,7 +63,6 @@ import com.android.systemui.testKosmos
import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import com.google.common.truth.Truth.assertThat
import dagger.Lazy
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
@@ -89,7 +88,6 @@ private const val DISPLAY_HEIGHT = 1920
private const val SENSOR_WIDTH = 30
private const val SENSOR_HEIGHT = 60
-@ExperimentalCoroutinesApi
@SmallTest
@RunWith(AndroidJUnit4::class)
@RunWithLooper(setAsMainLooper = true)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingButtonViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingButtonViewModelTest.kt
index 655b2cc2dece..f3aaa3d3c7aa 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingButtonViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingButtonViewModelTest.kt
@@ -32,7 +32,6 @@ import com.android.systemui.res.R
import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.test.runCurrent
@@ -43,7 +42,6 @@ import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
-@ExperimentalCoroutinesApi
@SmallTest
@RunWith(AndroidJUnit4::class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingDeviceItemActionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingDeviceItemActionInteractorTest.kt
index e61acc4e1d0b..9ae57153f3ef 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingDeviceItemActionInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingDeviceItemActionInteractorTest.kt
@@ -33,7 +33,6 @@ import com.android.systemui.plugins.activityStarter
import com.android.systemui.statusbar.phone.SystemUIDialog
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runTest
import org.junit.After
@@ -56,7 +55,6 @@ import org.mockito.kotlin.whenever
@SmallTest
@RunWith(AndroidTestingRunner::class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
-@OptIn(ExperimentalCoroutinesApi::class)
class AudioSharingDeviceItemActionInteractorTest : SysuiTestCase() {
@get:Rule val mockitoRule: MockitoRule = MockitoJUnit.rule()
private val kosmos = testKosmos().apply { testDispatcher = UnconfinedTestDispatcher() }
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 a56c2cb25542..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
@@ -45,7 +45,6 @@ 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.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
@@ -67,7 +66,6 @@ import org.mockito.junit.MockitoRule
@SmallTest
@RunWith(AndroidJUnit4::class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
-@OptIn(ExperimentalCoroutinesApi::class)
@EnableFlags(Flags.FLAG_BLUETOOTH_QS_TILE_DIALOG_AUTO_ON_TOGGLE)
class BluetoothTileDialogViewModelTest : SysuiTestCase() {
@@ -78,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
@@ -108,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()
@@ -131,7 +134,7 @@ class BluetoothTileDialogViewModelTest : SysuiTestCase() {
localBluetoothManager,
bluetoothTileDialogLogger,
testScope.backgroundScope,
- dispatcher
+ dispatcher,
),
// TODO(b/316822488): Create FakeBluetoothAutoOnInteractor.
BluetoothAutoOnInteractor(
@@ -139,7 +142,7 @@ class BluetoothTileDialogViewModelTest : SysuiTestCase() {
localBluetoothManager,
bluetoothAdapter,
testScope.backgroundScope,
- dispatcher
+ dispatcher,
)
),
kosmos.audioSharingInteractor,
@@ -153,7 +156,8 @@ class BluetoothTileDialogViewModelTest : SysuiTestCase() {
dispatcher,
dispatcher,
sharedPreferences,
- mBluetoothTileDialogDelegateDelegateFactory
+ mBluetoothTileDialogDelegateDelegateFactory,
+ bluetoothDetailsContentManagerFactory,
)
whenever(deviceItemInteractor.deviceItemUpdate).thenReturn(MutableSharedFlow())
whenever(deviceItemInteractor.deviceItemUpdateRequest)
@@ -163,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())
@@ -184,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())
@@ -194,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())
@@ -206,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
@@ -219,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)
@@ -236,7 +278,7 @@ class BluetoothTileDialogViewModelTest : SysuiTestCase() {
val actual =
BluetoothTileDialogViewModel.UiProperties.build(
isBluetoothEnabled = true,
- isAutoOnToggleFeatureAvailable = true
+ isAutoOnToggleFeatureAvailable = true,
)
assertThat(actual.autoOnToggleVisibility).isEqualTo(GONE)
}
@@ -248,7 +290,7 @@ class BluetoothTileDialogViewModelTest : SysuiTestCase() {
val actual =
BluetoothTileDialogViewModel.UiProperties.build(
isBluetoothEnabled = false,
- isAutoOnToggleFeatureAvailable = true
+ isAutoOnToggleFeatureAvailable = true,
)
assertThat(actual.autoOnToggleVisibility).isEqualTo(VISIBLE)
}
@@ -260,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/graphics/ImageLoaderContentProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/graphics/ImageLoaderContentProviderTest.kt
index 8d9fa6ad6e08..e50035d0067e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/graphics/ImageLoaderContentProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/graphics/ImageLoaderContentProviderTest.kt
@@ -41,7 +41,6 @@ const val AUTHORITY = "exception.provider.authority"
val TEST_URI = Uri.Builder().scheme("content").authority(AUTHORITY).path("path").build()
@SmallTest
-@kotlinx.coroutines.ExperimentalCoroutinesApi
@RunWith(AndroidJUnit4::class)
class ImageLoaderContentProviderTest : SysuiTestCase() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/graphics/ImageLoaderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/graphics/ImageLoaderTest.kt
index 76c3349c25ba..de70bca7e96c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/graphics/ImageLoaderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/graphics/ImageLoaderTest.kt
@@ -29,7 +29,6 @@ import org.junit.Test
import org.junit.runner.RunWith
@SmallTest
-@kotlinx.coroutines.ExperimentalCoroutinesApi
@RunWith(AndroidJUnit4::class)
class ImageLoaderTest : SysuiTestCase() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt
index a3c3d2cdbb43..32b8bb49de96 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt
@@ -70,7 +70,6 @@ import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.settings.fakeSettings
import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
@@ -84,7 +83,6 @@ import org.mockito.Mock
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
-@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidTestingRunner::class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
index ab691c630f97..a17125388ed6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
@@ -59,7 +59,6 @@ import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.settings.FakeSettings
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runTest
@@ -79,7 +78,6 @@ import platform.test.runner.parameterized.Parameter
import platform.test.runner.parameterized.ParameterizedAndroidJunit4
import platform.test.runner.parameterized.Parameters
-@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(ParameterizedAndroidJunit4::class)
@DisableSceneContainer
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorSceneContainerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorSceneContainerTest.kt
index caf08efc4b32..2648c9c6496e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorSceneContainerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorSceneContainerTest.kt
@@ -60,7 +60,6 @@ import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.settings.FakeSettings
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runTest
@@ -79,7 +78,6 @@ import platform.test.runner.parameterized.Parameter
import platform.test.runner.parameterized.ParameterizedAndroidJunit4
import platform.test.runner.parameterized.Parameters
-@OptIn(ExperimentalCoroutinesApi::class)
@FlakyTest(
bugId = 292574995,
detail = "on certain architectures all permutations with startActivity=true is causing failures",
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt
index 418972055324..df24bff43c08 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt
@@ -38,7 +38,6 @@ import com.android.systemui.shade.NotificationPanelView
import com.android.systemui.statusbar.VibratorHelper
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.test.TestScope
import org.junit.Before
@@ -49,7 +48,6 @@ import org.mockito.Mock
import org.mockito.Mockito.mock
import org.mockito.MockitoAnnotations
-@ExperimentalCoroutinesApi
@RunWith(AndroidJUnit4::class)
@SmallTest
class DefaultDeviceEntrySectionTest : SysuiTestCase() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt
index 051aba3d593f..26c13f2a2519 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt
@@ -77,7 +77,6 @@ import com.android.systemui.util.settings.fakeSettings
import com.google.common.truth.Truth
import kotlin.math.min
import kotlin.test.assertEquals
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.map
@@ -90,7 +89,6 @@ import org.mockito.Mock
import org.mockito.Mockito
import org.mockito.MockitoAnnotations
-@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
class KeyguardQuickAffordancesCombinedViewModelTest : SysuiTestCase() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/lifecycle/RepeatWhenAttachedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/lifecycle/RepeatWhenAttachedTest.kt
index e3aeaa85873d..fe5acb5668ac 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/lifecycle/RepeatWhenAttachedTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/lifecycle/RepeatWhenAttachedTest.kt
@@ -28,7 +28,6 @@ import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.DisposableHandle
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.Job
import kotlinx.coroutines.awaitCancellation
import kotlinx.coroutines.launch
@@ -52,7 +51,6 @@ import org.mockito.junit.MockitoJUnit
import org.mockito.kotlin.KArgumentCaptor
import org.mockito.kotlin.argumentCaptor
-@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(JUnit4::class)
class RepeatWhenAttachedTest : SysuiTestCase() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImplTest.kt
index 676d8fa06d82..122af0639030 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImplTest.kt
@@ -73,7 +73,6 @@ 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.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.advanceUntilIdle
import kotlinx.coroutines.test.runCurrent
@@ -117,7 +116,6 @@ private const val SESSION_EMPTY_TITLE = ""
private const val USER_ID = 0
private val DISMISS_INTENT = Intent().apply { action = "dismiss" }
-@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWithLooper(setAsMainLooper = true)
@RunWith(ParameterizedAndroidJunit4::class)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImplTest.kt
index 811d2e2b2b06..b731c4f18c7f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImplTest.kt
@@ -49,7 +49,6 @@ import com.android.systemui.testKosmos
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
import java.util.concurrent.Executor
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
@@ -81,7 +80,6 @@ private const val SMARTSPACE_KEY = "SMARTSPACE_KEY"
private const val SMARTSPACE_PACKAGE = "SMARTSPACE_PKG"
private val SMARTSPACE_INSTANCE_ID = InstanceId.fakeInstanceId(456)!!
-@ExperimentalCoroutinesApi
@SmallTest
@RunWith(AndroidJUnit4::class)
@TestableLooper.RunWithLooper
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorTest.kt
index 496b31990b9d..f9e8cbfc491c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorTest.kt
@@ -80,7 +80,6 @@ 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.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.advanceUntilIdle
import kotlinx.coroutines.test.runCurrent
@@ -123,7 +122,6 @@ private const val SESSION_EMPTY_TITLE = ""
private const val USER_ID = 0
private val DISMISS_INTENT = Intent().apply { action = "dismiss" }
-@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWithLooper(setAsMainLooper = true)
@RunWith(ParameterizedAndroidJunit4::class)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerTest.kt
index 6c8a46f19f2b..a2bd5ec28f08 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerTest.kt
@@ -79,7 +79,6 @@ import java.util.Locale
import javax.inject.Provider
import junit.framework.Assert.assertEquals
import junit.framework.Assert.assertTrue
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
@@ -112,7 +111,6 @@ private val SMARTSPACE_KEY = "smartspace"
private const val PAUSED_LOCAL = "paused local"
private const val PLAYING_LOCAL = "playing local"
-@ExperimentalCoroutinesApi
@SmallTest
@TestableLooper.RunWithLooper(setAsMainLooper = true)
@RunWith(ParameterizedAndroidJunit4::class)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManagerTest.kt
index 072caa74df20..6ca4ae2d2259 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManagerTest.kt
@@ -61,7 +61,6 @@ import com.android.systemui.util.mockito.nullable
import com.android.systemui.util.settings.FakeSettings
import com.android.systemui.utils.os.FakeHandler
import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
@@ -86,7 +85,6 @@ import org.mockito.kotlin.anyOrNull
import org.mockito.kotlin.atLeastOnce
import org.mockito.kotlin.lastValue
-@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
@@ -458,7 +456,6 @@ class MediaHierarchyManagerTest : SysuiTestCase() {
assertThat(mediaHierarchyManager.isCurrentlyInGuidedTransformation()).isTrue()
}
- @OptIn(ExperimentalCoroutinesApi::class)
@Test
fun isCurrentlyInGuidedTransformation_hostsVisible_expandImmediateEnabled_returnsFalse() =
testScope.runTest {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
index 69539743f96f..042eb871e3c9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
@@ -70,7 +70,6 @@ import com.android.wm.shell.bubbles.Bubbles
import com.google.common.truth.Truth.assertThat
import java.util.Optional
import kotlin.test.assertNotNull
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runCurrent
@@ -90,7 +89,6 @@ import org.mockito.MockitoAnnotations
import org.mockito.kotlin.whenever
/** atest SystemUITests:NoteTaskControllerTest */
-@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
internal class NoteTaskControllerTest : SysuiTestCase() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt
index 266cb51cc855..8a4f1ad13b78 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt
@@ -47,7 +47,6 @@ import com.android.systemui.util.time.FakeSystemClock
import com.android.wm.shell.bubbles.Bubbles
import com.google.common.truth.Truth.assertThat
import java.util.Optional
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import org.junit.Before
import org.junit.Rule
import org.junit.Test
@@ -60,7 +59,7 @@ import org.mockito.Mockito.verifyNoMoreInteractions
import org.mockito.MockitoAnnotations.initMocks
/** atest SystemUITests:NoteTaskInitializerTest */
-@OptIn(ExperimentalCoroutinesApi::class, InternalNoteTaskApi::class)
+@OptIn(InternalNoteTaskApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
internal class NoteTaskInitializerTest : SysuiTestCase() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfigTest.kt
index b7fb759eb138..8d7de7e04a03 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfigTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfigTest.kt
@@ -14,8 +14,6 @@
* limitations under the License.
*/
-@file:OptIn(ExperimentalCoroutinesApi::class)
-
package com.android.systemui.notetask.quickaffordance
import android.app.role.RoleManager
@@ -48,7 +46,6 @@ import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.test.runTest
import org.junit.After
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/src/com/android/systemui/reardisplay/RearDisplayCoreStartableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/reardisplay/RearDisplayCoreStartableTest.kt
index c8faa81adffa..cf54df8565d3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/reardisplay/RearDisplayCoreStartableTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/reardisplay/RearDisplayCoreStartableTest.kt
@@ -45,7 +45,6 @@ import org.mockito.kotlin.whenever
/** atest SystemUITests:com.android.systemui.reardisplay.RearDisplayCoreStartableTest */
@SmallTest
-@kotlinx.coroutines.ExperimentalCoroutinesApi
class RearDisplayCoreStartableTest : SysuiTestCase() {
private val kosmos = testKosmos().useUnconfinedTestDispatcher()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplTest.kt
index f695c13a9e62..1474defa1662 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplTest.kt
@@ -38,7 +38,6 @@ import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
import com.google.common.truth.TruthJUnit.assume
import java.util.concurrent.Executor
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
@@ -58,7 +57,6 @@ import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
-@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(Parameterized::class)
class UserTrackerImplTest : SysuiTestCase() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
index 6724f82dfd99..732561e0979b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
@@ -69,7 +69,6 @@ import com.android.systemui.statusbar.lockscreen.lockscreenSmartspaceController
import com.android.systemui.statusbar.notification.stack.notificationStackScrollLayoutController
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.launch
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runTest
@@ -87,7 +86,6 @@ import org.mockito.kotlin.spy
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
-@ExperimentalCoroutinesApi
@RunWith(AndroidTestingRunner::class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
@SmallTest
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
index 49d6909c1f93..856ece7e7ff3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
@@ -89,7 +89,6 @@ import com.android.systemui.window.ui.viewmodel.WindowRootViewModel
import com.google.common.truth.Truth.assertThat
import java.util.Optional
import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.test.TestScope
@@ -116,7 +115,6 @@ import org.mockito.kotlin.clearInvocations
import platform.test.runner.parameterized.ParameterizedAndroidJunit4
import platform.test.runner.parameterized.Parameters
-@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(ParameterizedAndroidJunit4::class)
@RunWithLooper(setAsMainLooper = true)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
index cfc00a918f61..b7040ee2a11e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
@@ -35,7 +35,6 @@ import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.mock
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.After
@@ -69,7 +68,6 @@ private fun <T> anyObject(): T {
@SmallTest
@RunWithLooper(setAsMainLooper = true)
@RunWith(AndroidJUnit4::class)
-@OptIn(ExperimentalCoroutinesApi::class)
class LockscreenShadeTransitionControllerTest : SysuiTestCase() {
private val kosmos =
testKosmos().apply {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/SplitShadeLockScreenOverScrollerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/SplitShadeLockScreenOverScrollerTest.kt
index 98487f7ac059..f8154ddb2a5f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/SplitShadeLockScreenOverScrollerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/SplitShadeLockScreenOverScrollerTest.kt
@@ -3,7 +3,6 @@ package com.android.systemui.statusbar
import android.testing.TestableLooper
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager
import com.android.systemui.plugins.qs.QS
@@ -30,7 +29,6 @@ class SplitShadeLockScreenOverScrollerTest : SysuiTestCase() {
private val configurationController = FakeConfigurationController()
- @OptIn(ExperimentalCoroutinesApi::class)
@Mock private lateinit var scrimController: ScrimController
@Mock private lateinit var statusBarStateController: SysuiStatusBarStateController
private var qS: QS? = null
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt
index 77ca51c5efcb..3c4aeccec2eb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt
@@ -41,7 +41,6 @@ import com.android.systemui.statusbar.window.StatusBarWindowController
import com.android.systemui.statusbar.window.StatusBarWindowControllerStore
import com.android.systemui.util.time.FakeSystemClock
import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.advanceTimeBy
@@ -64,7 +63,6 @@ import org.mockito.kotlin.whenever
@RunWith(AndroidJUnit4::class)
@RunWithLooper(setAsMainLooper = true)
-@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
class SystemStatusAnimationSchedulerImplTest : SysuiTestCase() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/IconManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/IconManagerTest.kt
index c4ef4f978ff8..a16f2f6161ff 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/IconManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/IconManagerTest.kt
@@ -14,8 +14,6 @@
* limitations under the License.
*/
-@file:OptIn(ExperimentalCoroutinesApi::class)
-
package com.android.systemui.statusbar.notification.icon
import android.app.ActivityManager
@@ -43,7 +41,6 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntryB
import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerWithScenesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerWithScenesTest.kt
index 8645a40319f4..7cc1ac1d3806 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerWithScenesTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerWithScenesTest.kt
@@ -75,7 +75,6 @@ import com.android.systemui.util.kotlin.JavaAdapter
import com.android.systemui.wmshell.BubblesManager
import java.util.Optional
import kotlin.test.assertEquals
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.test.runCurrent
import org.junit.Assert
@@ -98,7 +97,6 @@ import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
/** Tests for [NotificationGutsManager] with the scene container enabled. */
-@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
@RunWithLooper
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/airplane/ui/viewmodel/AirplaneModeViewModelImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/airplane/ui/viewmodel/AirplaneModeViewModelImplTest.kt
index 8beed01ffbe4..f4c0e367871f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/airplane/ui/viewmodel/AirplaneModeViewModelImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/airplane/ui/viewmodel/AirplaneModeViewModelImplTest.kt
@@ -29,7 +29,6 @@ import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.runBlocking
@@ -39,7 +38,6 @@ import org.junit.runner.RunWith
import org.mockito.MockitoAnnotations
@SmallTest
-@OptIn(ExperimentalCoroutinesApi::class)
@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
@RunWith(AndroidJUnit4::class)
class AirplaneModeViewModelImplTest : SysuiTestCase() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/model/SystemUiCarrierConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/model/SystemUiCarrierConfigTest.kt
index 4a2f6f281566..df74404aaaf8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/model/SystemUiCarrierConfigTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/model/SystemUiCarrierConfigTest.kt
@@ -25,12 +25,10 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
class SystemUiCarrierConfigTest : SysuiTestCase() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/CarrierConfigRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/CarrierConfigRepositoryImplTest.kt
index 7d101be4e748..d074fc256133 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/CarrierConfigRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/CarrierConfigRepositoryImplTest.kt
@@ -29,7 +29,6 @@ import com.android.systemui.statusbar.pipeline.mobile.data.MobileInputLogger
import com.android.systemui.statusbar.pipeline.mobile.data.model.SystemUiCarrierConfigTest.Companion.createTestConfig
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.test.TestScope
@@ -45,7 +44,6 @@ import org.mockito.MockitoAnnotations
import org.mockito.MockitoSession
import org.mockito.quality.Strictness
-@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
class CarrierConfigRepositoryImplTest : SysuiTestCase() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt
index 36f5236c3936..d05590521fab 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt
@@ -50,7 +50,6 @@ import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.launchIn
@@ -70,7 +69,6 @@ import org.mockito.MockitoAnnotations
* switches over when the value of `demoMode` changes
*/
@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
-@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
class MobileRepositorySwitcherTest : SysuiTestCase() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt
index fd23655ffc1c..02ad90cb4269 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt
@@ -50,7 +50,6 @@ import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import java.io.PrintWriter
import java.io.StringWriter
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
@@ -69,7 +68,6 @@ import org.mockito.Mockito.verify
* properly switches over when the value of `isCarrierMerged` changes.
*/
@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
-@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
class FullMobileConnectionRepositoryTest : SysuiTestCase() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
index 3a25ecb27404..df5c6e916931 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
@@ -98,7 +98,6 @@ import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.mockito.withArgCaptor
import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
@@ -115,7 +114,6 @@ import org.mockito.kotlin.any
import org.mockito.kotlin.argumentCaptor
@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
-@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
class MobileConnectionRepositoryTest : SysuiTestCase() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionTelephonySmokeTests.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionTelephonySmokeTests.kt
index 0d82c79fea79..ec260fcc7a65 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionTelephonySmokeTests.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionTelephonySmokeTests.kt
@@ -49,7 +49,6 @@ import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityMod
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
@@ -92,7 +91,6 @@ import org.mockito.MockitoAnnotations
* 5. Assert that B has the state sent in step #2
*/
@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
-@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
class MobileConnectionTelephonySmokeTests : SysuiTestCase() {
private lateinit var underTest: MobileConnectionRepositoryImpl
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
index 6c60f55a3904..d1d6e27332b0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
@@ -79,7 +79,6 @@ import com.android.wifitrackerlib.WifiEntry
import com.android.wifitrackerlib.WifiPickerTracker
import com.google.common.truth.Truth.assertThat
import java.util.UUID
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
@@ -101,7 +100,6 @@ import org.mockito.kotlin.mock
import org.mockito.kotlin.whenever
@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
-@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
// This is required because our [SubscriptionManager.OnSubscriptionsChangedListener] uses a looper
// to run the callback and this makes the looper place nicely with TestScope etc.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileViewTest.kt
index 84846a16f39a..ce99e595504d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileViewTest.kt
@@ -44,7 +44,6 @@ import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnec
import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runTest
@@ -57,7 +56,6 @@ import org.mockito.MockitoAnnotations
@SmallTest
@RunWith(AndroidTestingRunner::class)
@RunWithLooper(setAsMainLooper = true)
-@OptIn(ExperimentalCoroutinesApi::class)
class ModernStatusBarMobileViewTest : SysuiTestCase() {
private val kosmos = testKosmos()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImplTest.kt
index 599729d953d4..6a8b783acf73 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImplTest.kt
@@ -53,7 +53,6 @@ import com.google.common.truth.Truth.assertThat
import java.util.Optional
import java.util.function.Consumer
import kotlin.test.Test
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
@@ -71,7 +70,6 @@ import org.mockito.MockitoAnnotations
import org.mockito.kotlin.doThrow
@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
-@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
class DeviceBasedSatelliteRepositoryImplTest : SysuiTestCase() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepositoryImplTest.kt
index 88f262bec123..8a0159ce174e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepositoryImplTest.kt
@@ -47,7 +47,6 @@ import com.android.systemui.statusbar.pipeline.shared.data.repository.Connectivi
import com.android.systemui.testKosmos
import com.android.systemui.tuner.TunerService
import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -60,7 +59,6 @@ import org.mockito.kotlin.mock
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
-@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
class ConnectivityRepositoryImplTest : SysuiTestCase() {
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/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/ravenwood/texts/ravenwood-annotation-allowed-classes.txt b/ravenwood/texts/ravenwood-annotation-allowed-classes.txt
index 383e75bb5122..e202d0ecfa23 100644
--- a/ravenwood/texts/ravenwood-annotation-allowed-classes.txt
+++ b/ravenwood/texts/ravenwood-annotation-allowed-classes.txt
@@ -151,6 +151,10 @@ android.os.Looper
android.os.Message
android.os.MessageQueue
android.os.MessageQueue_ravenwood
+android.os.PerfettoTrace
+android.os.PerfettoTrace$Category
+android.os.PerfettoTrackEventExtra
+android.os.PerfettoTrackEventExtra$NoOpBuilder
android.os.PackageTagsList
android.os.Parcel
android.os.ParcelFileDescriptor
diff --git a/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickController.java b/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickController.java
index 40fb8b671d0c..8e448676c214 100644
--- a/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickController.java
+++ b/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickController.java
@@ -81,6 +81,7 @@ public class AutoclickController extends BaseEventStreamTransformation {
@VisibleForTesting AutoclickSettingsObserver mAutoclickSettingsObserver;
@VisibleForTesting AutoclickIndicatorScheduler mAutoclickIndicatorScheduler;
@VisibleForTesting AutoclickIndicatorView mAutoclickIndicatorView;
+ @VisibleForTesting AutoclickTypePanel mAutoclickTypePanel;
private WindowManager mWindowManager;
public AutoclickController(Context context, int userId, AccessibilityTraceManager trace) {
@@ -123,6 +124,9 @@ public class AutoclickController extends BaseEventStreamTransformation {
mAutoclickIndicatorView = new AutoclickIndicatorView(mContext);
mWindowManager = mContext.getSystemService(WindowManager.class);
+ mAutoclickTypePanel = new AutoclickTypePanel(mContext, mWindowManager);
+
+ mAutoclickTypePanel.show();
mWindowManager.addView(mAutoclickIndicatorView, mAutoclickIndicatorView.getLayoutParams());
}
@@ -167,6 +171,7 @@ public class AutoclickController extends BaseEventStreamTransformation {
mAutoclickIndicatorScheduler.cancel();
mAutoclickIndicatorScheduler = null;
mWindowManager.removeView(mAutoclickIndicatorView);
+ mAutoclickTypePanel.hide();
}
}
@@ -500,6 +505,11 @@ public class AutoclickController extends BaseEventStreamTransformation {
mMetaState = state;
}
+ @VisibleForTesting
+ int getMetaStateForTesting() {
+ return mMetaState;
+ }
+
/**
* Updates delay that should be used when scheduling clicks. The delay will be used only for
* clicks scheduled after this point (pending click tasks are not affected).
diff --git a/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickTypePanel.java b/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickTypePanel.java
new file mode 100644
index 000000000000..1ba574559918
--- /dev/null
+++ b/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickTypePanel.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.accessibility.autoclick;
+
+import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+
+import android.content.Context;
+import android.graphics.PixelFormat;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.WindowInsets;
+import android.view.WindowManager;
+
+import androidx.annotation.NonNull;
+
+import com.android.internal.R;
+
+public class AutoclickTypePanel {
+
+ private final String TAG = AutoclickTypePanel.class.getSimpleName();
+
+ private final Context mContext;
+
+ private final View mContentView;
+
+ private final WindowManager mWindowManager;
+
+ public AutoclickTypePanel(Context context, WindowManager windowManager) {
+ mContext = context;
+ mWindowManager = windowManager;
+
+ mContentView = LayoutInflater.from(context).inflate(
+ R.layout.accessibility_autoclick_type_panel, null);
+ }
+
+ public void show() {
+ mWindowManager.addView(mContentView, getLayoutParams());
+ }
+
+ public void hide() {
+ mWindowManager.removeView(mContentView);
+ }
+
+ /**
+ * Retrieves the layout params for AutoclickIndicatorView, used when it's added to the Window
+ * Manager.
+ */
+ @NonNull
+ private WindowManager.LayoutParams getLayoutParams() {
+ final WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams();
+ layoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+ layoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
+ layoutParams.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
+ layoutParams.setFitInsetsTypes(WindowInsets.Type.statusBars());
+ layoutParams.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+ layoutParams.format = PixelFormat.TRANSLUCENT;
+ layoutParams.setTitle(AutoclickTypePanel.class.getSimpleName());
+ layoutParams.accessibilityTitle =
+ mContext.getString(R.string.accessibility_autoclick_type_settings_panel_title);
+ layoutParams.width = WindowManager.LayoutParams.WRAP_CONTENT;
+ layoutParams.height = WindowManager.LayoutParams.WRAP_CONTENT;
+ // TODO(b/388847771): Compute position based on user interaction.
+ layoutParams.x = 15;
+ layoutParams.y = 90;
+ layoutParams.gravity = Gravity.END | Gravity.BOTTOM;
+
+ return layoutParams;
+ }
+}
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/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 19e7e062758a..ce6e1ba0cfda 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -947,7 +947,6 @@ class StorageManagerService extends IStorageManager.Stub
refreshZramSettings();
if (mmdEnabled()) {
- // TODO: b/375432472 - Start zram maintenance only when zram is enabled.
ZramMaintenance.startZramMaintenance(mContext);
} else {
// Schedule zram writeback unless zram is disabled by persist.sys.zram_enabled
diff --git a/services/core/java/com/android/server/UiModeManagerService.java b/services/core/java/com/android/server/UiModeManagerService.java
index 896c9b8d0932..c65981bf0703 100644
--- a/services/core/java/com/android/server/UiModeManagerService.java
+++ b/services/core/java/com/android/server/UiModeManagerService.java
@@ -16,11 +16,13 @@
package com.android.server;
-import static android.app.Flags.modesApi;
import static android.app.Flags.enableCurrentModeTypeBinderCache;
import static android.app.Flags.enableNightModeBinderCache;
+import static android.app.Flags.modesApi;
import static android.app.UiModeManager.ContrastUtils.CONTRAST_DEFAULT_VALUE;
import static android.app.UiModeManager.DEFAULT_PRIORITY;
+import static android.app.UiModeManager.FORCE_INVERT_TYPE_DARK;
+import static android.app.UiModeManager.FORCE_INVERT_TYPE_OFF;
import static android.app.UiModeManager.MODE_ATTENTION_THEME_OVERLAY_OFF;
import static android.app.UiModeManager.MODE_NIGHT_AUTO;
import static android.app.UiModeManager.MODE_NIGHT_CUSTOM;
@@ -33,6 +35,7 @@ import static android.app.UiModeManager.PROJECTION_TYPE_AUTOMOTIVE;
import static android.app.UiModeManager.PROJECTION_TYPE_NONE;
import static android.os.UserHandle.USER_SYSTEM;
import static android.os.UserHandle.getCallingUserId;
+import static android.provider.Settings.Secure.ACCESSIBILITY_FORCE_INVERT_COLOR_ENABLED;
import static android.provider.Settings.Secure.CONTRAST_LEVEL;
import static android.util.TimeUtils.isTimeBetween;
@@ -45,6 +48,7 @@ import android.annotation.Nullable;
import android.app.Activity;
import android.app.ActivityManager;
import android.app.ActivityTaskManager;
+import android.app.ActivityThread;
import android.app.AlarmManager;
import android.app.IOnProjectionStateChangedListener;
import android.app.IUiModeManager;
@@ -56,6 +60,7 @@ import android.app.PendingIntent;
import android.app.StatusBarManager;
import android.app.UiModeManager;
import android.app.UiModeManager.AttentionModeThemeOverlayType;
+import android.app.UiModeManager.ForceInvertType;
import android.app.UiModeManager.NightModeCustomReturnType;
import android.app.UiModeManager.NightModeCustomType;
import android.content.BroadcastReceiver;
@@ -93,6 +98,7 @@ import android.service.vr.IVrStateCallbacks;
import android.util.ArraySet;
import android.util.Slog;
import android.util.SparseArray;
+import android.util.SparseIntArray;
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
@@ -256,6 +262,9 @@ final class UiModeManagerService extends SystemService {
@GuardedBy("mLock")
private final SparseArray<Float> mContrasts = new SparseArray<>();
+ @GuardedBy("mLock")
+ private final SparseIntArray mForceInvertStates = new SparseIntArray();
+
public UiModeManagerService(Context context) {
this(context, /* setupWizardComplete= */ false, /* tm= */ null, new Injector());
}
@@ -407,9 +416,33 @@ final class UiModeManagerService extends SystemService {
@Override
public void onChange(boolean selfChange, Uri uri) {
updateSystemProperties();
+ updateForceInvertStates();
}
};
+ private final ContentObserver mForceInvertStateObserver = new ContentObserver(mHandler) {
+ @Override
+ public void onChange(boolean selfChange, Uri uri) {
+ updateForceInvertStates();
+ }
+ };
+
+ private void updateForceInvertStates() {
+ if (!android.view.accessibility.Flags.forceInvertColor()) {
+ return;
+ }
+
+ synchronized (mLock) {
+ if (updateForceInvertStateLocked()) {
+ int forceInvertState = getForceInvertStateLocked();
+ mUiModeManagerCallbacks.get(mCurrentUser, new RemoteCallbackList<>())
+ .broadcast(ignoreRemoteException(
+ callback ->
+ callback.notifyForceInvertStateChanged(forceInvertState)));
+ }
+ }
+ }
+
private final ContentObserver mContrastObserver = new ContentObserver(mHandler) {
@Override
public void onChange(boolean selfChange, Uri uri) {
@@ -485,6 +518,12 @@ final class UiModeManagerService extends SystemService {
context.getContentResolver()
.registerContentObserver(Secure.getUriFor(Secure.UI_NIGHT_MODE),
false, mDarkThemeObserver, 0);
+ if (android.view.accessibility.Flags.forceInvertColor()) {
+ context.getContentResolver()
+ .registerContentObserver(
+ Secure.getUriFor(ACCESSIBILITY_FORCE_INVERT_COLOR_ENABLED),
+ false, mForceInvertStateObserver, UserHandle.USER_ALL);
+ }
context.getContentResolver().registerContentObserver(
Secure.getUriFor(Secure.CONTRAST_LEVEL), false,
mContrastObserver, UserHandle.USER_ALL);
@@ -919,7 +958,7 @@ final class UiModeManagerService extends SystemService {
@android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_DAY_NIGHT_MODE)
@Override
- public @NightModeCustomReturnType int getNightModeCustomType() {
+ public @NightModeCustomReturnType int getNightModeCustomType() {
getNightModeCustomType_enforcePermission();
synchronized (mLock) {
return mNightModeCustomType;
@@ -1275,6 +1314,14 @@ final class UiModeManagerService extends SystemService {
return getContrastLocked();
}
}
+
+ @Override
+ @ForceInvertType
+ public int getForceInvertState() {
+ synchronized (mLock) {
+ return getForceInvertStateLocked();
+ }
+ }
};
private void enforceProjectionTypePermissions(@UiModeManager.ProjectionType int p) {
@@ -1358,6 +1405,76 @@ final class UiModeManagerService extends SystemService {
}
/**
+ * Return the force invert for the current user. If not cached, fetch it from the settings.
+ */
+ @GuardedBy("mLock")
+ @ForceInvertType
+ private int getForceInvertStateLocked() {
+ if (mForceInvertStates.indexOfKey(mCurrentUser) < 0) {
+ updateForceInvertStateLocked();
+ }
+ return mForceInvertStates.get(mCurrentUser);
+ }
+
+ /**
+ * Read the force invert setting for the current user and update {@link #mForceInvertStates}
+ * if the contrast changed. Returns true if {@link #mForceInvertStates} was updated.
+ */
+ @GuardedBy("mLock")
+ private boolean updateForceInvertStateLocked() {
+ int forceInvertState = getForceInvertStateInternal();
+ if (mForceInvertStates.get(mCurrentUser) != forceInvertState) {
+ mForceInvertStates.put(mCurrentUser, forceInvertState);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Returns the current state of force invert, which modifies the display colors to
+ * enhance visibility based on the system's dark theme settings and app-specific configurations.
+ *
+ * <p>This method is for informational purposes only. The application does not need to
+ * implement any special handling for force invert; the system applies it automatically.
+ * If you want to prevent force invert from affecting your app, ensure you have defined
+ * both light and dark themes. Force invert is not applied to apps that already adapt
+ * to the user's system theme preference.</p>
+ *
+ * @return The current force invert state, represented by a {@code ForceDarkType} constant.
+ *
+ * @hide
+ */
+ private int getForceInvertStateInternal() {
+ if (!android.view.accessibility.Flags.forceInvertColor()) {
+ return FORCE_INVERT_TYPE_OFF;
+ }
+
+ if (!isSystemInDarkTheme()) {
+ return FORCE_INVERT_TYPE_OFF;
+ }
+
+ if (!isForceInvert()) {
+ return FORCE_INVERT_TYPE_OFF;
+ }
+
+ return FORCE_INVERT_TYPE_DARK;
+ }
+
+ private boolean isSystemInDarkTheme() {
+ Context sysUiContext = ActivityThread.currentActivityThread().getSystemUiContext();
+ int sysUiNightMode = sysUiContext.getResources().getConfiguration().uiMode
+ & Configuration.UI_MODE_NIGHT_MASK;
+ return sysUiNightMode == Configuration.UI_MODE_NIGHT_YES;
+ }
+
+ private boolean isForceInvert() {
+ return Settings.Secure.getIntForUser(
+ getContext().getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_FORCE_INVERT_COLOR_ENABLED,
+ /* def= */ 0, mCurrentUser) == 1;
+ }
+
+ /**
* Return the contrast for the current user. If not cached, fetch it from the settings.
*/
@GuardedBy("mLock")
@@ -1967,6 +2084,14 @@ final class UiModeManagerService extends SystemService {
sendConfigurationAndStartDreamOrDockAppLocked(category);
}
+ private boolean shouldStartDockApp(Context context, Intent homeIntent) {
+ if (mWatch && !mSetupWizardComplete) {
+ // Do not ever start dock app when setup is not complete on a watch.
+ return false;
+ }
+ return Sandman.shouldStartDockApp(context, homeIntent);
+ }
+
private void sendConfigurationAndStartDreamOrDockAppLocked(String category) {
// Update the configuration but don't send it yet.
mHoldingConfiguration = false;
@@ -1983,7 +2108,7 @@ final class UiModeManagerService extends SystemService {
// activity manager take care of both the start and config
// change.
Intent homeIntent = buildHomeIntent(category);
- if (Sandman.shouldStartDockApp(getContext(), homeIntent)) {
+ if (shouldStartDockApp(getContext(), homeIntent)) {
try {
int result = ActivityTaskManager.getService().startActivityWithConfig(
null, getContext().getBasePackageName(),
diff --git a/services/core/java/com/android/server/ZramMaintenance.java b/services/core/java/com/android/server/ZramMaintenance.java
index cdb48122e321..099e5b3fe440 100644
--- a/services/core/java/com/android/server/ZramMaintenance.java
+++ b/services/core/java/com/android/server/ZramMaintenance.java
@@ -24,11 +24,15 @@ import android.content.ComponentName;
import android.content.Context;
import android.os.IBinder;
import android.os.IMmd;
+import android.os.PersistableBundle;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemProperties;
+import android.provider.DeviceConfig;
import android.util.Slog;
+import com.android.internal.annotations.VisibleForTesting;
+
import java.time.Duration;
/**
@@ -46,43 +50,45 @@ public class ZramMaintenance extends JobService {
private static final String TAG = ZramMaintenance.class.getName();
// Job id must be unique across all clients of the same uid. ZramMaintenance uses the bug number
// as the job id.
- private static final int JOB_ID = 375432472;
+ @VisibleForTesting
+ public static final int JOB_ID = 375432472;
private static final ComponentName sZramMaintenance =
new ComponentName("android", ZramMaintenance.class.getName());
+ @VisibleForTesting
+ public static final String KEY_CHECK_STATUS = "check_status";
+ private static final String SYSTEM_PROPERTY_PREFIX = "mm.";
private static final String FIRST_DELAY_SECONDS_PROP =
- "mm.zram.maintenance.first_delay_seconds";
+ "zram.maintenance.first_delay_seconds";
// The default is 1 hour.
private static final long DEFAULT_FIRST_DELAY_SECONDS = 3600;
private static final String PERIODIC_DELAY_SECONDS_PROP =
- "mm.zram.maintenance.periodic_delay_seconds";
+ "zram.maintenance.periodic_delay_seconds";
// The default is 1 hour.
private static final long DEFAULT_PERIODIC_DELAY_SECONDS = 3600;
private static final String REQUIRE_DEVICE_IDLE_PROP =
- "mm.zram.maintenance.require_device_idle";
+ "zram.maintenance.require_device_idle";
private static final boolean DEFAULT_REQUIRE_DEVICE_IDLE =
true;
private static final String REQUIRE_BATTERY_NOT_LOW_PROP =
- "mm.zram.maintenance.require_battry_not_low";
+ "zram.maintenance.require_battry_not_low";
private static final boolean DEFAULT_REQUIRE_BATTERY_NOT_LOW =
true;
@Override
public boolean onStartJob(JobParameters params) {
- IBinder binder = ServiceManager.getService("mmd");
- if (binder != null) {
- IMmd mmd = IMmd.Stub.asInterface(binder);
- try {
- mmd.doZramMaintenance();
- } catch (RemoteException e) {
- Slog.e(TAG, "Failed to doZramMaintenance", e);
+ new Thread("ZramMaintenance") {
+ @Override
+ public void run() {
+ try {
+ IBinder binder = ServiceManager.getService("mmd");
+ IMmd mmd = IMmd.Stub.asInterface(binder);
+ startJob(ZramMaintenance.this, params, mmd);
+ } finally {
+ jobFinished(params, false);
+ }
}
- } else {
- Slog.w(TAG, "binder not found");
- }
- Duration delay = Duration.ofSeconds(SystemProperties.getLong(PERIODIC_DELAY_SECONDS_PROP,
- DEFAULT_PERIODIC_DELAY_SECONDS));
- scheduleZramMaintenance(this, delay);
+ }.start();
return true;
}
@@ -92,27 +98,75 @@ public class ZramMaintenance extends JobService {
}
/**
+ * This is public to test ZramMaintenance logic.
+ *
+ * <p>
+ * We need to pass mmd as parameter because we can't mock "IMmd.Stub.asInterface".
+ *
+ * <p>
+ * Since IMmd.isZramMaintenanceSupported() is blocking call, this method should be executed on
+ * a worker thread.
+ */
+ @VisibleForTesting
+ public static void startJob(Context context, JobParameters params, IMmd mmd) {
+ boolean checkStatus = params.getExtras().getBoolean(KEY_CHECK_STATUS);
+ if (mmd != null) {
+ try {
+ if (checkStatus && !mmd.isZramMaintenanceSupported()) {
+ Slog.i(TAG, "zram maintenance is not supported");
+ return;
+ }
+ // Status check is required before the first doZramMaintenanceAsync() call once.
+ checkStatus = false;
+
+ mmd.doZramMaintenanceAsync();
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to binder call to mmd", e);
+ }
+ } else {
+ Slog.w(TAG, "binder not found");
+ }
+ Duration delay = Duration.ofSeconds(getLongProperty(PERIODIC_DELAY_SECONDS_PROP,
+ DEFAULT_PERIODIC_DELAY_SECONDS));
+ scheduleZramMaintenance(context, delay, checkStatus);
+ }
+
+ /**
* Starts periodical zram maintenance.
*/
public static void startZramMaintenance(Context context) {
Duration delay = Duration.ofSeconds(
- SystemProperties.getLong(FIRST_DELAY_SECONDS_PROP, DEFAULT_FIRST_DELAY_SECONDS));
- scheduleZramMaintenance(context, delay);
+ getLongProperty(FIRST_DELAY_SECONDS_PROP, DEFAULT_FIRST_DELAY_SECONDS));
+ scheduleZramMaintenance(context, delay, true);
}
- private static void scheduleZramMaintenance(Context context, Duration delay) {
+ private static void scheduleZramMaintenance(Context context, Duration delay,
+ boolean checkStatus) {
JobScheduler js = context.getSystemService(JobScheduler.class);
if (js != null) {
+ final PersistableBundle bundle = new PersistableBundle();
+ bundle.putBoolean(KEY_CHECK_STATUS, checkStatus);
js.schedule(new JobInfo.Builder(JOB_ID, sZramMaintenance)
.setMinimumLatency(delay.toMillis())
.setRequiresDeviceIdle(
- SystemProperties.getBoolean(REQUIRE_DEVICE_IDLE_PROP,
+ getBooleanProperty(REQUIRE_DEVICE_IDLE_PROP,
DEFAULT_REQUIRE_DEVICE_IDLE))
.setRequiresBatteryNotLow(
- SystemProperties.getBoolean(REQUIRE_BATTERY_NOT_LOW_PROP,
+ getBooleanProperty(REQUIRE_BATTERY_NOT_LOW_PROP,
DEFAULT_REQUIRE_BATTERY_NOT_LOW))
+ .setExtras(bundle)
.build());
}
}
+
+ private static long getLongProperty(String name, long defaultValue) {
+ return DeviceConfig.getLong(DeviceConfig.NAMESPACE_MM, name,
+ SystemProperties.getLong(SYSTEM_PROPERTY_PREFIX + name, defaultValue));
+ }
+
+ private static boolean getBooleanProperty(String name, boolean defaultValue) {
+ return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_MM, name,
+ SystemProperties.getBoolean(SYSTEM_PROPERTY_PREFIX + name, defaultValue));
+ }
}
diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index 5184a2c4ec30..b5f11e8a0f78 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -186,6 +186,7 @@ public class SettingsToPropertiesMapper {
"core_libraries",
"crumpet",
"dck_framework",
+ "desktop_connectivity",
"desktop_hwsec",
"desktop_stats",
"devoptions_settings",
@@ -218,6 +219,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/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index e8a222625b1b..32c4e9b1727e 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -551,6 +551,11 @@ public class AppOpsService extends IAppOpsService.Stub {
@VisibleForTesting
final Constants mConstants;
+ /**
+ * Some processes in the user may still be running when trying to drop the user's state
+ */
+ private static final long REMOVE_USER_DELAY = 5000L;
+
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
final class UidState {
public final int uid;
@@ -6820,14 +6825,17 @@ public class AppOpsService extends IAppOpsService.Stub {
@Override
public void removeUser(int userHandle) throws RemoteException {
checkSystemUid("removeUser");
- synchronized (AppOpsService.this) {
- final int tokenCount = mOpUserRestrictions.size();
- for (int i = tokenCount - 1; i >= 0; i--) {
- ClientUserRestrictionState opRestrictions = mOpUserRestrictions.valueAt(i);
- opRestrictions.removeUser(userHandle);
+ mHandler.postDelayed(() -> {
+ Slog.i(TAG, "Removing user " + userHandle + " from AppOpsService");
+ synchronized (AppOpsService.this) {
+ final int tokenCount = mOpUserRestrictions.size();
+ for (int i = tokenCount - 1; i >= 0; i--) {
+ ClientUserRestrictionState opRestrictions = mOpUserRestrictions.valueAt(i);
+ opRestrictions.removeUser(userHandle);
+ }
+ removeUidsForUserLocked(userHandle);
}
- removeUidsForUserLocked(userHandle);
- }
+ }, REMOVE_USER_DELAY);
}
@Override
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/input/KeyGestureController.java b/services/core/java/com/android/server/input/KeyGestureController.java
index fb5ce5b4e5fa..41f58ae76a4d 100644
--- a/services/core/java/com/android/server/input/KeyGestureController.java
+++ b/services/core/java/com/android/server/input/KeyGestureController.java
@@ -809,7 +809,7 @@ final class KeyGestureController {
if (firstDown) {
handleKeyGesture(deviceId, new int[]{KeyEvent.KEYCODE_FULLSCREEN},
/* modifierState = */0,
- KeyGestureEvent.KEY_GESTURE_TYPE_MAXIMIZE_FREEFORM_WINDOW,
+ KeyGestureEvent.KEY_GESTURE_TYPE_MULTI_WINDOW_NAVIGATION,
KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId, focusedToken,
/* flags = */0, /* appLaunchData = */null);
}
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/om/OverlayManagerService.java b/services/core/java/com/android/server/om/OverlayManagerService.java
index 8710438d76b3..d6b587b5f16d 100644
--- a/services/core/java/com/android/server/om/OverlayManagerService.java
+++ b/services/core/java/com/android/server/om/OverlayManagerService.java
@@ -251,14 +251,17 @@ public final class OverlayManagerService extends SystemService {
private final Object mLock = new Object();
+ @GuardedBy("mLock")
private final AtomicFile mSettingsFile;
private final PackageManagerHelperImpl mPackageManager;
private final UserManagerService mUserManager;
+ @GuardedBy("mLock")
private final OverlayManagerSettings mSettings;
+ @GuardedBy("mLock")
private final OverlayManagerServiceImpl mImpl;
private final OverlayActorEnforcer mActorEnforcer;
@@ -296,7 +299,9 @@ public final class OverlayManagerService extends SystemService {
UserManagerInternal umi = LocalServices.getService(UserManagerInternal.class);
umi.addUserLifecycleListener(new UserLifecycleListener());
- restoreSettings();
+ // No async stuff happening in the constructor yet, so it's OK to call a ...Locked()
+ // method without a lock here.
+ restoreSettingsLocked();
// Wipe all shell overlays on boot, to recover from a potentially broken device
String shellPkgName = TextUtils.emptyIfNull(
@@ -403,6 +408,20 @@ public final class OverlayManagerService extends SystemService {
return userIds;
}
+ /**
+ * Ensure that the caller has permission to interact with the given userId.
+ * If the calling user is not the same as the provided user, the caller needs
+ * to hold the INTERACT_ACROSS_USERS_FULL permission (or be system uid or
+ * root).
+ *
+ * @param userId the user to interact with
+ * @param message message for any SecurityException
+ */
+ static int handleIncomingUser(final int userId, @NonNull final String message) {
+ return ActivityManager.handleIncomingUser(Binder.getCallingPid(),
+ Binder.getCallingUid(), userId, false, true, message, null);
+ }
+
private void handlePackageAdd(String packageName, Bundle extras, int userId) {
final boolean replacing = extras.getBoolean(Intent.EXTRA_REPLACING, false);
if (replacing) {
@@ -902,25 +921,28 @@ public final class OverlayManagerService extends SystemService {
throws RemoteException {
try {
traceBegin(TRACE_TAG_RRO, "OMS#commit " + transaction);
- try {
- executeAllRequests(transaction);
- } catch (Exception e) {
- final long ident = Binder.clearCallingIdentity();
+ synchronized (mLock) {
try {
- restoreSettings();
- } finally {
- Binder.restoreCallingIdentity(ident);
+ executeAllRequestsLocked(transaction);
+ } catch (Exception e) {
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ restoreSettingsLocked();
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ Slog.d(TAG, "commit failed: " + e.getMessage(), e);
+ throw new SecurityException("commit failed"
+ + (DEBUG || Build.IS_DEBUGGABLE ? ": " + e.getMessage() : ""));
}
- Slog.d(TAG, "commit failed: " + e.getMessage(), e);
- throw new SecurityException("commit failed"
- + (DEBUG || Build.IS_DEBUGGABLE ? ": " + e.getMessage() : ""));
}
} finally {
traceEnd(TRACE_TAG_RRO);
}
}
- private Set<UserPackage> executeRequest(
+ @GuardedBy("mLock")
+ private Set<UserPackage> executeRequestLocked(
@NonNull final OverlayManagerTransaction.Request request)
throws OperationFailedException {
Objects.requireNonNull(request, "Transaction contains a null request");
@@ -995,33 +1017,29 @@ public final class OverlayManagerService extends SystemService {
}
}
- private void executeAllRequests(@NonNull final OverlayManagerTransaction transaction)
+ @GuardedBy("mLock")
+ private void executeAllRequestsLocked(@NonNull final OverlayManagerTransaction transaction)
throws OperationFailedException {
if (DEBUG) {
Slog.d(TAG, "commit " + transaction);
}
- if (transaction == null) {
- throw new IllegalArgumentException("null transaction");
- }
- synchronized (mLock) {
- // execute the requests (as calling user)
- Set<UserPackage> affectedPackagesToUpdate = null;
- for (Iterator<Request> it = transaction.getRequests(); it.hasNext(); ) {
- Request request = it.next();
- affectedPackagesToUpdate = CollectionUtils.addAll(affectedPackagesToUpdate,
- executeRequest(request));
- }
+ // execute the requests (as calling user)
+ Set<UserPackage> affectedPackagesToUpdate = null;
+ for (Iterator<Request> it = transaction.getRequests(); it.hasNext(); ) {
+ Request request = it.next();
+ affectedPackagesToUpdate = CollectionUtils.addAll(affectedPackagesToUpdate,
+ executeRequestLocked(request));
+ }
- // past the point of no return: the entire transaction has been
- // processed successfully, we can no longer fail: continue as
- // system_server
- final long ident = Binder.clearCallingIdentity();
- try {
- updateTargetPackagesLocked(affectedPackagesToUpdate);
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
+ // past the point of no return: the entire transaction has been
+ // processed successfully, we can no longer fail: continue as
+ // system_server
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ updateTargetPackagesLocked(affectedPackagesToUpdate);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
}
}
@@ -1037,7 +1055,7 @@ public final class OverlayManagerService extends SystemService {
@Override
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
final DumpState dumpState = new DumpState();
- dumpState.setUserId(UserHandle.USER_ALL);
+ int userId = UserHandle.USER_ALL;
int opti = 0;
while (opti < args.length) {
@@ -1064,9 +1082,7 @@ public final class OverlayManagerService extends SystemService {
return;
}
try {
- final int userId = UserHandle.parseUserArg(args[opti]);
- final int realUserId = handleIncomingUser(userId, "dump");
- dumpState.setUserId(realUserId);
+ userId = UserHandle.parseUserArg(args[opti]);
opti++;
} catch (Exception e) {
pw.println("Error: " + e.getMessage());
@@ -1105,6 +1121,9 @@ public final class OverlayManagerService extends SystemService {
}
enforceDumpPermission("dump");
+ final int realUserId = userId != UserHandle.USER_ALL
+ ? handleIncomingUser(userId, "dump") : userId;
+ dumpState.setUserId(realUserId);
synchronized (mLock) {
mImpl.dump(pw, dumpState);
if (dumpState.getPackageName() == null) {
@@ -1114,20 +1133,6 @@ public final class OverlayManagerService extends SystemService {
}
/**
- * Ensure that the caller has permission to interact with the given userId.
- * If the calling user is not the same as the provided user, the caller needs
- * to hold the INTERACT_ACROSS_USERS_FULL permission (or be system uid or
- * root).
- *
- * @param userId the user to interact with
- * @param message message for any SecurityException
- */
- private int handleIncomingUser(final int userId, @NonNull final String message) {
- return ActivityManager.handleIncomingUser(Binder.getCallingPid(),
- Binder.getCallingUid(), userId, false, true, message, null);
- }
-
- /**
* Enforce that the caller holds the DUMP permission (or is system or root).
*
* @param message used as message if SecurityException is thrown
@@ -1447,12 +1452,14 @@ public final class OverlayManagerService extends SystemService {
}
}
+ @GuardedBy("mLock")
private void updateTargetPackagesLocked(@Nullable UserPackage updatedTarget) {
if (updatedTarget != null) {
updateTargetPackagesLocked(Set.of(updatedTarget));
}
}
+ @GuardedBy("mLock")
private void updateTargetPackagesLocked(@Nullable Set<UserPackage> updatedTargets) {
if (CollectionUtils.isEmpty(updatedTargets)) {
return;
@@ -1548,6 +1555,7 @@ public final class OverlayManagerService extends SystemService {
}
@NonNull
+ @GuardedBy("mLock")
private SparseArray<List<String>> updatePackageManagerLocked(
@Nullable Set<UserPackage> targets) {
if (CollectionUtils.isEmpty(targets)) {
@@ -1568,6 +1576,7 @@ public final class OverlayManagerService extends SystemService {
* targetPackageNames: the target themselves and shared libraries)
*/
@NonNull
+ @GuardedBy("mLock")
private List<String> updatePackageManagerLocked(@NonNull Collection<String> targetPackageNames,
final int userId) {
try {
@@ -1623,6 +1632,7 @@ public final class OverlayManagerService extends SystemService {
}
}
+ @GuardedBy("mLock")
private void persistSettingsLocked() {
if (DEBUG) {
Slog.d(TAG, "Writing overlay settings");
@@ -1638,35 +1648,35 @@ public final class OverlayManagerService extends SystemService {
}
}
- private void restoreSettings() {
+ @GuardedBy("mLock")
+ private void restoreSettingsLocked() {
try {
traceBegin(TRACE_TAG_RRO, "OMS#restoreSettings");
- synchronized (mLock) {
- if (!mSettingsFile.getBaseFile().exists()) {
- return;
- }
- try (FileInputStream stream = mSettingsFile.openRead()) {
- mSettings.restore(stream);
- // We might have data for dying users if the device was
- // restarted before we received USER_REMOVED. Remove data for
- // users that will not exist after the system is ready.
+ if (!mSettingsFile.getBaseFile().exists()) {
+ return;
+ }
+ try (FileInputStream stream = mSettingsFile.openRead()) {
+ mSettings.restore(stream);
- final List<UserInfo> liveUsers = mUserManager.getUsers(true /*excludeDying*/);
- final int[] liveUserIds = new int[liveUsers.size()];
- for (int i = 0; i < liveUsers.size(); i++) {
- liveUserIds[i] = liveUsers.get(i).getUserHandle().getIdentifier();
- }
- Arrays.sort(liveUserIds);
+ // We might have data for dying users if the device was
+ // restarted before we received USER_REMOVED. Remove data for
+ // users that will not exist after the system is ready.
- for (int userId : mSettings.getUsers()) {
- if (Arrays.binarySearch(liveUserIds, userId) < 0) {
- mSettings.removeUser(userId);
- }
+ final List<UserInfo> liveUsers = mUserManager.getUsers(true /*excludeDying*/);
+ final int[] liveUserIds = new int[liveUsers.size()];
+ for (int i = 0; i < liveUsers.size(); i++) {
+ liveUserIds[i] = liveUsers.get(i).getUserHandle().getIdentifier();
+ }
+ Arrays.sort(liveUserIds);
+
+ for (int userId : mSettings.getUsers()) {
+ if (Arrays.binarySearch(liveUserIds, userId) < 0) {
+ mSettings.removeUser(userId);
}
- } catch (IOException | XmlPullParserException e) {
- Slog.e(TAG, "failed to restore overlay state", e);
}
+ } catch (IOException | XmlPullParserException e) {
+ Slog.e(TAG, "failed to restore overlay state", e);
}
} finally {
traceEnd(TRACE_TAG_RRO);
diff --git a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
index 0e9ec4d71421..02c0190224d0 100644
--- a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
+++ b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
@@ -715,20 +715,11 @@ final class OverlayManagerServiceImpl {
}
void dump(@NonNull final PrintWriter pw, @NonNull DumpState dumpState) {
- Pair<OverlayIdentifier, String> overlayIdmap = null;
- if (dumpState.getPackageName() != null) {
- OverlayIdentifier id = new OverlayIdentifier(dumpState.getPackageName(),
- dumpState.getOverlayName());
- OverlayInfo oi = mSettings.getNullableOverlayInfo(id, USER_SYSTEM);
- if (oi != null) {
- overlayIdmap = new Pair<>(id, oi.baseCodePath);
- }
- }
-
// settings
mSettings.dump(pw, dumpState);
// idmap data
+ final var overlayIdmap = mSettings.getIdentifierAndBaseCodePath(dumpState);
if (dumpState.getField() == null) {
Set<Pair<OverlayIdentifier, String>> allIdmaps = (overlayIdmap != null)
? Set.of(overlayIdmap) : mSettings.getAllIdentifiersAndBaseCodePaths();
diff --git a/services/core/java/com/android/server/om/OverlayManagerSettings.java b/services/core/java/com/android/server/om/OverlayManagerSettings.java
index f9758fcd5d01..e6b1c5f640f2 100644
--- a/services/core/java/com/android/server/om/OverlayManagerSettings.java
+++ b/services/core/java/com/android/server/om/OverlayManagerSettings.java
@@ -212,17 +212,41 @@ final class OverlayManagerSettings {
}
Set<String> getAllBaseCodePaths() {
+ // Overlays installed for multiple users have the same code path, avoid duplicates with Set.
final Set<String> paths = new ArraySet<>();
mItems.forEach(item -> paths.add(item.mBaseCodePath));
return paths;
}
Set<Pair<OverlayIdentifier, String>> getAllIdentifiersAndBaseCodePaths() {
+ // Overlays installed for multiple users have the same code path, avoid duplicates with Set.
final Set<Pair<OverlayIdentifier, String>> set = new ArraySet<>();
- mItems.forEach(item -> set.add(new Pair(item.mOverlay, item.mBaseCodePath)));
+ mItems.forEach(item -> set.add(new Pair<>(item.mOverlay, item.mBaseCodePath)));
return set;
}
+ @Nullable
+ Pair<OverlayIdentifier, String> getIdentifierAndBaseCodePath(@NonNull DumpState dumpState) {
+ if (dumpState.getPackageName() == null) {
+ return null;
+ }
+ OverlayIdentifier id = new OverlayIdentifier(dumpState.getPackageName(),
+ dumpState.getOverlayName());
+ final int userId = dumpState.getUserId();
+ for (int i = 0; i < mItems.size(); i++) {
+ final var item = mItems.get(i);
+ if (userId != UserHandle.USER_ALL && userId != item.mUserId) {
+ continue;
+ }
+ if (!id.equals(item.mOverlay)) {
+ continue;
+ }
+ // Overlays installed for multiple users have the same code path, return first found.
+ return new Pair<>(id, item.mBaseCodePath);
+ }
+ return null;
+ }
+
@NonNull
List<OverlayInfo> removeIf(@NonNull final Predicate<OverlayInfo> predicate, final int userId) {
return removeIf(info -> (predicate.test(info) && info.userId == userId));
diff --git a/services/core/java/com/android/server/om/OverlayManagerShellCommand.java b/services/core/java/com/android/server/om/OverlayManagerShellCommand.java
index b7f21125148c..39a6b1895c3a 100644
--- a/services/core/java/com/android/server/om/OverlayManagerShellCommand.java
+++ b/services/core/java/com/android/server/om/OverlayManagerShellCommand.java
@@ -17,6 +17,7 @@
package com.android.server.om;
import static com.android.internal.content.om.OverlayConfig.PARTITION_ORDER_FILE_PATH;
+import static com.android.server.om.OverlayManagerService.handleIncomingUser;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -145,7 +146,7 @@ final class OverlayManagerShellCommand extends ShellCommand {
out.println(" Load a package and print the value of a given resource");
out.println(" applying the current configuration and enabled overlays.");
out.println(" For a more fine-grained alternative, use 'idmap2 lookup'.");
- out.println(" fabricate [--user USER_ID] [--target-name OVERLAYABLE] --target PACKAGE");
+ out.println(" fabricate [--target-name OVERLAYABLE] --target PACKAGE");
out.println(" --name NAME [--file FILE] ");
out.println(" PACKAGE:TYPE/NAME ENCODED-TYPE-ID|TYPE-NAME ENCODED-VALUE");
out.println(" Create an overlay from a single resource. Caller must be root. Example:");
@@ -160,7 +161,7 @@ final class OverlayManagerShellCommand extends ShellCommand {
final PrintWriter out = getOutPrintWriter();
final PrintWriter err = getErrPrintWriter();
- int userId = UserHandle.USER_SYSTEM;
+ int userId = UserHandle.USER_CURRENT;
String opt;
while ((opt = getNextOption()) != null) {
switch (opt) {
@@ -234,7 +235,7 @@ final class OverlayManagerShellCommand extends ShellCommand {
private int runEnableDisable(final boolean enable) throws RemoteException {
final PrintWriter err = getErrPrintWriter();
- int userId = UserHandle.USER_SYSTEM;
+ int userId = UserHandle.USER_CURRENT;
String opt;
while ((opt = getNextOption()) != null) {
switch (opt) {
@@ -269,7 +270,6 @@ final class OverlayManagerShellCommand extends ShellCommand {
return 1;
}
- int userId = UserHandle.USER_SYSTEM;
String targetPackage = "";
String targetOverlayable = "";
String name = "";
@@ -278,9 +278,6 @@ final class OverlayManagerShellCommand extends ShellCommand {
String config = null;
while ((opt = getNextOption()) != null) {
switch (opt) {
- case "--user":
- userId = UserHandle.parseUserArg(getNextArgRequired());
- break;
case "--target":
targetPackage = getNextArgRequired();
break;
@@ -442,7 +439,7 @@ final class OverlayManagerShellCommand extends ShellCommand {
private int runEnableExclusive() throws RemoteException {
final PrintWriter err = getErrPrintWriter();
- int userId = UserHandle.USER_SYSTEM;
+ int userId = UserHandle.USER_CURRENT;
boolean inCategory = false;
String opt;
while ((opt = getNextOption()) != null) {
@@ -469,7 +466,7 @@ final class OverlayManagerShellCommand extends ShellCommand {
private int runSetPriority() throws RemoteException {
final PrintWriter err = getErrPrintWriter();
- int userId = UserHandle.USER_SYSTEM;
+ int userId = UserHandle.USER_CURRENT;
String opt;
while ((opt = getNextOption()) != null) {
switch (opt) {
@@ -498,7 +495,7 @@ final class OverlayManagerShellCommand extends ShellCommand {
final PrintWriter out = getOutPrintWriter();
final PrintWriter err = getErrPrintWriter();
- int userId = UserHandle.USER_SYSTEM;
+ int userId = UserHandle.USER_CURRENT;
boolean verbose = false;
String opt;
while ((opt = getNextOption()) != null) {
@@ -525,15 +522,16 @@ final class OverlayManagerShellCommand extends ShellCommand {
return 1;
}
+ final int realUserId = handleIncomingUser(userId, "runLookup");
final Resources res;
try {
res = mContext
- .createContextAsUser(UserHandle.of(userId), /* flags */ 0)
+ .createContextAsUser(UserHandle.of(realUserId), /* flags */ 0)
.getPackageManager()
.getResourcesForApplication(packageToLoad);
} catch (PackageManager.NameNotFoundException e) {
err.println(String.format("Error: failed to get resources for package %s for user %d",
- packageToLoad, userId));
+ packageToLoad, realUserId));
return 1;
}
final AssetManager assets = res.getAssets();
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/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/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 09b10739d469..d9c79b5c40bb 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -781,13 +781,71 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
if (mLastWallpaper == null || mFallbackWallpaper == null) return;
final WallpaperConnection systemConnection = mLastWallpaper.connection;
final WallpaperConnection fallbackConnection = mFallbackWallpaper.connection;
+ final WallpaperConnection lockConnection;
+ if (mLastLockWallpaper != null) {
+ lockConnection = mLastLockWallpaper.connection;
+ } else {
+ lockConnection = null;
+ }
if (fallbackConnection == null) {
Slog.w(TAG, "Fallback wallpaper connection has not been created yet!!");
return;
}
- // TODO(b/384520326) Passing DEFAULT_DISPLAY temporarily before we revamp the
- // multi-display supports.
- if (isWallpaperCompatibleForDisplay(DEFAULT_DISPLAY, systemConnection)) {
+
+ if (enableConnectedDisplaysWallpaper()) {
+ mWallpaperDisplayHelper.forEachDisplayData(displayData -> {
+ int displayId = displayData.mDisplayId;
+ // If the display is already connected to the desired wallpaper(s), either the
+ // same wallpaper for both lock and system, or different wallpapers for each,
+ // any existing fallback wallpaper connection will be removed.
+ if (systemConnection.containsDisplay(displayId)
+ && (lockConnection == null || lockConnection.containsDisplay(displayId))) {
+ DisplayConnector fallbackConnector =
+ mFallbackWallpaper.connection.mDisplayConnector.get(displayId);
+ if (fallbackConnector != null && fallbackConnector.mEngine != null) {
+ fallbackConnector.disconnectLocked(mFallbackWallpaper.connection);
+ mFallbackWallpaper.connection.mDisplayConnector.remove(displayId);
+ }
+ return;
+ }
+
+ // Identify if the fallback wallpaper should be use for lock or system or both.
+ int which = 0;
+ if (!systemConnection.containsDisplay(displayId)) {
+ which |= FLAG_SYSTEM;
+ }
+ if (lockConnection == null || !lockConnection.containsDisplay(displayId)) {
+ which |= FLAG_LOCK;
+ }
+ if (mFallbackWallpaper.connection.containsDisplay(displayId)) {
+ // For existing fallback wallpaper connection, update the `which` flags.
+ DisplayConnector fallbackConnector =
+ mFallbackWallpaper.connection.mDisplayConnector.get(displayId);
+ try {
+ if (fallbackConnector != null && fallbackConnector.mEngine != null
+ && fallbackConnector.mWhich != which) {
+ fallbackConnector.mEngine.setWallpaperFlags(which);
+ mWindowManagerInternal.setWallpaperShowWhenLocked(
+ fallbackConnector.mToken,
+ /* showWhenLocked= */ (which & FLAG_LOCK) != 0);
+ fallbackConnector.mWhich = which;
+ }
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to update fallback wallpaper engine flags", e);
+ }
+ } else {
+ // For new fallback connection, establish the connection with the desired
+ // `which` flag.
+ DisplayConnector fallbackConnector =
+ mFallbackWallpaper.connection.getDisplayConnectorOrCreate(displayId);
+ if (fallbackConnector != null && fallbackConnector.mEngine != null) {
+ fallbackConnector.mWhich = which;
+ fallbackConnector.connectLocked(mFallbackWallpaper.connection,
+ mFallbackWallpaper);
+ }
+ }
+ });
+ } else if (isWallpaperCompatibleForDisplay(DEFAULT_DISPLAY, systemConnection)) {
if (fallbackConnection.mDisplayConnector.size() != 0) {
fallbackConnection.forEachDisplayConnector(connector -> {
if (connector.mEngine != null) {
@@ -820,8 +878,13 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
boolean mDimensionsChanged;
boolean mPaddingChanged;
- DisplayConnector(int displayId) {
+ // This field is added for the fallback wallpaper, which may have a different which flag for
+ // a different display.
+ int mWhich;
+
+ DisplayConnector(int displayId, int which) {
mDisplayId = displayId;
+ mWhich = which;
}
void ensureStatusHandled() {
@@ -850,13 +913,17 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
Slog.w(TAG, "WallpaperService is not connected yet");
return;
}
+ int which = wallpaper.mWhich;
+ if (enableConnectedDisplaysWallpaper()) {
+ which = mWhich;
+ }
TimingsTraceAndSlog t = new TimingsTraceAndSlog(TAG);
t.traceBegin("WPMS.connectLocked-" + wallpaper.getComponent());
if (DEBUG) Slog.v(TAG, "Adding window token: " + mToken);
mWindowManagerInternal.addWindowToken(mToken, TYPE_WALLPAPER, mDisplayId,
null /* options */);
mWindowManagerInternal.setWallpaperShowWhenLocked(
- mToken, (wallpaper.mWhich & FLAG_LOCK) != 0);
+ mToken, (which & FLAG_LOCK) != 0);
if (multiCrop() && mImageWallpaper.equals(wallpaper.getComponent())) {
mWindowManagerInternal.setWallpaperCropHints(mToken,
mWallpaperCropper.getRelativeCropHints(wallpaper));
@@ -868,16 +935,15 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
try {
if (liveWallpaperContentHandling()) {
connection.mService.attach(connection, mToken, TYPE_WALLPAPER, false,
- wpdData.mWidth, wpdData.mHeight,
- wpdData.mPadding, mDisplayId, wallpaper.mWhich, connection.mInfo,
- wallpaper.getDescription());
+ wpdData.mWidth, wpdData.mHeight, wpdData.mPadding, mDisplayId, which,
+ connection.mInfo, wallpaper.getDescription());
} else {
WallpaperDescription desc = new WallpaperDescription.Builder().setComponent(
(connection.mInfo != null) ? connection.mInfo.getComponent()
: null).build();
connection.mService.attach(connection, mToken, TYPE_WALLPAPER, false,
- wpdData.mWidth, wpdData.mHeight,
- wpdData.mPadding, mDisplayId, wallpaper.mWhich, connection.mInfo, desc);
+ wpdData.mWidth, wpdData.mHeight, wpdData.mPadding, mDisplayId, which,
+ connection.mInfo, desc);
}
} catch (RemoteException e) {
Slog.w(TAG, "Failed attaching wallpaper on display", e);
@@ -980,7 +1046,8 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
final int displayId = display.getDisplayId();
final DisplayConnector connector = mDisplayConnector.get(displayId);
if (connector == null) {
- mDisplayConnector.append(displayId, new DisplayConnector(displayId));
+ mDisplayConnector.append(displayId,
+ new DisplayConnector(displayId, mWallpaper.mWhich));
}
}
}
@@ -1006,7 +1073,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
DisplayConnector connector = mDisplayConnector.get(displayId);
if (connector == null) {
if (mWallpaperDisplayHelper.isUsableDisplay(displayId, mClientUid)) {
- connector = new DisplayConnector(displayId);
+ connector = new DisplayConnector(displayId, mWallpaper.mWhich);
mDisplayConnector.append(displayId, connector);
}
}
@@ -1364,6 +1431,17 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
Slog.v(TAG, "static system+lock to system failure");
}
WallpaperData currentSystem = mWallpaperMap.get(mNewWallpaper.userId);
+ // In the constructor, we copied the system+lock wallpaper to
+ // mOriginalSystem. However, the copied WallpaperData#connection is a
+ // reference, not a deep copy. This means
+ // currentSystem.connection.mWallpaper points to mOriginalSystem, so
+ // changes to currentSystem.mWhich alone won't update the corresponding
+ // flag in currentSystem.connection.mWallpaper.mWhich. Let's point
+ // currentSystem.connection.mWallpaper back to currentSystem.
+ if (enableConnectedDisplaysWallpaper()
+ && currentSystem.connection != null) {
+ currentSystem.connection.mWallpaper = currentSystem;
+ }
currentSystem.mWhich = FLAG_SYSTEM | FLAG_LOCK;
updateEngineFlags(currentSystem);
mLockWallpaperMap.remove(mNewWallpaper.userId);
@@ -1385,6 +1463,11 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
}
WallpaperData currentSystem = mWallpaperMap.get(mNewWallpaper.userId);
if (currentSystem.wallpaperId == mOriginalSystem.wallpaperId) {
+ // Fixing the reference, see above for more details.
+ if (enableConnectedDisplaysWallpaper()
+ && currentSystem.connection != null) {
+ currentSystem.connection.mWallpaper = currentSystem;
+ }
currentSystem.mWhich = FLAG_SYSTEM;
updateEngineFlags(currentSystem);
}
@@ -3814,6 +3897,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
wallpaper.connection.forEachDisplayConnector(
connector -> {
try {
+ connector.mWhich = wallpaper.mWhich;
if (connector.mEngine != null) {
connector.mEngine.setWallpaperFlags(wallpaper.mWhich);
mWindowManagerInternal.setWallpaperShowWhenLocked(
@@ -3949,8 +4033,8 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
if (mLastWallpaper == null) {
return;
}
+ int useFallbackWallpaperWhich = 0;
if (enableConnectedDisplaysWallpaper()) {
- int useFallbackWallpaperWhich = 0;
List<WallpaperData> wallpapers = new ArrayList<>();
wallpapers.add(mLastWallpaper);
// If the system and the lock wallpapers are not the same, we should also
@@ -3981,7 +4065,6 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
|| mFallbackWallpaper.connection == null) {
return;
}
- mFallbackWallpaper.mWhich = useFallbackWallpaperWhich;
} else {
if (isWallpaperCompatibleForDisplay(displayId, mLastWallpaper.connection)) {
final DisplayConnector connector =
@@ -3999,6 +4082,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
final DisplayConnector connector = mFallbackWallpaper
.connection.getDisplayConnectorOrCreate(displayId);
if (connector == null) return;
+ connector.mWhich = useFallbackWallpaperWhich;
connector.connectLocked(mFallbackWallpaper.connection, mFallbackWallpaper);
} else {
Slog.w(TAG, "No wallpaper can be added to the new display");
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 89b46bc4eba4..e8498bca1809 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -178,6 +178,7 @@ import static com.android.server.wm.ActivityRecordProto.PROC_ID;
import static com.android.server.wm.ActivityRecordProto.PROVIDES_MAX_BOUNDS;
import static com.android.server.wm.ActivityRecordProto.REPORTED_DRAWN;
import static com.android.server.wm.ActivityRecordProto.REPORTED_VISIBLE;
+import static com.android.server.wm.ActivityRecordProto.REQUEST_OPEN_IN_BROWSER_EDUCATION_TIMESTAMP;
import static com.android.server.wm.ActivityRecordProto.SHOULD_ENABLE_USER_ASPECT_RATIO_SETTINGS;
import static com.android.server.wm.ActivityRecordProto.SHOULD_FORCE_ROTATE_FOR_CAMERA_COMPAT;
import static com.android.server.wm.ActivityRecordProto.SHOULD_IGNORE_ORIENTATION_REQUEST_LOOP;
@@ -9961,6 +9962,8 @@ final class ActivityRecord extends WindowToken {
aspectRatioOverrides.shouldEnableUserAspectRatioSettings());
proto.write(IS_USER_FULLSCREEN_OVERRIDE_ENABLED,
aspectRatioOverrides.isUserFullscreenOverrideEnabled());
+ proto.write(REQUEST_OPEN_IN_BROWSER_EDUCATION_TIMESTAMP,
+ mRequestOpenInBrowserEducationTimestamp);
}
@Override
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index 906befc1edcc..0d88a9b1f8ee 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -145,6 +145,7 @@ import android.util.SparseIntArray;
import android.view.Display;
import android.webkit.URLUtil;
import android.window.ActivityWindowInfo;
+import android.window.DesktopModeFlags;
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
@@ -161,7 +162,6 @@ 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;
@@ -2974,7 +2974,8 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks {
@Override
public void accept(ActivityRecord r) {
- if (Flags.enableDesktopWindowingAppToWeb() && mInfo.capturedLink == null) {
+ if (DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_APP_TO_WEB.isTrue()
+ && mInfo.capturedLink == null) {
setCapturedLink(r);
}
if (r.mLaunchCookie != null) {
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/ImeInsetsSourceProvider.java b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
index 59ca79c7ffc3..cf16204f93a1 100644
--- a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
@@ -182,12 +182,18 @@ final class ImeInsetsSourceProvider extends InsetsSourceProvider {
if (control != null && control.getLeash() != null) {
ImeTracker.Token statsToken = getAndClearStatsToken();
if (statsToken == null) {
- ProtoLog.d(WM_DEBUG_IME, "IME getControl without statsToken");
- } else {
- ImeTracker.forLogging().onProgress(statsToken,
- ImeTracker.PHASE_WM_GET_CONTROL_WITH_LEASH);
- control.setImeStatsToken(statsToken);
+ ProtoLog.w(WM_DEBUG_IME,
+ "IME getControl without statsToken (check previous request!). "
+ + "Start new request");
+ // TODO(b/353463205) remove this later after fixing the race of two requests
+ // that cancel each other (cf. b/383466954#comment19).
+ statsToken = ImeTracker.forLogging().onStart(ImeTracker.TYPE_SHOW,
+ ImeTracker.ORIGIN_SERVER, SoftInputShowHideReason.CONTROLS_CHANGED,
+ false /* fromUser */);
}
+ ImeTracker.forLogging().onProgress(statsToken,
+ ImeTracker.PHASE_WM_GET_CONTROL_WITH_LEASH);
+ control.setImeStatsToken(statsToken);
}
}
return control;
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/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java
index d80fd20dd1e6..ef77a0ee067f 100644
--- a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java
@@ -1028,6 +1028,154 @@ public class WallpaperManagerServiceTests {
}
// Verify a secondary display removes system decorations ended
+ // Test setWallpaperComponent on multiple displays.
+ // GIVEN 3 displays, 0, 2, 3, the new wallpaper is only compatible for display 0 and 3 but not
+ // 2.
+ // WHEN the new wallpaper is set for system and lock via setWallpaperComponent.
+ // THEN there are 2 connections in mLastWallpaper and 1 connection in mFallbackWallpaper.
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_CONNECTED_DISPLAYS_WALLPAPER)
+ public void setWallpaperComponent_multiDisplays_shouldHaveExpectedConnections() {
+ // Skip if there is no pre-defined default wallpaper component.
+ assumeThat(sDefaultWallpaperComponent,
+ not(CoreMatchers.equalTo(sImageWallpaperComponentName)));
+
+ final int testUserId = USER_SYSTEM;
+ mService.switchUser(testUserId, null);
+ final int incompatibleDisplayId = 2;
+ final int compatibleDisplayId = 3;
+ setUpDisplays(List.of(DEFAULT_DISPLAY, incompatibleDisplayId, compatibleDisplayId));
+ mService.removeWallpaperCompatibleDisplayForTest(incompatibleDisplayId);
+
+ mService.setWallpaperComponent(sImageWallpaperComponentName, sContext.getOpPackageName(),
+ FLAG_SYSTEM | FLAG_LOCK, testUserId);
+
+ verifyLastWallpaperData(testUserId, sImageWallpaperComponentName);
+ verifyCurrentSystemData(testUserId);
+ assertThat(mService.mLastWallpaper.connection.getConnectedEngineSize()).isEqualTo(2);
+ assertThat(mService.mLastWallpaper.connection.containsDisplay(DEFAULT_DISPLAY)).isTrue();
+ assertThat(mService.mLastWallpaper.connection.containsDisplay(compatibleDisplayId))
+ .isTrue();
+ assertThat(mService.mFallbackWallpaper.connection.getConnectedEngineSize()).isEqualTo(1);
+ assertThat(mService.mFallbackWallpaper.connection.containsDisplay(incompatibleDisplayId))
+ .isTrue();
+ assertThat(mService.mLastLockWallpaper).isNull();
+ }
+
+ // Test setWallpaperComponent on multiple displays.
+ // GIVEN 3 displays, 0, 2, 3, the existing wallpaper is only compatible for display 0 and 3 but
+ // not 2.
+ // GIVEN the new wallpaper is compatible to 2.
+ // WHEN the new wallpaper is set for system and lock via setWallpaperComponent.
+ // THEN there are 3 connections in mLastWallpaper and 0 connection in mFallbackWallpaper.
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_CONNECTED_DISPLAYS_WALLPAPER)
+ public void setWallpaperComponent_multiDisplays_displayBecomeCompatible_shouldHaveExpectedConnections() {
+ // Skip if there is no pre-defined default wallpaper component.
+ assumeThat(sDefaultWallpaperComponent,
+ not(CoreMatchers.equalTo(sImageWallpaperComponentName)));
+
+ final int testUserId = USER_SYSTEM;
+ mService.switchUser(testUserId, null);
+ final int display2 = 2;
+ final int display3 = 3;
+ setUpDisplays(List.of(DEFAULT_DISPLAY, display2, display3));
+ mService.removeWallpaperCompatibleDisplayForTest(display2);
+ mService.setWallpaperComponent(sImageWallpaperComponentName, sContext.getOpPackageName(),
+ FLAG_SYSTEM | FLAG_LOCK, testUserId);
+
+ mService.addWallpaperCompatibleDisplayForTest(display2);
+ mService.setWallpaperComponent(sImageWallpaperComponentName, sContext.getOpPackageName(),
+ FLAG_SYSTEM | FLAG_LOCK, testUserId);
+
+ verifyLastWallpaperData(testUserId, sImageWallpaperComponentName);
+ verifyCurrentSystemData(testUserId);
+ assertThat(mService.mLastWallpaper.connection.getConnectedEngineSize()).isEqualTo(3);
+ assertThat(mService.mLastWallpaper.connection.containsDisplay(DEFAULT_DISPLAY)).isTrue();
+ assertThat(mService.mLastWallpaper.connection.containsDisplay(display2)).isTrue();
+ assertThat(mService.mLastWallpaper.connection.containsDisplay(display3)).isTrue();
+ assertThat(mService.mFallbackWallpaper.connection.getConnectedEngineSize()).isEqualTo(0);
+ assertThat(mService.mLastLockWallpaper).isNull();
+ }
+
+ // Test setWallpaperComponent on multiple displays.
+ // GIVEN 3 displays, 0, 2, 3, the existing wallpaper is only compatible for display 0 and 3 but
+ // not 2.
+ // GIVEN the new wallpaper is incompatible to 2 and 3.
+ // WHEN the new wallpaper is set for system and lock via setWallpaperComponent.
+ // THEN there are 1 connections in mLastWallpaper and 2 connection in mFallbackWallpaper.
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_CONNECTED_DISPLAYS_WALLPAPER)
+ public void setWallpaperComponent_multiDisplays_displayBecomeIncompatible_shouldHaveExpectedConnections() {
+ // Skip if there is no pre-defined default wallpaper component.
+ assumeThat(sDefaultWallpaperComponent,
+ not(CoreMatchers.equalTo(sImageWallpaperComponentName)));
+
+ final int testUserId = USER_SYSTEM;
+ mService.switchUser(testUserId, null);
+ final int display2 = 2;
+ final int display3 = 3;
+ setUpDisplays(List.of(DEFAULT_DISPLAY, display2, display3));
+ mService.removeWallpaperCompatibleDisplayForTest(display2);
+ mService.setWallpaperComponent(sImageWallpaperComponentName, sContext.getOpPackageName(),
+ FLAG_SYSTEM | FLAG_LOCK, testUserId);
+
+ mService.removeWallpaperCompatibleDisplayForTest(display3);
+ mService.setWallpaperComponent(sImageWallpaperComponentName, sContext.getOpPackageName(),
+ FLAG_SYSTEM | FLAG_LOCK, testUserId);
+
+ verifyLastWallpaperData(testUserId, sImageWallpaperComponentName);
+ verifyCurrentSystemData(testUserId);
+ assertThat(mService.mLastWallpaper.connection.getConnectedEngineSize()).isEqualTo(1);
+ assertThat(mService.mLastWallpaper.connection.containsDisplay(DEFAULT_DISPLAY)).isTrue();
+ assertThat(mService.mFallbackWallpaper.connection.getConnectedEngineSize()).isEqualTo(2);
+ assertThat(mService.mFallbackWallpaper.connection.containsDisplay(display2)).isTrue();
+ assertThat(mService.mFallbackWallpaper.connection.containsDisplay(display3)).isTrue();
+ assertThat(mService.mLastLockWallpaper).isNull();
+ }
+
+ // Test setWallpaperComponent on multiple displays.
+ // GIVEN 3 displays, 0, 2, 3, the new wallpaper is only compatible for display 0 and 3 but not
+ // 2.
+ // WHEN two different wallpapers set for system and lock via setWallpaperComponent.
+ // THEN there are two connections in mLastWallpaper, two connection in mLastLockWallpaper and
+ // one connection in mFallbackWallpaper.
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_CONNECTED_DISPLAYS_WALLPAPER)
+ public void setWallpaperComponent_systemAndLockWallpapers_multiDisplays_shouldHaveExpectedConnections() {
+ // Skip if there is no pre-defined default wallpaper component.
+ assumeThat(sDefaultWallpaperComponent,
+ not(CoreMatchers.equalTo(sImageWallpaperComponentName)));
+
+ final int testUserId = USER_SYSTEM;
+ mService.switchUser(testUserId, null);
+ final int incompatibleDisplayId = 2;
+ final int compatibleDisplayId = 3;
+ setUpDisplays(List.of(DEFAULT_DISPLAY, incompatibleDisplayId, compatibleDisplayId));
+ mService.removeWallpaperCompatibleDisplayForTest(incompatibleDisplayId);
+
+ mService.setWallpaperComponent(sImageWallpaperComponentName, sContext.getOpPackageName(),
+ FLAG_SYSTEM, testUserId);
+ mService.setWallpaperComponent(sImageWallpaperComponentName, sContext.getOpPackageName(),
+ FLAG_LOCK, testUserId);
+
+ verifyLastWallpaperData(testUserId, sImageWallpaperComponentName);
+ verifyLastLockWallpaperData(testUserId, sImageWallpaperComponentName);
+ verifyCurrentSystemData(testUserId);
+ assertThat(mService.mLastWallpaper.connection.getConnectedEngineSize()).isEqualTo(2);
+ assertThat(mService.mLastWallpaper.connection.containsDisplay(DEFAULT_DISPLAY)).isTrue();
+ assertThat(mService.mLastWallpaper.connection.containsDisplay(compatibleDisplayId))
+ .isTrue();
+ assertThat(mService.mLastLockWallpaper.connection.getConnectedEngineSize()).isEqualTo(2);
+ assertThat(mService.mLastLockWallpaper.connection.containsDisplay(DEFAULT_DISPLAY))
+ .isTrue();
+ assertThat(mService.mLastLockWallpaper.connection.containsDisplay(compatibleDisplayId))
+ .isTrue();
+ assertThat(mService.mFallbackWallpaper.connection.getConnectedEngineSize()).isEqualTo(1);
+ assertThat(mService.mFallbackWallpaper.connection.containsDisplay(incompatibleDisplayId))
+ .isTrue();
+ }
+
// Verify that after continue switch user from userId 0 to lastUserId, the wallpaper data for
// non-current user must not bind to wallpaper service.
private void verifyNoConnectionBeforeLastUser(int lastUserId) {
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/accessibility/autoclick/AutoclickControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickControllerTest.java
index f02bdae1d9e6..457fde8d74d0 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickControllerTest.java
@@ -20,11 +20,11 @@ import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentat
import static com.android.server.testutils.MockitoUtilsKt.eq;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
-import static org.testng.AssertJUnit.assertEquals;
-import static org.testng.AssertJUnit.assertNotNull;
-import static org.testng.AssertJUnit.assertNull;
import android.content.Context;
import android.platform.test.annotations.DisableFlags;
@@ -35,6 +35,7 @@ import android.testing.AndroidTestingRunner;
import android.testing.TestableContext;
import android.testing.TestableLooper;
import android.view.InputDevice;
+import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityManager;
@@ -82,69 +83,89 @@ public class AutoclickControllerTest {
@Test
public void onMotionEvent_lazyInitClickScheduler() {
- assertNull(mController.mClickScheduler);
+ assertThat(mController.mClickScheduler).isNull();
injectFakeMouseActionDownEvent();
- assertNotNull(mController.mClickScheduler);
+ assertThat(mController.mClickScheduler).isNotNull();
}
@Test
public void onMotionEvent_nonMouseSource_notInitClickScheduler() {
- assertNull(mController.mClickScheduler);
+ assertThat(mController.mClickScheduler).isNull();
injectFakeNonMouseActionDownEvent();
- assertNull(mController.mClickScheduler);
+ assertThat(mController.mClickScheduler).isNull();
}
@Test
public void onMotionEvent_lazyInitAutoclickSettingsObserver() {
- assertNull(mController.mAutoclickSettingsObserver);
+ assertThat(mController.mAutoclickSettingsObserver).isNull();
injectFakeMouseActionDownEvent();
- assertNotNull(mController.mAutoclickSettingsObserver);
+ assertThat(mController.mAutoclickSettingsObserver).isNotNull();
}
@Test
@EnableFlags(com.android.server.accessibility.Flags.FLAG_ENABLE_AUTOCLICK_INDICATOR)
public void onMotionEvent_flagOn_lazyInitAutoclickIndicatorScheduler() {
- assertNull(mController.mAutoclickIndicatorScheduler);
+ assertThat(mController.mAutoclickIndicatorScheduler).isNull();
injectFakeMouseActionDownEvent();
- assertNotNull(mController.mAutoclickIndicatorScheduler);
+ assertThat(mController.mAutoclickIndicatorScheduler).isNotNull();
}
@Test
@DisableFlags(com.android.server.accessibility.Flags.FLAG_ENABLE_AUTOCLICK_INDICATOR)
public void onMotionEvent_flagOff_notInitAutoclickIndicatorScheduler() {
- assertNull(mController.mAutoclickIndicatorScheduler);
+ assertThat(mController.mAutoclickIndicatorScheduler).isNull();
injectFakeMouseActionDownEvent();
- assertNull(mController.mAutoclickIndicatorScheduler);
+ assertThat(mController.mAutoclickIndicatorScheduler).isNull();
}
@Test
@EnableFlags(com.android.server.accessibility.Flags.FLAG_ENABLE_AUTOCLICK_INDICATOR)
public void onMotionEvent_flagOn_lazyInitAutoclickIndicatorView() {
- assertNull(mController.mAutoclickIndicatorView);
+ assertThat(mController.mAutoclickIndicatorView).isNull();
injectFakeMouseActionDownEvent();
- assertNotNull(mController.mAutoclickIndicatorView);
+ assertThat(mController.mAutoclickIndicatorView).isNotNull();
}
@Test
@DisableFlags(com.android.server.accessibility.Flags.FLAG_ENABLE_AUTOCLICK_INDICATOR)
public void onMotionEvent_flagOff_notInitAutoclickIndicatorView() {
- assertNull(mController.mAutoclickIndicatorView);
+ assertThat(mController.mAutoclickIndicatorView).isNull();
+
+ injectFakeMouseActionDownEvent();
+
+ assertThat(mController.mAutoclickIndicatorView).isNull();
+ }
+
+ @Test
+ @EnableFlags(com.android.server.accessibility.Flags.FLAG_ENABLE_AUTOCLICK_INDICATOR)
+ public void onMotionEvent_flagOn_lazyInitAutoclickTypePanelView() {
+ assertThat(mController.mAutoclickTypePanel).isNull();
+
+ injectFakeMouseActionDownEvent();
+
+ assertThat(mController.mAutoclickTypePanel).isNotNull();
+ }
+
+ @Test
+ @DisableFlags(com.android.server.accessibility.Flags.FLAG_ENABLE_AUTOCLICK_INDICATOR)
+ public void onMotionEvent_flagOff_notInitAutoclickTypePanelView() {
+ assertThat(mController.mAutoclickTypePanel).isNull();
injectFakeMouseActionDownEvent();
- assertNull(mController.mAutoclickIndicatorView);
+ assertThat(mController.mAutoclickTypePanel).isNull();
}
@Test
@@ -166,6 +187,18 @@ public class AutoclickControllerTest {
}
@Test
+ @EnableFlags(com.android.server.accessibility.Flags.FLAG_ENABLE_AUTOCLICK_INDICATOR)
+ public void onDestroy_flagOn_removeAutoclickTypePanelViewToWindowManager() {
+ injectFakeMouseActionDownEvent();
+ AutoclickTypePanel mockAutoclickTypePanel = mock(AutoclickTypePanel.class);
+ mController.mAutoclickTypePanel = mockAutoclickTypePanel;
+
+ mController.onDestroy();
+
+ verify(mockAutoclickTypePanel).hide();
+ }
+
+ @Test
public void onMotionEvent_initClickSchedulerDelayFromSetting() {
injectFakeMouseActionDownEvent();
@@ -175,7 +208,7 @@ public class AutoclickControllerTest {
Settings.Secure.ACCESSIBILITY_AUTOCLICK_DELAY,
AccessibilityManager.AUTOCLICK_DELAY_DEFAULT,
mTestableContext.getUserId());
- assertEquals(delay, mController.mClickScheduler.getDelayForTesting());
+ assertThat(mController.mClickScheduler.getDelayForTesting()).isEqualTo(delay);
}
@Test
@@ -189,7 +222,40 @@ public class AutoclickControllerTest {
Settings.Secure.ACCESSIBILITY_AUTOCLICK_CURSOR_AREA_SIZE,
AccessibilityManager.AUTOCLICK_CURSOR_AREA_SIZE_DEFAULT,
mTestableContext.getUserId());
- assertEquals(size, mController.mAutoclickIndicatorView.getRadiusForTesting());
+ assertThat(mController.mAutoclickIndicatorView.getRadiusForTesting()).isEqualTo(size);
+ }
+
+ @Test
+ @EnableFlags(com.android.server.accessibility.Flags.FLAG_ENABLE_AUTOCLICK_INDICATOR)
+ public void onKeyEvent_modifierKey_doNotUpdateMetaStateWhenControllerIsNull() {
+ assertThat(mController.mClickScheduler).isNull();
+
+ injectFakeKeyEvent(KeyEvent.KEYCODE_ALT_LEFT, KeyEvent.META_ALT_ON);
+
+ assertThat(mController.mClickScheduler).isNull();
+ }
+
+ @Test
+ @EnableFlags(com.android.server.accessibility.Flags.FLAG_ENABLE_AUTOCLICK_INDICATOR)
+ public void onKeyEvent_modifierKey_updateMetaStateWhenControllerNotNull() {
+ injectFakeMouseActionDownEvent();
+
+ int metaState = KeyEvent.META_ALT_ON | KeyEvent.META_META_ON;
+ injectFakeKeyEvent(KeyEvent.KEYCODE_ALT_LEFT, metaState);
+
+ assertThat(mController.mClickScheduler).isNotNull();
+ assertThat(mController.mClickScheduler.getMetaStateForTesting()).isEqualTo(metaState);
+ }
+
+ @Test
+ @EnableFlags(com.android.server.accessibility.Flags.FLAG_ENABLE_AUTOCLICK_INDICATOR)
+ public void onKeyEvent_modifierKey_cancelAutoClickWhenAdditionalRegularKeyPresssed() {
+ injectFakeMouseActionDownEvent();
+
+ injectFakeKeyEvent(KeyEvent.KEYCODE_J, KeyEvent.META_ALT_ON);
+
+ assertThat(mController.mClickScheduler).isNotNull();
+ assertThat(mController.mClickScheduler.getMetaStateForTesting()).isEqualTo(0);
}
@Test
@@ -198,7 +264,7 @@ public class AutoclickControllerTest {
mController.onDestroy();
- assertNull(mController.mClickScheduler);
+ assertThat(mController.mClickScheduler).isNull();
}
@Test
@@ -207,7 +273,7 @@ public class AutoclickControllerTest {
mController.onDestroy();
- assertNull(mController.mAutoclickSettingsObserver);
+ assertThat(mController.mAutoclickSettingsObserver).isNull();
}
@Test
@@ -217,7 +283,7 @@ public class AutoclickControllerTest {
mController.onDestroy();
- assertNull(mController.mAutoclickIndicatorScheduler);
+ assertThat(mController.mAutoclickIndicatorScheduler).isNull();
}
private void injectFakeMouseActionDownEvent() {
@@ -232,6 +298,17 @@ public class AutoclickControllerTest {
mController.onMotionEvent(event, event, /* policyFlags= */ 0);
}
+ private void injectFakeKeyEvent(int keyCode, int modifiers) {
+ KeyEvent keyEvent = new KeyEvent(
+ /* downTime= */ 0,
+ /* eventTime= */ 0,
+ /* action= */ KeyEvent.ACTION_DOWN,
+ /* code= */ keyCode,
+ /* repeat= */ 0,
+ /* metaState= */ modifiers);
+ mController.onKeyEvent(keyEvent, /* policyFlags= */ 0);
+ }
+
private MotionEvent getFakeMotionDownEvent() {
return MotionEvent.obtain(
/* downTime= */ 0,
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/zram/ZramMaintenanceTest.kt b/services/tests/servicestests/src/com/android/server/zram/ZramMaintenanceTest.kt
new file mode 100644
index 000000000000..5448a05aafc3
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/zram/ZramMaintenanceTest.kt
@@ -0,0 +1,155 @@
+/*
+ * 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.server.zram
+
+import android.app.job.JobInfo
+import android.app.job.JobParameters
+import android.app.job.JobScheduler
+import android.os.IMmd
+import android.os.PersistableBundle
+import android.os.RemoteException
+import android.testing.TestableContext
+import androidx.test.filters.SmallTest
+import androidx.test.platform.app.InstrumentationRegistry
+
+import com.android.server.ZramMaintenance
+import com.google.common.truth.Truth.assertThat
+
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.any
+import org.mockito.Mockito.never
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
+import org.mockito.MockitoAnnotations
+
+private fun generateJobParameters(jobId: Int, extras: PersistableBundle): JobParameters {
+ return JobParameters(
+ null, "", jobId, extras, null, null, 0, false, false, false, null, null, null
+ )
+}
+
+@SmallTest
+@RunWith(JUnit4::class)
+class ZramMaintenanceTest {
+ private val context = TestableContext(InstrumentationRegistry.getInstrumentation().context)
+
+ @Captor
+ private lateinit var jobInfoCaptor: ArgumentCaptor<JobInfo>
+
+ @Mock
+ private lateinit var mockJobScheduler: JobScheduler
+
+ @Mock
+ private lateinit var mockMmd: IMmd
+
+ @Before
+ @Throws(RemoteException::class)
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ context.addMockSystemService(JobScheduler::class.java, mockJobScheduler)
+ }
+
+ @Test
+ fun startZramMaintenance() {
+ ZramMaintenance.startZramMaintenance(context)
+
+ verify(mockJobScheduler, times(1)).schedule(jobInfoCaptor.capture())
+ val job = jobInfoCaptor.value
+ assertThat(job.id).isEqualTo(ZramMaintenance.JOB_ID)
+ assertThat(job.extras.getBoolean(ZramMaintenance.KEY_CHECK_STATUS)).isTrue()
+ }
+
+ @Test
+ fun startJobForFirstTime() {
+ val extras = PersistableBundle()
+ extras.putBoolean(ZramMaintenance.KEY_CHECK_STATUS, true)
+ val params = generateJobParameters(
+ ZramMaintenance.JOB_ID,
+ extras,
+ )
+ `when`(mockMmd.isZramMaintenanceSupported()).thenReturn(true)
+
+ ZramMaintenance.startJob(context, params, mockMmd)
+
+ verify(mockMmd, times(1)).isZramMaintenanceSupported()
+ verify(mockMmd, times(1)).doZramMaintenanceAsync()
+ verify(mockJobScheduler, times(1)).schedule(jobInfoCaptor.capture())
+ val nextJob = jobInfoCaptor.value
+ assertThat(nextJob.id).isEqualTo(ZramMaintenance.JOB_ID)
+ assertThat(nextJob.extras.getBoolean(ZramMaintenance.KEY_CHECK_STATUS)).isFalse()
+ }
+
+ @Test
+ fun startJobWithoutCheckStatus() {
+ val extras = PersistableBundle()
+ extras.putBoolean(ZramMaintenance.KEY_CHECK_STATUS, false)
+ val params = generateJobParameters(
+ ZramMaintenance.JOB_ID,
+ extras,
+ )
+
+ ZramMaintenance.startJob(context, params, mockMmd)
+
+ verify(mockMmd, never()).isZramMaintenanceSupported()
+ verify(mockMmd, times(1)).doZramMaintenanceAsync()
+ verify(mockJobScheduler, times(1)).schedule(jobInfoCaptor.capture())
+ val nextJob = jobInfoCaptor.value
+ assertThat(nextJob.id).isEqualTo(ZramMaintenance.JOB_ID)
+ assertThat(nextJob.extras.getBoolean(ZramMaintenance.KEY_CHECK_STATUS)).isFalse()
+ }
+
+ @Test
+ fun startJobZramIsDisabled() {
+ val extras = PersistableBundle()
+ extras.putBoolean(ZramMaintenance.KEY_CHECK_STATUS, true)
+ val params = generateJobParameters(
+ ZramMaintenance.JOB_ID,
+ extras,
+ )
+ `when`(mockMmd.isZramMaintenanceSupported()).thenReturn(false)
+
+ ZramMaintenance.startJob(context, params, mockMmd)
+
+ verify(mockMmd, times(1)).isZramMaintenanceSupported()
+ verify(mockMmd, never()).doZramMaintenanceAsync()
+ verify(mockJobScheduler, never()).schedule(any())
+ }
+
+ @Test
+ fun startJobMmdIsNotReadyYet() {
+ val extras = PersistableBundle()
+ extras.putBoolean(ZramMaintenance.KEY_CHECK_STATUS, true)
+ val params = generateJobParameters(
+ ZramMaintenance.JOB_ID,
+ extras,
+ )
+
+ ZramMaintenance.startJob(context, params, null)
+
+ verify(mockJobScheduler, times(1)).schedule(jobInfoCaptor.capture())
+ val nextJob = jobInfoCaptor.value
+ assertThat(nextJob.id).isEqualTo(ZramMaintenance.JOB_ID)
+ assertThat(nextJob.extras.getBoolean(ZramMaintenance.KEY_CHECK_STATUS)).isTrue()
+ }
+} \ No newline at end of file
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 31f9a1cffe7c..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,9 +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,
- FLAG_NOTIFICATION_CLASSIFICATION, FLAG_NOTIFICATION_CLASSIFICATION_UI,
- FLAG_MODES_UI, android.app.Flags.FLAG_NM_BINDER_PERF_CACHE_CHANNELS);
+ android.app.Flags.FLAG_UI_RICH_ONGOING,
+ FLAG_NOTIFICATION_CLASSIFICATION_UI,
+ android.app.Flags.FLAG_NM_BINDER_PERF_CACHE_CHANNELS);
}
public PreferencesHelperTest(FlagsParameterization flags) {
@@ -662,6 +661,7 @@ public class PreferencesHelperTest extends UiServiceTestCase {
mHelper.setShowBadge(PKG_N_MR1, UID_N_MR1, true);
if (android.app.Flags.uiRichOngoing()) {
+ mHelper.setCanBePromoted(PKG_N_MR1, UID_N_MR1, false, true);
mHelper.setCanBePromoted(PKG_N_MR1, UID_N_MR1, true, true);
}
@@ -6643,6 +6643,8 @@ public class PreferencesHelperTest extends UiServiceTestCase {
@Test
@EnableFlags(android.app.Flags.FLAG_API_RICH_ONGOING)
public void testSetCanBePromoted_allowlistNotOverrideUser() {
+ // default value is true. So we need to set it false to trigger the change.
+ mHelper.setCanBePromoted(PKG_P, UID_P, false, true);
mHelper.setCanBePromoted(PKG_P, UID_P, true, true);
assertThat(mHelper.canBePromoted(PKG_P, UID_P)).isTrue();
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/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/Input/src/com/android/server/input/KeyGestureControllerTests.kt b/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt
index 36db955c3085..37bdf6b8614d 100644
--- a/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt
+++ b/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt
@@ -1111,9 +1111,9 @@ class KeyGestureControllerTests {
intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
),
TestData(
- "FULLSCREEN -> Maximizes a task to fit the screen",
+ "FULLSCREEN -> Turns a task into fullscreen",
intArrayOf(KeyEvent.KEYCODE_FULLSCREEN),
- KeyGestureEvent.KEY_GESTURE_TYPE_MAXIMIZE_FREEFORM_WINDOW,
+ KeyGestureEvent.KEY_GESTURE_TYPE_MULTI_WINDOW_NAVIGATION,
intArrayOf(KeyEvent.KEYCODE_FULLSCREEN),
0,
intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
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) |')