summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--AconfigFlags.bp31
-rw-r--r--apex/jobscheduler/service/aconfig/job.aconfig7
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java25
-rw-r--r--core/java/android/app/assist/AssistStructure.java11
-rw-r--r--core/java/android/app/assist/flags.aconfig13
-rw-r--r--core/java/android/companion/virtual/VirtualDeviceManager.java3
-rw-r--r--core/java/android/companion/virtual/VirtualDeviceParams.java20
-rw-r--r--core/java/android/content/pm/multiuser.aconfig7
-rw-r--r--core/java/android/hardware/display/DisplayManagerInternal.java10
-rw-r--r--core/java/android/hardware/input/InputSettings.java98
-rw-r--r--core/java/android/hardware/input/VirtualInputDeviceConfig.java5
-rw-r--r--core/java/android/os/UserManager.java10
-rw-r--r--core/java/android/os/flags.aconfig16
-rw-r--r--core/java/android/permission/flags.aconfig11
-rw-r--r--core/java/android/provider/Settings.java26
-rw-r--r--core/java/android/window/DesktopModeFlags.java4
-rw-r--r--core/java/android/window/flags/lse_desktop_experience.aconfig2
-rw-r--r--core/jni/android_view_WindowManagerGlobal.cpp6
-rw-r--r--core/jni/platform/host/HostRuntime.cpp2
-rw-r--r--core/proto/android/providers/settings/system.proto11
-rw-r--r--core/tests/coretests/src/android/app/ActivityManagerTest.java1
-rw-r--r--core/tests/coretests/src/android/app/activity/ActivityManagerTest.java6
-rw-r--r--core/tests/coretests/src/android/app/assist/AssistStructureTest.java14
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt15
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt1
-rw-r--r--libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/OpenTransparentActivityTest.kt5
-rw-r--r--libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/QuickSwitchLauncherToLetterboxAppTest.kt4
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeEventLoggerTest.kt32
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt3
-rw-r--r--nfc/api/system-current.txt2
-rw-r--r--nfc/java/android/nfc/INfcCardEmulation.aidl2
-rw-r--r--nfc/java/android/nfc/NfcOemExtension.java9
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt2
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialogWithIcon.kt8
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/BaseLayout.kt5
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/MainSwitchPreference.kt46
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/ZeroStatePreference.kt2
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/IDeviceSettingsConfigProviderService.aidl2
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingServiceConnection.kt55
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/media/InputMediaDevice.java40
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/media/InputRouteManager.java22
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InputMediaDeviceTest.java33
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InputRouteManagerTest.java67
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiUtilsTest.java14
-rw-r--r--packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java2
-rw-r--r--packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java2
-rw-r--r--packages/SystemUI/TEST_MAPPING5
-rw-r--r--packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ComposedDigitalLayerController.kt203
-rw-r--r--packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt192
-rw-r--r--packages/SystemUI/customization/src/com/android/systemui/shared/clocks/LayoutUtils.kt35
-rw-r--r--packages/SystemUI/customization/src/com/android/systemui/shared/clocks/SimpleClockController.kt152
-rw-r--r--packages/SystemUI/customization/src/com/android/systemui/shared/clocks/SimpleClockFaceController.kt314
-rw-r--r--packages/SystemUI/customization/src/com/android/systemui/shared/clocks/SimpleClockLayerController.kt102
-rw-r--r--packages/SystemUI/customization/src/com/android/systemui/shared/clocks/SimpleClockRelativeLayout.kt47
-rw-r--r--packages/SystemUI/customization/src/com/android/systemui/shared/clocks/SimpleDigitalHandLayerController.kt328
-rw-r--r--packages/SystemUI/customization/src/com/android/systemui/shared/clocks/TimespecHandler.kt161
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt (renamed from packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt (renamed from packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt (renamed from packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityViewFlipperControllerTest.java (renamed from packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityViewFlipperControllerTest.java)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt (renamed from packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt (renamed from packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSliceViewControllerTest.java (renamed from packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewControllerTest.java)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/clipboardoverlay/ClipboardOverlaySuppressionControllerImplTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlaySuppressionControllerImplTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt25
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/lifecycle/HydratorTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/lifecycle/HydratorTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/views/NavigationBarButtonTest.java (renamed from packages/SystemUI/tests/src/com/android/systemui/navigationbar/views/NavigationBarButtonTest.java)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/RecordIssueTileTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RecordIssueTileTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/screenrecord/data/model/ScreenRecordModelTest.kt3
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/ScreenshotDataTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotDataTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java25
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/OperatorNameViewControllerTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/statusbar/OperatorNameViewControllerTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt54
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/call/domain/interactor/CallChipInteractorTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/call/domain/interactor/CallChipInteractorTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/casttootherdevice/domian/interactor/MediaRouterChipInteractorTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/casttootherdevice/domian/interactor/MediaRouterChipInteractorTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndCastScreenToOtherDeviceDialogDelegateTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndCastScreenToOtherDeviceDialogDelegateTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndGenericCastToOtherDeviceDialogDelegateTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndGenericCastToOtherDeviceDialogDelegateTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModelTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModelTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/mediaprojection/domain/interactor/MediaProjectionChipInteractorTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/mediaprojection/domain/interactor/MediaProjectionChipInteractorTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndMediaProjectionDialogHelperTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndMediaProjectionDialogHelperTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ron/demo/ui/viewmodel/DemoRonChipViewModelTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ron/demo/ui/viewmodel/DemoRonChipViewModelTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/screenrecord/domain/interactor/ScreenRecordChipInteractorTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/screenrecord/domain/interactor/ScreenRecordChipInteractorTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/screenrecord/ui/view/EndScreenRecordingDialogDelegateTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/screenrecord/ui/view/EndScreenRecordingDialogDelegateTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModelTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModelTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/view/EndShareToAppDialogDelegateTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/view/EndShareToAppDialogDelegateTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModelTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModelTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/ChipTransitionHelperTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/viewmodel/ChipTransitionHelperTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipViewModelTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipViewModelTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithRonsViewModelTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithRonsViewModelTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/AboveShelfObserverTest.java (renamed from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/AboveShelfObserverTest.java)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationTransitionAnimatorControllerTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationTransitionAnimatorControllerTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractorTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractorTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragControllerTest.java (renamed from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragControllerTest.java)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationMenuRowTest.java (renamed from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationMenuRowTest.java)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImplTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImplTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationSnoozeTest.java (renamed from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationSnoozeTest.java)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java (renamed from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationBigPictureTemplateViewWrapperTest.java (renamed from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationBigPictureTemplateViewWrapperTest.java)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapperTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapperTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCustomViewWrapperTest.java (renamed from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCustomViewWrapperTest.java)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMessagingTemplateViewWrapperTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMessagingTemplateViewWrapperTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapperTest.java (renamed from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapperTest.java)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java (renamed from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationTargetsHelperTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationTargetsHelperTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java (renamed from packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java (renamed from packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewTest.java (renamed from packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewTest.java)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/LightBarTransitionsControllerTest.java (renamed from packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarTransitionsControllerTest.java)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java (renamed from packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialogTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialogTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelImplTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelImplTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/CastDeviceTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/CastDeviceTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegateTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegateTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/BackGestureMonitorTest.kt13
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/EasterEggGestureTest.kt5
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/HomeGestureMonitorTest.kt13
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/RecentAppsGestureMonitorTest.kt15
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureHandlerTest.kt17
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/wallpapers/data/repository/WallpaperRepositoryImplTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/wallpapers/data/repository/WallpaperRepositoryImplTest.kt)0
-rw-r--r--packages/SystemUI/plugin/src/com/android/systemui/plugins/VolumeDialogController.java5
-rw-r--r--packages/SystemUI/res/drawable/volume_dialog_background.xml22
-rw-r--r--packages/SystemUI/res/drawable/volume_dialog_floating_slider_background.xml21
-rw-r--r--packages/SystemUI/res/drawable/volume_dialog_floating_sliders_spacer.xml23
-rw-r--r--packages/SystemUI/res/drawable/volume_dialog_spacer.xml23
-rw-r--r--packages/SystemUI/res/drawable/volume_row_seekbar_progress.xml2
-rw-r--r--packages/SystemUI/res/layout-land-television/volume_dialog.xml135
-rw-r--r--packages/SystemUI/res/layout-land-television/volume_dialog_legacy.xml92
-rw-r--r--packages/SystemUI/res/layout-land-television/volume_dialog_row.xml4
-rw-r--r--packages/SystemUI/res/layout-land/volume_dialog.xml181
-rw-r--r--packages/SystemUI/res/layout-land/volume_dialog_legacy.xml146
-rw-r--r--packages/SystemUI/res/layout/volume_dialog.xml154
-rw-r--r--packages/SystemUI/res/layout/volume_dialog_legacy.xml145
-rw-r--r--packages/SystemUI/res/layout/volume_dialog_slider.xml27
-rw-r--r--packages/SystemUI/res/layout/volume_dialog_slider_floating.xml24
-rw-r--r--packages/SystemUI/res/values/dimens.xml24
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java4
-rw-r--r--packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractor.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt95
-rw-r--r--packages/SystemUI/src/com/android/systemui/display/DisplayModule.kt21
-rw-r--r--packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayWindowPropertiesRepository.kt115
-rw-r--r--packages/SystemUI/src/com/android/systemui/display/shared/model/DisplayWindowProperties.kt43
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt38
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/composefragment/ui/NotificationScrimClip.kt102
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java17
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt44
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java36
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java14
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepository.kt19
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowControllerImpl.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowControllerStore.kt117
-rw-r--r--packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/BackGestureTutorialScreen.kt17
-rw-r--r--packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/HomeGestureTutorialScreen.kt17
-rw-r--r--packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/RecentAppsGestureTutorialScreen.kt23
-rw-r--r--packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/TutorialSelectionScreen.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/BackGestureMonitor.kt19
-rw-r--r--packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/DistanceTracker.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/GestureStateUpdates.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/HomeGestureMonitor.kt19
-rw-r--r--packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/RecentAppsGestureMonitor.kt54
-rw-r--r--packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureHandler.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureMonitor.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/VelocityTracker.kt51
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/domain/model/VolumeDialogStateModel.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/domain/model/VolumeDialogStreamModel.kt (renamed from packages/SystemUI/src/com/android/systemui/volume/dialog/domain/model/VolumeDialogStreamStateModel.kt)6
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/settings/ui/binder/VolumeDialogSettingsButtonViewBinder.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSliderInteractor.kt54
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSlidersInteractor.kt60
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/domain/model/VolumeDialogSliderType.kt32
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/domain/model/VolumeDialogSlidersModel.kt23
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt60
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSlidersViewBinder.kt49
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderViewModel.kt41
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSlidersViewModel.kt73
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogBinder.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt (renamed from packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt)0
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsViewTest.kt (renamed from packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsViewTest.kt)0
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardModelTest.kt1
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/communal/data/backup/CommunalBackupUtilsTest.kt (renamed from packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/backup/CommunalBackupUtilsTest.kt)0
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/communal/data/db/CommunalWidgetDaoTest.kt (renamed from packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/db/CommunalWidgetDaoTest.kt)0
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DisplayWindowPropertiesRepositoryImplTest.kt158
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyboard/data/repository/KeyboardRepositoryTest.kt (renamed from packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/data/repository/KeyboardRepositoryTest.kt)0
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt (renamed from packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt)0
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt (renamed from packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt)0
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/lifecycle/ActivatableTest.kt (renamed from packages/SystemUI/multivalentTests/src/com/android/systemui/lifecycle/ActivatableTest.kt)0
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/lifecycle/SysUiViewModelTest.kt (renamed from packages/SystemUI/multivalentTests/src/com/android/systemui/lifecycle/SysUiViewModelTest.kt)0
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaSwitchingControllerTest.java11
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/QSImplTest.java (renamed from packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QSImplTest.java)0
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/DragAndDropTest.kt (renamed from packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/DragAndDropTest.kt)0
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/ResizingTest.kt (renamed from packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/ResizingTest.kt)0
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt (renamed from packages/SystemUI/multivalentTests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt)0
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotDetectionControllerTest.kt (renamed from packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/ScreenshotDetectionControllerTest.kt)0
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerWifiTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt28
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepositoryImplTest.kt97
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/window/MultiDisplayStatusBarWindowControllerStoreTest.kt120
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/app/viewcapture/ViewCaptureAwareWindowManagerKosmos.kt26
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/DisplayScopeRepositoryKosmos.kt26
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/DisplayWindowPropertiesRepositoryKosmos.kt25
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayScopeRepository.kt30
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayWindowPropertiesRepository.kt37
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.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/statusbar/window/FakeStatusBarWindowControllerFactory.kt27
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/FakeStatusBarWindowControllerStore.kt31
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/StatusBarWindowControllerKosmos.kt12
-rw-r--r--services/appfunctions/java/com/android/server/appfunctions/AppFunctionDumpHelper.java20
-rw-r--r--services/appfunctions/java/com/android/server/appfunctions/FutureSearchResults.java6
-rw-r--r--services/appfunctions/java/com/android/server/appfunctions/FutureSearchResultsImpl.java5
-rw-r--r--services/appfunctions/java/com/android/server/appfunctions/MetadataSyncAdapter.java31
-rw-r--r--services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java100
-rw-r--r--services/core/java/com/android/server/am/ProcessList.java94
-rw-r--r--services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java17
-rw-r--r--services/core/java/com/android/server/audio/AudioService.java15
-rw-r--r--services/core/java/com/android/server/compat/PlatformCompat.java15
-rw-r--r--services/core/java/com/android/server/compat/platform_compat_flags.aconfig10
-rw-r--r--services/core/java/com/android/server/crashrecovery/CrashRecoveryUtils.java8
-rw-r--r--services/core/java/com/android/server/display/DisplayDeviceConfig.java14
-rw-r--r--services/core/java/com/android/server/display/DisplayManagerService.java14
-rw-r--r--services/core/java/com/android/server/display/ExternalDisplayPolicy.java48
-rw-r--r--services/core/java/com/android/server/display/LogicalDisplayMapper.java8
-rw-r--r--services/core/java/com/android/server/display/brightness/clamper/BrightnessPowerClamper.java20
-rw-r--r--services/core/java/com/android/server/display/feature/DisplayManagerFlags.java14
-rw-r--r--services/core/java/com/android/server/display/feature/display_flags.aconfig11
-rw-r--r--services/core/java/com/android/server/input/InputSettingsObserver.java16
-rw-r--r--services/core/java/com/android/server/input/NativeInputManagerService.java10
-rw-r--r--services/core/java/com/android/server/inputmethod/InputMethodManagerService.java17
-rw-r--r--services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverySnapshotStorage.java72
-rw-r--r--services/core/java/com/android/server/pm/ComputerEngine.java23
-rw-r--r--services/core/java/com/android/server/pm/InstallRequest.java5
-rw-r--r--services/core/java/com/android/server/pm/UserManagerService.java34
-rw-r--r--services/core/java/com/android/server/wm/ContentRecorder.java7
-rw-r--r--services/core/jni/com_android_server_input_InputManagerService.cpp57
-rw-r--r--services/core/jni/com_android_server_utils_AnrTimer.cpp3
-rw-r--r--services/core/xsd/display-device-config/display-device-config.xsd2
-rw-r--r--services/core/xsd/display-device-config/schema/current.txt4
-rw-r--r--services/tests/appfunctions/src/com/android/server/appfunctions/MetadataSyncAdapterTest.kt4
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java4
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java93
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/ExternalDisplayPolicyTest.java59
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java10
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/PersistentDataStoreTest.java1
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUidStateTrackerTest.java72
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java65
-rw-r--r--services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java124
-rw-r--r--services/tests/servicestests/src/com/android/server/compat/PlatformCompatTest.java84
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java22
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DisplayRotationImmersiveAppCompatPolicyTests.java2
-rw-r--r--telephony/java/android/telephony/satellite/SatelliteManager.java11
-rw-r--r--telephony/java/com/android/internal/telephony/ITelephony.aidl6
-rw-r--r--tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/LetterboxAppHelper.kt4
-rw-r--r--tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml14
-rw-r--r--tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java6
-rw-r--r--tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/NonResizeableFixedAspectRatioPortraitActivity.java28
263 files changed, 5624 insertions, 1121 deletions
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index fbe4905d9754..c6ce799f0a24 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -24,6 +24,7 @@ aconfig_declarations_group {
"android-sdk-flags-java",
"android.adaptiveauth.flags-aconfig-java",
"android.app.appfunctions.flags-aconfig-java",
+ "android.app.assist.flags-aconfig-java",
"android.app.contextualsearch.flags-aconfig-java",
"android.app.flags-aconfig-java",
"android.app.jank.flags-aconfig-java",
@@ -64,6 +65,7 @@ aconfig_declarations_group {
"android.server.app.flags-aconfig-java",
"android.service.autofill.flags-aconfig-java",
"android.service.chooser.flags-aconfig-java",
+ "android.service.compat.flags-aconfig-java",
"android.service.controls.flags-aconfig-java",
"android.service.dreams.flags-aconfig-java",
"android.service.notification.flags-aconfig-java",
@@ -862,6 +864,21 @@ java_aconfig_library {
defaults: ["framework-minus-apex-aconfig-java-defaults"],
}
+aconfig_declarations {
+ name: "android.service.compat.flags-aconfig",
+ package: "com.android.server.compat",
+ container: "system",
+ srcs: [
+ "services/core/java/com/android/server/compat/*.aconfig",
+ ],
+}
+
+java_aconfig_library {
+ name: "android.service.compat.flags-aconfig-java",
+ aconfig_declarations: "android.service.compat.flags-aconfig",
+ defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
// Multi user
aconfig_declarations {
name: "android.multiuser.flags-aconfig",
@@ -1230,6 +1247,20 @@ java_aconfig_library {
defaults: ["framework-minus-apex-aconfig-java-defaults"],
}
+// Assist
+aconfig_declarations {
+ name: "android.app.assist.flags-aconfig",
+ package: "android.app.assist.flags",
+ container: "system",
+ srcs: ["core/java/android/app/assist/flags.aconfig"],
+}
+
+java_aconfig_library {
+ name: "android.app.assist.flags-aconfig-java",
+ aconfig_declarations: "android.app.assist.flags-aconfig",
+ defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
// Smartspace
aconfig_declarations {
name: "android.app.smartspace.flags-aconfig",
diff --git a/apex/jobscheduler/service/aconfig/job.aconfig b/apex/jobscheduler/service/aconfig/job.aconfig
index e5389b4f96fb..11c5b51e23ae 100644
--- a/apex/jobscheduler/service/aconfig/job.aconfig
+++ b/apex/jobscheduler/service/aconfig/job.aconfig
@@ -75,3 +75,10 @@ flag {
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "enforce_quota_policy_to_fgs_jobs"
+ namespace: "backstage_power"
+ description: "Applies the normal quota policy to FGS jobs"
+ bug: "341201311"
+}
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
index a1c72fb4c06c..03a3a0d51891 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
@@ -99,10 +99,10 @@ import java.util.function.Predicate;
* the number of jobs or sessions that can run within the window. Regardless of bucket, apps will
* not be allowed to run more than 20 jobs within the past 10 minutes.
*
- * Jobs are throttled while an app is not in a foreground state. All jobs are allowed to run
- * freely when an app enters the foreground state and are restricted when the app leaves the
- * foreground state. However, jobs that are started while the app is in the TOP state do not count
- * towards any quota and are not restricted regardless of the app's state change.
+ * Jobs are throttled while an app is not in a TOP or BOUND_TOP state. All jobs are allowed to run
+ * freely when an app enters the TOP or BOUND_TOP state and are restricted when the app leaves those
+ * states. However, jobs that are started while the app is in the TOP state do not count towards any
+ * quota and are not restricted regardless of the app's state change.
*
* Jobs will not be throttled when the device is charging. The device is considered to be charging
* once the {@link BatteryManager#ACTION_CHARGING} intent has been broadcast.
@@ -567,6 +567,11 @@ public final class QuotaController extends StateController {
ActivityManager.getService().registerUidObserver(new QcUidObserver(),
ActivityManager.UID_OBSERVER_PROCSTATE,
ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, null);
+ if (Flags.enforceQuotaPolicyToFgsJobs()) {
+ ActivityManager.getService().registerUidObserver(new QcUidObserver(),
+ ActivityManager.UID_OBSERVER_PROCSTATE,
+ ActivityManager.PROCESS_STATE_BOUND_TOP, null);
+ }
ActivityManager.getService().registerUidObserver(new QcUidObserver(),
ActivityManager.UID_OBSERVER_PROCSTATE,
ActivityManager.PROCESS_STATE_TOP, null);
@@ -2706,6 +2711,12 @@ public final class QuotaController extends StateController {
}
}
+ @VisibleForTesting
+ int getProcessStateQuotaFreeThreshold() {
+ return Flags.enforceQuotaPolicyToFgsJobs() ? ActivityManager.PROCESS_STATE_BOUND_TOP :
+ ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE;
+ }
+
private class QcHandler extends Handler {
QcHandler(Looper looper) {
@@ -2832,15 +2843,15 @@ public final class QuotaController extends StateController {
mTopAppCache.put(uid, true);
mTopAppGraceCache.delete(uid);
if (mForegroundUids.get(uid)) {
- // Went from FGS to TOP. We don't need to reprocess timers or
- // jobs.
+ // Went from a process state with quota free to TOP. We don't
+ // need to reprocess timers or jobs.
break;
}
mForegroundUids.put(uid, true);
isQuotaFree = true;
} else {
final boolean reprocess;
- if (procState <= ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE) {
+ if (procState <= getProcessStateQuotaFreeThreshold()) {
reprocess = !mForegroundUids.get(uid);
mForegroundUids.put(uid, true);
isQuotaFree = true;
diff --git a/core/java/android/app/assist/AssistStructure.java b/core/java/android/app/assist/AssistStructure.java
index 508077ed43cc..1af2437a5d6a 100644
--- a/core/java/android/app/assist/AssistStructure.java
+++ b/core/java/android/app/assist/AssistStructure.java
@@ -1,5 +1,6 @@
package android.app.assist;
+import static android.app.assist.flags.Flags.addPlaceholderViewForNullChild;
import static android.credentials.Constants.FAILURE_CREDMAN_SELECTOR;
import static android.credentials.Constants.SUCCESS_CREDMAN_SELECTOR;
import static android.service.autofill.Flags.FLAG_AUTOFILL_CREDMAN_DEV_INTEGRATION;
@@ -284,12 +285,18 @@ public class AssistStructure implements Parcelable {
mCurViewStackEntry = entry;
}
- void writeView(ViewNode child, Parcel out, PooledStringWriter pwriter, int levelAdj) {
+ void writeView(@Nullable ViewNode child, Parcel out, PooledStringWriter pwriter,
+ int levelAdj) {
if (DEBUG_PARCEL) Log.d(TAG, "write view: at " + out.dataPosition()
+ ", windows=" + mNumWrittenWindows
+ ", views=" + mNumWrittenViews
+ ", level=" + (mCurViewStackPos+levelAdj));
out.writeInt(VALIDATE_VIEW_TOKEN);
+ if (addPlaceholderViewForNullChild() && child == null) {
+ if (DEBUG_PARCEL_TREE) Log.d(TAG, "Detected an empty child"
+ + "; writing a placeholder for the child.");
+ child = new ViewNode();
+ }
int flags = child.writeSelfToParcel(out, pwriter, mSanitizeOnWrite,
mTmpMatrix, /*willWriteChildren=*/true);
mNumWrittenViews++;
@@ -2545,7 +2552,7 @@ public class AssistStructure implements Parcelable {
ensureData();
}
Log.i(TAG, "Task id: " + mTaskId);
- Log.i(TAG, "Activity: " + (mActivityComponent != null
+ Log.i(TAG, "Activity: " + (mActivityComponent != null
? mActivityComponent.flattenToShortString()
: null));
Log.i(TAG, "Sanitize on write: " + mSanitizeOnWrite);
diff --git a/core/java/android/app/assist/flags.aconfig b/core/java/android/app/assist/flags.aconfig
new file mode 100644
index 000000000000..bf0aeacbc7f3
--- /dev/null
+++ b/core/java/android/app/assist/flags.aconfig
@@ -0,0 +1,13 @@
+package: "android.app.assist.flags"
+container: "system"
+
+flag {
+ name: "add_placeholder_view_for_null_child"
+ namespace: "machine_learning"
+ description: "Flag to add a placeholder view when a child view is null."
+ bug: "369503426"
+ is_fixed_read_only: true
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/core/java/android/companion/virtual/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java
index 96700a9a3c18..70211bfd77b1 100644
--- a/core/java/android/companion/virtual/VirtualDeviceManager.java
+++ b/core/java/android/companion/virtual/VirtualDeviceManager.java
@@ -1135,8 +1135,11 @@ public final class VirtualDeviceManager {
/**
* Sets the visibility of the pointer icon for this VirtualDevice's associated displays.
*
+ * <p>Only applicable to trusted displays.</p>
+ *
* @param showPointerIcon True if the pointer should be shown; false otherwise. The default
* visibility is true.
+ * @see DisplayManager#VIRTUAL_DISPLAY_FLAG_TRUSTED
*/
@RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
public void setShowPointerIcon(boolean showPointerIcon) {
diff --git a/core/java/android/companion/virtual/VirtualDeviceParams.java b/core/java/android/companion/virtual/VirtualDeviceParams.java
index 03b72bdb8823..65f9cbefb052 100644
--- a/core/java/android/companion/virtual/VirtualDeviceParams.java
+++ b/core/java/android/companion/virtual/VirtualDeviceParams.java
@@ -159,7 +159,7 @@ public final class VirtualDeviceParams implements Parcelable {
* @hide
*/
@IntDef(prefix = "POLICY_TYPE_", value = {POLICY_TYPE_SENSORS, POLICY_TYPE_AUDIO,
- POLICY_TYPE_RECENTS, POLICY_TYPE_ACTIVITY, POLICY_TYPE_CAMERA,
+ POLICY_TYPE_RECENTS, POLICY_TYPE_ACTIVITY, POLICY_TYPE_CLIPBOARD, POLICY_TYPE_CAMERA,
POLICY_TYPE_BLOCKED_ACTIVITY})
@Retention(RetentionPolicy.SOURCE)
@Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
@@ -220,11 +220,16 @@ public final class VirtualDeviceParams implements Parcelable {
* Tells the activity manager how to handle recents entries for activities run on this device.
*
* <ul>
- * <li>{@link #DEVICE_POLICY_DEFAULT}: Activities launched on VirtualDisplays owned by this
+ * <li>{@link #DEVICE_POLICY_DEFAULT}: Activities launched on trusted displays owned by this
* device will appear in the host device recents.
- * <li>{@link #DEVICE_POLICY_CUSTOM}: Activities launched on VirtualDisplays owned by this
+ * <li>{@link #DEVICE_POLICY_CUSTOM}: Activities launched on trusted displays owned by this
* device will not appear in recents.
* </ul>
+ *
+ * <p>Activities launched on untrusted displays will always show in the host device recents,
+ * regardless of the policy.</p>
+ *
+ * @see android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_TRUSTED
*/
public static final int POLICY_TYPE_RECENTS = 2;
@@ -254,8 +259,10 @@ public final class VirtualDeviceParams implements Parcelable {
* not shared with other devices' clipboards, including the clipboard of the default device.
* <li>{@link #DEVICE_POLICY_CUSTOM}: The device's clipboard is shared with the default
* device's clipboard. Any clipboard operation on the virtual device is as if it was done on
- * the default device.
+ * the default device. Requires all displays of the virtual device to be trusted.
* </ul>
+ *
+ * @see android.hardware.display.DisplayManager#VIRTUAL_DISPLAY_FLAG_TRUSTED
*/
@FlaggedApi(Flags.FLAG_CROSS_DEVICE_CLIPBOARD)
public static final int POLICY_TYPE_CLIPBOARD = 4;
@@ -821,8 +828,8 @@ public final class VirtualDeviceParams implements Parcelable {
}
/**
- * Specifies a component to be used as input method on all displays owned by this virtual
- * device.
+ * Specifies a component to be used as input method on all trusted displays owned by this
+ * virtual device.
*
* @param inputMethodComponent The component name to be used as input method. Must comply to
* all general input method requirements described in the guide to
@@ -831,6 +838,7 @@ public final class VirtualDeviceParams implements Parcelable {
* may interact with the virtual device, then there will effectively be no IME on this
* device's displays for that user.
*
+ * @see android.hardware.display.DisplayManager#VIRTUAL_DISPLAY_FLAG_TRUSTED
* @see android.inputmethodservice.InputMethodService
* @attr ref android.R.styleable#InputMethod_isVirtualDeviceOnly
* @attr ref android.R.styleable#InputMethod_showInInputMethodPicker
diff --git a/core/java/android/content/pm/multiuser.aconfig b/core/java/android/content/pm/multiuser.aconfig
index dcf82bf35828..ff0a3ddc746e 100644
--- a/core/java/android/content/pm/multiuser.aconfig
+++ b/core/java/android/content/pm/multiuser.aconfig
@@ -47,13 +47,6 @@ flag {
}
flag {
- name: "start_user_before_scheduled_alarms"
- namespace: "multiuser"
- description: "Persist list of users with alarms scheduled and wakeup stopped users before alarms are due"
- bug: "314907186"
-}
-
-flag {
name: "add_ui_for_sounds_from_background_users"
namespace: "multiuser"
description: "Allow foreground user to dismiss sounds that are coming from background users"
diff --git a/core/java/android/hardware/display/DisplayManagerInternal.java b/core/java/android/hardware/display/DisplayManagerInternal.java
index 6c1aa90c831b..75ffcc3a8863 100644
--- a/core/java/android/hardware/display/DisplayManagerInternal.java
+++ b/core/java/android/hardware/display/DisplayManagerInternal.java
@@ -461,6 +461,16 @@ public abstract class DisplayManagerInternal {
public abstract void stylusGestureStarted(long eventTime);
/**
+ * Called by {@link com.android.server.wm.ContentRecorder} to verify whether
+ * the display is allowed to mirror primary display's content.
+ * @param displayId the id of the display where we mirror to.
+ * @return true if the mirroring dialog is confirmed (display is enabled), or
+ * {@link com.android.server.display.ExternalDisplayPolicy#ENABLE_ON_CONNECT}
+ * system property is enabled.
+ */
+ public abstract boolean isDisplayReadyForMirroring(int displayId);
+
+ /**
* Describes the requested power state of the display.
*
* This object is intended to describe the general characteristics of the
diff --git a/core/java/android/hardware/input/InputSettings.java b/core/java/android/hardware/input/InputSettings.java
index 177ee6f1540a..897ce4a7b022 100644
--- a/core/java/android/hardware/input/InputSettings.java
+++ b/core/java/android/hardware/input/InputSettings.java
@@ -24,6 +24,8 @@ import static com.android.hardware.input.Flags.keyboardA11yBounceKeysFlag;
import static com.android.hardware.input.Flags.keyboardA11ySlowKeysFlag;
import static com.android.hardware.input.Flags.keyboardA11yStickyKeysFlag;
import static com.android.hardware.input.Flags.keyboardA11yMouseKeys;
+import static com.android.hardware.input.Flags.mouseReverseVerticalScrolling;
+import static com.android.hardware.input.Flags.mouseSwapPrimaryButton;
import static com.android.hardware.input.Flags.touchpadTapDragging;
import static com.android.hardware.input.Flags.touchpadVisualizer;
import static com.android.input.flags.Flags.enableInputFilterRustImpl;
@@ -363,6 +365,22 @@ public class InputSettings {
}
/**
+ * Returns true if the feature flag for mouse reverse vertical scrolling is enabled.
+ * @hide
+ */
+ public static boolean isMouseReverseVerticalScrollingFeatureFlagEnabled() {
+ return mouseReverseVerticalScrolling();
+ }
+
+ /**
+ * Returns true if the feature flag for mouse swap primary button is enabled.
+ * @hide
+ */
+ public static boolean isMouseSwapPrimaryButtonFeatureFlagEnabled() {
+ return mouseSwapPrimaryButton();
+ }
+
+ /**
* Returns true if the touchpad visualizer is allowed to appear.
*
* @param context The application context.
@@ -501,6 +519,86 @@ public class InputSettings {
}
/**
+ * Whether mouse vertical scrolling is enabled, this applies only to connected mice.
+ *
+ * @param context The application context.
+ * @return Whether the mouse will have its vertical scrolling reversed
+ * (scroll down to move up).
+ *
+ * @hide
+ */
+ public static boolean isMouseReverseVerticalScrollingEnabled(@NonNull Context context) {
+ if (!isMouseReverseVerticalScrollingFeatureFlagEnabled()) {
+ return false;
+ }
+
+ return Settings.System.getIntForUser(context.getContentResolver(),
+ Settings.System.MOUSE_REVERSE_VERTICAL_SCROLLING, 0, UserHandle.USER_CURRENT)
+ != 0;
+ }
+
+ /**
+ * Sets whether the connected mouse will have its vertical scrolling reversed.
+ *
+ * @param context The application context.
+ * @param reverseScrolling Whether reverse scrolling is enabled.
+ *
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.WRITE_SETTINGS)
+ public static void setMouseReverseVerticalScrolling(@NonNull Context context,
+ boolean reverseScrolling) {
+ if (!isMouseReverseVerticalScrollingFeatureFlagEnabled()) {
+ return;
+ }
+
+ Settings.System.putIntForUser(context.getContentResolver(),
+ Settings.System.MOUSE_REVERSE_VERTICAL_SCROLLING, reverseScrolling ? 1 : 0,
+ UserHandle.USER_CURRENT);
+ }
+
+ /**
+ * Whether the primary mouse button is swapped on connected mice.
+ *
+ * @param context The application context.
+ * @return Whether mice will have their primary buttons swapped, so that left clicking will
+ * perform the secondary action (e.g. show menu) and right clicking will perform the primary
+ * action.
+ *
+ * @hide
+ */
+ public static boolean isMouseSwapPrimaryButtonEnabled(@NonNull Context context) {
+ if (!isMouseSwapPrimaryButtonFeatureFlagEnabled()) {
+ return false;
+ }
+
+ return Settings.System.getIntForUser(context.getContentResolver(),
+ Settings.System.MOUSE_SWAP_PRIMARY_BUTTON, 0, UserHandle.USER_CURRENT)
+ != 0;
+ }
+
+ /**
+ * Sets whether mice will have their primary buttons swapped between left and right
+ * clicks.
+ *
+ * @param context The application context.
+ * @param swapPrimaryButton Whether swapping the primary button is enabled.
+ *
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.WRITE_SETTINGS)
+ public static void setMouseSwapPrimaryButton(@NonNull Context context,
+ boolean swapPrimaryButton) {
+ if (!isMouseSwapPrimaryButtonFeatureFlagEnabled()) {
+ return;
+ }
+
+ Settings.System.putIntForUser(context.getContentResolver(),
+ Settings.System.MOUSE_SWAP_PRIMARY_BUTTON, swapPrimaryButton ? 1 : 0,
+ UserHandle.USER_CURRENT);
+ }
+
+ /**
* Whether Accessibility bounce keys feature is enabled.
*
* <p>
diff --git a/core/java/android/hardware/input/VirtualInputDeviceConfig.java b/core/java/android/hardware/input/VirtualInputDeviceConfig.java
index e8ef8cd11585..3b74d7f5253d 100644
--- a/core/java/android/hardware/input/VirtualInputDeviceConfig.java
+++ b/core/java/android/hardware/input/VirtualInputDeviceConfig.java
@@ -163,7 +163,6 @@ public abstract class VirtualInputDeviceConfig {
return self();
}
-
/**
* Sets the product id of the device, uniquely identifying the device within the address
* space of a given vendor, identified by the device's vendor id.
@@ -179,6 +178,10 @@ public abstract class VirtualInputDeviceConfig {
*
* <p>The input device is restricted to the display with the given ID and may not send
* events to any other display.</p>
+ * <p>The corresponding display must be trusted or mirror display.</p>
+ *
+ * @see android.hardware.display.DisplayManager#VIRTUAL_DISPLAY_FLAG_TRUSTED
+ * @see android.hardware.display.DisplayManager#VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR
*/
@NonNull
public T setAssociatedDisplayId(int displayId) {
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 3ae951170759..f1964e7bc024 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -6368,8 +6368,12 @@ public class UserManager {
Settings.Global.DEVICE_DEMO_MODE, 0) > 0;
}
- /** @hide */
- public static final void invalidateUserSerialNumberCache() {
+
+ /**
+ * This method is used to invalidate caches, when user was added or removed.
+ * @hide
+ */
+ public static final void invalidateCacheOnUserListChange() {
UserManagerCache.invalidateUserSerialNumber();
}
@@ -6382,7 +6386,7 @@ public class UserManager {
* @hide
*/
@UnsupportedAppUsage
- @CachedProperty(modsFlagOnOrNone = {})
+ @CachedProperty(modsFlagOnOrNone = {}, api = "user_manager_users")
public int getUserSerialNumber(@UserIdInt int userId) {
// Read only flag should is to fix early access to this API
// cacheUserSerialNumber to be removed after the
diff --git a/core/java/android/os/flags.aconfig b/core/java/android/os/flags.aconfig
index a1bfe39c0fc4..81987907452f 100644
--- a/core/java/android/os/flags.aconfig
+++ b/core/java/android/os/flags.aconfig
@@ -232,3 +232,19 @@ flag {
bug: "361329788"
is_exported: true
}
+
+flag {
+ name: "enable_angle_allow_list"
+ namespace: "gpu"
+ description: "Whether to read from angle allowlist to determine if app should use ANGLE"
+ is_fixed_read_only: true
+ bug: "370845648"
+}
+
+flag {
+ name: "api_for_backported_fixes"
+ namespace: "media_reliability"
+ description: "Public API app developers use to check if a known issue is fixed on a device."
+ bug: "308461809"
+ is_exported: true
+}
diff --git a/core/java/android/permission/flags.aconfig b/core/java/android/permission/flags.aconfig
index feeb339c1200..1d8fcec8cf31 100644
--- a/core/java/android/permission/flags.aconfig
+++ b/core/java/android/permission/flags.aconfig
@@ -255,3 +255,14 @@ flag {
description: "This fixed read-only flag is used to enable replacing permission BODY_SENSORS (and BODY_SENSORS_BACKGROUND) with granular health permission READ_HEART_RATE (and READ_HEALTH_DATA_IN_BACKGROUND)"
bug: "364638912"
}
+
+flag {
+ name: "delay_uid_state_changes_from_capability_updates"
+ is_fixed_read_only: true
+ namespace: "permissions"
+ description: "If proc state is decreasing over the restriction threshold and capability is changed, delay if no new capabilities are added"
+ bug: "347891382"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 1a15d09c0a50..594005c3ebd6 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -2351,6 +2351,11 @@ public final class Settings {
/**
* Activity Action: Show the permission screen for allowing apps to post promoted notifications.
+ * Properly formatted priority notifications are elevated in appearance. For example they may be
+ * able to use colors, have richer progress bars, show as chips in the status bar, and/or
+ * permanently appear on always-on-displays. This functionality is intended to be reserved for
+ * user initiated ongoing activities like navigation, phone calls, and ride sharing.
+ *
* <p>
* Input: {@link #EXTRA_APP_PACKAGE}, the package to display.
* <p>
@@ -6205,6 +6210,25 @@ public final class Settings {
public static final String TOUCHPAD_RIGHT_CLICK_ZONE = "touchpad_right_click_zone";
/**
+ * Whether to enable reversed vertical scrolling for connected mice.
+ *
+ * When enabled, scrolling down on the mouse wheel will move the screen up and vice versa.
+ * @hide
+ */
+ public static final String MOUSE_REVERSE_VERTICAL_SCROLLING =
+ "mouse_reverse_vertical_scrolling";
+
+ /**
+ * Whether to enable swapping the primary button for connected mice.
+ *
+ * When enabled, right clicking will be the primary button and left clicking will be the
+ * secondary button (e.g. show menu).
+ * @hide
+ */
+ public static final String MOUSE_SWAP_PRIMARY_BUTTON =
+ "mouse_swap_primary_button";
+
+ /**
* Pointer fill style, specified by
* {@link android.view.PointerIcon.PointerIconVectorStyleFill} constants.
*
@@ -6442,6 +6466,8 @@ public final class Settings {
PRIVATE_SETTINGS.add(SCREEN_FLASH_NOTIFICATION);
PRIVATE_SETTINGS.add(SCREEN_FLASH_NOTIFICATION_COLOR);
PRIVATE_SETTINGS.add(DEFAULT_DEVICE_FONT_SCALE);
+ PRIVATE_SETTINGS.add(MOUSE_REVERSE_VERTICAL_SCROLLING);
+ PRIVATE_SETTINGS.add(MOUSE_SWAP_PRIMARY_BUTTON);
}
/**
diff --git a/core/java/android/window/DesktopModeFlags.java b/core/java/android/window/DesktopModeFlags.java
index 6fb82af2b292..8e35843e2193 100644
--- a/core/java/android/window/DesktopModeFlags.java
+++ b/core/java/android/window/DesktopModeFlags.java
@@ -65,7 +65,9 @@ public enum DesktopModeFlags {
ENABLE_DESKTOP_WINDOWING_TASKBAR_RUNNING_APPS(
Flags::enableDesktopWindowingTaskbarRunningApps, true),
ENABLE_DESKTOP_WINDOWING_TRANSITIONS(Flags::enableDesktopWindowingTransitions, false),
- ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS(Flags::enableDesktopWindowingExitTransitions, false);
+ ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS(Flags::enableDesktopWindowingExitTransitions, false),
+ ENABLE_WINDOWING_TRANSITION_HANDLERS_OBSERVERS(
+ Flags::enableWindowingTransitionHandlersObservers, false);
private static final String TAG = "DesktopModeFlagsUtil";
// Function called to obtain aconfig flag value.
diff --git a/core/java/android/window/flags/lse_desktop_experience.aconfig b/core/java/android/window/flags/lse_desktop_experience.aconfig
index 31bb3a610376..155494fb3b25 100644
--- a/core/java/android/window/flags/lse_desktop_experience.aconfig
+++ b/core/java/android/window/flags/lse_desktop_experience.aconfig
@@ -228,7 +228,7 @@ flag {
name: "enable_desktop_windowing_app_handle_education"
namespace: "lse_desktop_experience"
description: "Enables desktop windowing app handle education"
- bug: "348208342"
+ bug: "316006079"
}
flag {
diff --git a/core/jni/android_view_WindowManagerGlobal.cpp b/core/jni/android_view_WindowManagerGlobal.cpp
index abc621d8dc90..4202de39adb0 100644
--- a/core/jni/android_view_WindowManagerGlobal.cpp
+++ b/core/jni/android_view_WindowManagerGlobal.cpp
@@ -69,8 +69,8 @@ void removeInputChannel(const sp<IBinder>& clientToken) {
JNIEnv* env = AndroidRuntime::getJNIEnv();
ScopedLocalRef<jobject> clientTokenObj(env, javaObjectForIBinder(env, clientToken));
- env->CallStaticObjectMethod(gWindowManagerGlobal.clazz, gWindowManagerGlobal.removeInputChannel,
- clientTokenObj.get());
+ env->CallStaticVoidMethod(gWindowManagerGlobal.clazz, gWindowManagerGlobal.removeInputChannel,
+ clientTokenObj.get());
}
int register_android_view_WindowManagerGlobal(JNIEnv* env) {
@@ -88,4 +88,4 @@ int register_android_view_WindowManagerGlobal(JNIEnv* env) {
return NO_ERROR;
}
-} // namespace android \ No newline at end of file
+} // namespace android
diff --git a/core/jni/platform/host/HostRuntime.cpp b/core/jni/platform/host/HostRuntime.cpp
index 88b3e1c1ed9d..27417c0c9929 100644
--- a/core/jni/platform/host/HostRuntime.cpp
+++ b/core/jni/platform/host/HostRuntime.cpp
@@ -115,9 +115,9 @@ static const std::unordered_map<std::string, RegJNIRec> gRegJNIMap = {
#ifdef __linux__
{"android.content.res.ApkAssets", REG_JNI(register_android_content_res_ApkAssets)},
{"android.content.res.AssetManager", REG_JNI(register_android_content_AssetManager)},
+#endif
{"android.content.res.StringBlock", REG_JNI(register_android_content_StringBlock)},
{"android.content.res.XmlBlock", REG_JNI(register_android_content_XmlBlock)},
-#endif
{"android.database.CursorWindow", REG_JNI(register_android_database_CursorWindow)},
{"android.database.sqlite.SQLiteConnection",
REG_JNI(register_android_database_SQLiteConnection)},
diff --git a/core/proto/android/providers/settings/system.proto b/core/proto/android/providers/settings/system.proto
index e795e8096641..9779dc0e00b8 100644
--- a/core/proto/android/providers/settings/system.proto
+++ b/core/proto/android/providers/settings/system.proto
@@ -220,6 +220,15 @@ message SystemSettingsProto {
}
optional Touchpad touchpad = 36;
+ message Mouse {
+ option (android.msg_privacy).dest = DEST_EXPLICIT;
+
+ optional SettingProto reverse_vertical_scrolling = 1 [ (android.privacy).dest = DEST_AUTOMATIC ];
+ optional SettingProto swap_primary_button = 2 [ (android.privacy).dest = DEST_AUTOMATIC ];
+ }
+
+ optional Mouse mouse = 38;
+
optional SettingProto tty_mode = 31 [ (android.privacy).dest = DEST_AUTOMATIC ];
message Vibrate {
@@ -277,5 +286,5 @@ message SystemSettingsProto {
// Please insert fields in alphabetical order and group them into messages
// if possible (to avoid reaching the method limit).
- // Next tag = 38;
+ // Next tag = 39;
}
diff --git a/core/tests/coretests/src/android/app/ActivityManagerTest.java b/core/tests/coretests/src/android/app/ActivityManagerTest.java
index d850f86070bc..85ff8463725e 100644
--- a/core/tests/coretests/src/android/app/ActivityManagerTest.java
+++ b/core/tests/coretests/src/android/app/ActivityManagerTest.java
@@ -60,7 +60,6 @@ public class ActivityManagerTest {
public void testProcState() throws Exception {
// For the moment mostly want to confirm we don't crash
assertNotNull(ActivityManager.procStateToString(PROCESS_STATE_SERVICE));
- assertNotNull(ActivityManager.processStateAmToProto(PROCESS_STATE_SERVICE));
assertTrue(ActivityManager.isProcStateBackground(PROCESS_STATE_SERVICE));
assertFalse(ActivityManager.isProcStateCached(PROCESS_STATE_SERVICE));
assertFalse(ActivityManager.isForegroundService(PROCESS_STATE_SERVICE));
diff --git a/core/tests/coretests/src/android/app/activity/ActivityManagerTest.java b/core/tests/coretests/src/android/app/activity/ActivityManagerTest.java
index b972882e68e6..cd524214e6af 100644
--- a/core/tests/coretests/src/android/app/activity/ActivityManagerTest.java
+++ b/core/tests/coretests/src/android/app/activity/ActivityManagerTest.java
@@ -111,12 +111,6 @@ public class ActivityManagerTest extends AndroidTestCase {
assertEquals(config.reqKeyboardType, vconfig.keyboard);
assertEquals(config.reqTouchScreen, vconfig.touchscreen);
assertEquals(config.reqNavigation, vconfig.navigation);
- if (vconfig.navigation == Configuration.NAVIGATION_NONAV) {
- assertNotNull(config.reqInputFeatures & ConfigurationInfo.INPUT_FEATURE_FIVE_WAY_NAV);
- }
- if (vconfig.keyboard != Configuration.KEYBOARD_UNDEFINED) {
- assertNotNull(config.reqInputFeatures & ConfigurationInfo.INPUT_FEATURE_HARD_KEYBOARD);
- }
}
@SmallTest
diff --git a/core/tests/coretests/src/android/app/assist/AssistStructureTest.java b/core/tests/coretests/src/android/app/assist/AssistStructureTest.java
index a28b2f66aaa8..51e79e7ac4e1 100644
--- a/core/tests/coretests/src/android/app/assist/AssistStructureTest.java
+++ b/core/tests/coretests/src/android/app/assist/AssistStructureTest.java
@@ -24,6 +24,7 @@ import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
import android.app.assist.AssistStructure.ViewNode;
import android.app.assist.AssistStructure.ViewNodeBuilder;
@@ -37,6 +38,7 @@ import android.os.Bundle;
import android.os.LocaleList;
import android.os.OutcomeReceiver;
import android.os.Parcel;
+import android.os.PooledStringWriter;
import android.os.SystemClock;
import android.text.InputFilter;
import android.util.Log;
@@ -355,6 +357,18 @@ public class AssistStructureTest {
}
+ @Test
+ public void testParcelTransferWriter_writeNull() {
+ AssistStructure structure = new AssistStructure(mActivity, FOR_AUTOFILL, NO_FLAGS);
+ Parcel parcel = Parcel.obtain();
+ AssistStructure.ParcelTransferWriter writer =
+ new AssistStructure.ParcelTransferWriter(structure, parcel);
+ writer.writeView(null, parcel, new PooledStringWriter(parcel), 0);
+
+ // No throw any exception.
+ assertTrue(true);
+ }
+
private EditText newSmallView() {
EditText view = new EditText(mContext);
view.setText("I AM GROOT");
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt
index 5a277316ffd4..379e052e7b38 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt
@@ -156,6 +156,21 @@ class DesktopModeEventLogger {
)
}
+ fun logTaskInfoStateInit() {
+ logTaskUpdate(
+ FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_INIT_STATSD,
+ /* session_id */ 0,
+ TaskUpdate(
+ visibleTaskCount = 0,
+ instanceId = 0,
+ uid = 0,
+ taskHeight = 0,
+ taskWidth = 0,
+ taskX = 0,
+ taskY = 0)
+ )
+ }
+
private fun logTaskUpdate(taskEvent: Int, sessionId: Int, taskUpdate: TaskUpdate) {
FrameworkStatsLog.write(
DESKTOP_MODE_TASK_UPDATE_ATOM_ID,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt
index b8507e3b2764..f847aa8918c2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt
@@ -102,6 +102,7 @@ class DesktopModeLoggerTransitionObserver(
SystemProperties.set(
VISIBLE_TASKS_COUNTER_SYSTEM_PROPERTY,
VISIBLE_TASKS_COUNTER_SYSTEM_PROPERTY_DEFAULT_VALUE)
+ desktopModeEventLogger.logTaskInfoStateInit()
}
override fun onTransitionReady(
diff --git a/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/OpenTransparentActivityTest.kt b/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/OpenTransparentActivityTest.kt
index 2980d5113bba..e176f47d4094 100644
--- a/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/OpenTransparentActivityTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/OpenTransparentActivityTest.kt
@@ -17,7 +17,6 @@
package com.android.wm.shell.flicker.appcompat
import android.platform.test.annotations.Postsubmit
-import android.tools.Rotation
import android.tools.flicker.assertions.FlickerTest
import android.tools.flicker.junit.FlickerParametersRunnerFactory
import android.tools.flicker.legacy.FlickerBuilder
@@ -109,9 +108,7 @@ class OpenTransparentActivityTest(flicker: LegacyFlickerTest) : TransparentBaseA
@Parameterized.Parameters(name = "{0}")
@JvmStatic
fun getParams(): Collection<FlickerTest> {
- return LegacyFlickerTestFactory.nonRotationTests(
- supportedRotations = listOf(Rotation.ROTATION_90)
- )
+ return LegacyFlickerTestFactory.nonRotationTests()
}
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/QuickSwitchLauncherToLetterboxAppTest.kt b/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/QuickSwitchLauncherToLetterboxAppTest.kt
index 2484f675b236..9b8c949a1705 100644
--- a/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/QuickSwitchLauncherToLetterboxAppTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/appcompat/src/com/android/wm/shell/flicker/appcompat/QuickSwitchLauncherToLetterboxAppTest.kt
@@ -20,7 +20,6 @@ import android.graphics.Rect
import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.RequiresDevice
import android.tools.NavBar
-import android.tools.Rotation
import android.tools.flicker.assertions.FlickerTest
import android.tools.flicker.junit.FlickerParametersRunnerFactory
import android.tools.flicker.legacy.FlickerBuilder
@@ -266,8 +265,7 @@ class QuickSwitchLauncherToLetterboxAppTest(flicker: LegacyFlickerTest) : BaseAp
@JvmStatic
fun getParams(): Collection<FlickerTest> {
return LegacyFlickerTestFactory.nonRotationTests(
- supportedNavigationModes = listOf(NavBar.MODE_GESTURAL),
- supportedRotations = listOf(Rotation.ROTATION_90)
+ supportedNavigationModes = listOf(NavBar.MODE_GESTURAL)
)
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeEventLoggerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeEventLoggerTest.kt
index d7a132dfa1be..dde9fda13ea9 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeEventLoggerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeEventLoggerTest.kt
@@ -16,7 +16,6 @@
package com.android.wm.shell.desktopmode
-import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import android.platform.test.flag.junit.SetFlagsRule
import com.android.dx.mockito.inline.extended.ExtendedMockito.verify
@@ -397,6 +396,37 @@ class DesktopModeEventLoggerTest : ShellTestCase() {
}
}
+ @Test
+ fun logTaskInfoStateInit_logsTaskInfoChangedStateInit() {
+ desktopModeEventLogger.logTaskInfoStateInit()
+ verify {
+ FrameworkStatsLog.write(eq(FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE),
+ /* task_event */
+ eq(FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_INIT_STATSD),
+ /* instance_id */
+ eq(0),
+ /* uid */
+ eq(0),
+ /* task_height */
+ eq(0),
+ /* task_width */
+ eq(0),
+ /* task_x */
+ eq(0),
+ /* task_y */
+ eq(0),
+ /* session_id */
+ eq(0),
+ /* minimize_reason */
+ eq(UNSET_MINIMIZE_REASON),
+ /* unminimize_reason */
+ eq(UNSET_UNMINIMIZE_REASON),
+ /* visible_task_count */
+ eq(0)
+ )
+ }
+ }
+
private companion object {
private const val SESSION_ID = 1
private const val TASK_ID = 1
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt
index daf7e7d5397b..e7593b5b9324 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt
@@ -115,6 +115,9 @@ class DesktopModeLoggerTransitionObserverTest : ShellTestCase() {
val initRunnableCaptor = ArgumentCaptor.forClass(Runnable::class.java)
verify(mockShellInit).addInitCallback(initRunnableCaptor.capture(), same(transitionObserver))
initRunnableCaptor.value.run()
+ // verify this initialisation interaction to leave the desktopmodeEventLogger mock in a
+ // consistent state with no outstanding interactions when test cases start executing.
+ verify(desktopModeEventLogger).logTaskInfoStateInit()
}
@Test
diff --git a/nfc/api/system-current.txt b/nfc/api/system-current.txt
index 4428adee818d..24e14e69637b 100644
--- a/nfc/api/system-current.txt
+++ b/nfc/api/system-current.txt
@@ -63,7 +63,7 @@ package android.nfc {
method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean isAutoChangeEnabled();
method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean isTagPresent();
method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void maybeTriggerFirmwareUpdate();
- method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void overwriteRoutingTable(int, int, int);
+ method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void overwriteRoutingTable(int, int, int, int);
method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void pausePolling(int);
method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void registerCallback(@NonNull java.util.concurrent.Executor, @NonNull android.nfc.NfcOemExtension.Callback);
method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void resumePolling();
diff --git a/nfc/java/android/nfc/INfcCardEmulation.aidl b/nfc/java/android/nfc/INfcCardEmulation.aidl
index 1eae3c6f30f1..8535e4a9cfd2 100644
--- a/nfc/java/android/nfc/INfcCardEmulation.aidl
+++ b/nfc/java/android/nfc/INfcCardEmulation.aidl
@@ -54,5 +54,5 @@ interface INfcCardEmulation
void setAutoChangeStatus(boolean state);
boolean isAutoChangeEnabled();
List<String> getRoutingStatus();
- void overwriteRoutingTable(int userHandle, String emptyAid, String protocol, String tech);
+ void overwriteRoutingTable(int userHandle, String emptyAid, String protocol, String tech, String sc);
}
diff --git a/nfc/java/android/nfc/NfcOemExtension.java b/nfc/java/android/nfc/NfcOemExtension.java
index fb63b5c03d00..bc410c7b8ba5 100644
--- a/nfc/java/android/nfc/NfcOemExtension.java
+++ b/nfc/java/android/nfc/NfcOemExtension.java
@@ -647,24 +647,29 @@ public final class NfcOemExtension {
* {@link ProtocolAndTechnologyRoute}
* @param emptyAid Zero-length AID route destination, where the possible inputs are defined in
* {@link ProtocolAndTechnologyRoute}
+ * @param systemCode System Code route destination, where the possible inputs are defined in
+ * {@link ProtocolAndTechnologyRoute}
*/
@RequiresPermission(Manifest.permission.WRITE_SECURE_SETTINGS)
@FlaggedApi(Flags.FLAG_NFC_OEM_EXTENSION)
public void overwriteRoutingTable(
@CardEmulation.ProtocolAndTechnologyRoute int protocol,
@CardEmulation.ProtocolAndTechnologyRoute int technology,
- @CardEmulation.ProtocolAndTechnologyRoute int emptyAid) {
+ @CardEmulation.ProtocolAndTechnologyRoute int emptyAid,
+ @CardEmulation.ProtocolAndTechnologyRoute int systemCode) {
String protocolRoute = routeIntToString(protocol);
String technologyRoute = routeIntToString(technology);
String emptyAidRoute = routeIntToString(emptyAid);
+ String systemCodeRoute = routeIntToString(systemCode);
NfcAdapter.callService(() ->
NfcAdapter.sCardEmulationService.overwriteRoutingTable(
mContext.getUser().getIdentifier(),
emptyAidRoute,
protocolRoute,
- technologyRoute
+ technologyRoute,
+ systemCodeRoute
));
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt
index ab95162fb142..5dca63724faf 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt
@@ -85,4 +85,6 @@ object SettingsDimension {
val illustrationMaxHeight = 300.dp
val illustrationPadding = paddingLarge
val illustrationCornerRadius = 28.dp
+
+ val preferenceMinHeight = 72.dp
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialogWithIcon.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialogWithIcon.kt
index 58a83fa72532..4cf270dca2bb 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialogWithIcon.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialogWithIcon.kt
@@ -17,7 +17,6 @@
package com.android.settingslib.spa.widget.dialog
import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
@@ -26,12 +25,13 @@ import androidx.compose.material.icons.filled.WarningAmber
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button
import androidx.compose.material3.Icon
+import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
-import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.window.DialogProperties
+import com.android.settingslib.spa.framework.theme.isSpaExpressiveEnabled
@Composable
fun SettingsAlertDialogWithIcon(
@@ -57,7 +57,9 @@ fun SettingsAlertDialogWithIcon(
title?.let {
{
CenterRow {
- Text(it, modifier = Modifier.fillMaxWidth(), textAlign = TextAlign.Center)
+ if (isSpaExpressiveEnabled)
+ Text(it, style = MaterialTheme.typography.bodyLarge)
+ else Text(it)
}
}
},
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/BaseLayout.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/BaseLayout.kt
index c68ec78b1ba6..acb96be64a34 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/BaseLayout.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/BaseLayout.kt
@@ -22,6 +22,7 @@ import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
@@ -35,6 +36,7 @@ import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.min
import com.android.settingslib.spa.framework.theme.SettingsDimension
import com.android.settingslib.spa.framework.theme.SettingsOpacity.alphaForEnabled
import com.android.settingslib.spa.framework.theme.SettingsShape
@@ -62,7 +64,8 @@ internal fun BaseLayout(
.semantics(mergeDescendants = true) {}
.then(
if (isSpaExpressiveEnabled)
- Modifier.clip(SettingsShape.CornerExtraSmall)
+ Modifier.heightIn(min = SettingsDimension.preferenceMinHeight)
+ .clip(SettingsShape.CornerExtraSmall)
.background(MaterialTheme.colorScheme.surfaceBright)
else Modifier
)
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/MainSwitchPreference.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/MainSwitchPreference.kt
index b28e88eb8af8..e6a23662e9f0 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/MainSwitchPreference.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/MainSwitchPreference.kt
@@ -17,6 +17,7 @@
package com.android.settingslib.spa.widget.preference
import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.MaterialTheme
@@ -25,6 +26,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.min
import com.android.settingslib.spa.framework.theme.SettingsDimension
import com.android.settingslib.spa.framework.theme.SettingsShape
import com.android.settingslib.spa.framework.theme.SettingsTheme
@@ -35,13 +37,19 @@ import com.android.settingslib.spa.framework.util.EntryHighlight
fun MainSwitchPreference(model: SwitchPreferenceModel) {
EntryHighlight {
Surface(
- modifier = Modifier.padding(SettingsDimension.itemPaddingEnd),
- color = when (model.checked()) {
- true -> MaterialTheme.colorScheme.primaryContainer
- else -> MaterialTheme.colorScheme.secondaryContainer
- },
- shape = if (isSpaExpressiveEnabled) CircleShape
- else SettingsShape.CornerExtraLarge,
+ modifier =
+ Modifier.padding(SettingsDimension.itemPaddingEnd)
+ .then(
+ if (isSpaExpressiveEnabled)
+ Modifier.heightIn(min = SettingsDimension.preferenceMinHeight)
+ else Modifier
+ ),
+ color =
+ when (model.checked()) {
+ true -> MaterialTheme.colorScheme.primaryContainer
+ else -> MaterialTheme.colorScheme.secondaryContainer
+ },
+ shape = if (isSpaExpressiveEnabled) CircleShape else SettingsShape.CornerExtraLarge,
) {
InternalSwitchPreference(
title = model.title,
@@ -61,16 +69,20 @@ fun MainSwitchPreference(model: SwitchPreferenceModel) {
private fun MainSwitchPreferencePreview() {
SettingsTheme {
Column {
- MainSwitchPreference(object : SwitchPreferenceModel {
- override val title = "Use Dark theme"
- override val checked = { true }
- override val onCheckedChange: (Boolean) -> Unit = {}
- })
- MainSwitchPreference(object : SwitchPreferenceModel {
- override val title = "Use Dark theme"
- override val checked = { false }
- override val onCheckedChange: (Boolean) -> Unit = {}
- })
+ MainSwitchPreference(
+ object : SwitchPreferenceModel {
+ override val title = "Use Dark theme"
+ override val checked = { true }
+ override val onCheckedChange: (Boolean) -> Unit = {}
+ }
+ )
+ MainSwitchPreference(
+ object : SwitchPreferenceModel {
+ override val title = "Use Dark theme"
+ override val checked = { false }
+ override val onCheckedChange: (Boolean) -> Unit = {}
+ }
+ )
}
}
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/ZeroStatePreference.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/ZeroStatePreference.kt
index b771f367e697..541922335387 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/ZeroStatePreference.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/ZeroStatePreference.kt
@@ -54,7 +54,7 @@ fun ZeroStatePreference(icon: ImageVector, text: String? = null, description: St
val zeroStateShape = remember {
RoundedPolygon.star(
numVerticesPerRadius = 6,
- innerRadius = 0.75f,
+ innerRadius = 0.8f,
rounding = CornerRounding(0.3f)
)
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/IDeviceSettingsConfigProviderService.aidl b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/IDeviceSettingsConfigProviderService.aidl
index 9cf49070a62c..c85756ed067a 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/IDeviceSettingsConfigProviderService.aidl
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/IDeviceSettingsConfigProviderService.aidl
@@ -20,5 +20,5 @@ import com.android.settingslib.bluetooth.devicesettings.DeviceInfo;
import com.android.settingslib.bluetooth.devicesettings.IGetDeviceSettingsConfigCallback;
interface IDeviceSettingsConfigProviderService {
- oneway void getDeviceSettingsConfig(in DeviceInfo device, in IGetDeviceSettingsConfigCallback callback);
+ void getDeviceSettingsConfig(in DeviceInfo device, in IGetDeviceSettingsConfigCallback callback);
} \ No newline at end of file
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingServiceConnection.kt b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingServiceConnection.kt
index 4af0504bd73a..a33fcc6747b4 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingServiceConnection.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingServiceConnection.kt
@@ -23,6 +23,7 @@ import android.content.Intent
import android.content.ServiceConnection
import android.os.IBinder
import android.os.IInterface
+import android.os.RemoteException
import android.text.TextUtils
import android.util.Log
import com.android.settingslib.bluetooth.BluetoothUtils
@@ -55,6 +56,7 @@ import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.emitAll
import kotlinx.coroutines.flow.filterIsInstance
import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.flow.flatMapConcat
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flow
@@ -100,6 +102,9 @@ class DeviceSettingServiceConnection(
private var isServiceEnabled =
coroutineScope.async(backgroundCoroutineContext, start = CoroutineStart.LAZY) {
val states = getSettingsProviderServices()?.values ?: return@async false
+ if (states.isEmpty()) {
+ return@async true
+ }
combine(states) { it.toList() }
.mapNotNull { allStatus ->
if (allStatus.any { it is ServiceConnectionStatus.Failed }) {
@@ -114,7 +119,7 @@ class DeviceSettingServiceConnection(
null
}
}
- .first()
+ .firstOrNull() ?: false
}
private var config =
@@ -131,9 +136,15 @@ class DeviceSettingServiceConnection(
is ServiceConnectionStatus.Connected ->
flowOf(
getDeviceSettingsConfigFromService(
- deviceInfo { setBluetoothAddress(cachedDevice.address) },
- it.service,
- )
+ deviceInfo { setBluetoothAddress(cachedDevice.address) },
+ it.service,
+ )
+ .also { config ->
+ Log.i(
+ TAG,
+ "device setting config for $cachedDevice is $config",
+ )
+ }
)
ServiceConnectionStatus.Connecting -> flowOf()
ServiceConnectionStatus.Failed -> flowOf(null)
@@ -146,21 +157,26 @@ class DeviceSettingServiceConnection(
deviceInfo: DeviceInfo,
service: IDeviceSettingsConfigProviderService,
): DeviceSettingsConfig? = suspendCancellableCoroutine { continuation ->
- service.getDeviceSettingsConfig(
- deviceInfo,
- object : IGetDeviceSettingsConfigCallback.Stub() {
- override fun onResult(
- status: DeviceSettingsConfigServiceStatus,
- config: DeviceSettingsConfig?,
- ) {
- if (!status.success) {
- continuation.resume(null)
- } else {
- continuation.resume(config)
+ try {
+ service.getDeviceSettingsConfig(
+ deviceInfo,
+ object : IGetDeviceSettingsConfigCallback.Stub() {
+ override fun onResult(
+ status: DeviceSettingsConfigServiceStatus,
+ config: DeviceSettingsConfig?,
+ ) {
+ if (!status.success) {
+ continuation.resume(null)
+ } else {
+ continuation.resume(config)
+ }
}
- }
- },
- )
+ },
+ )
+ } catch (e: RemoteException) {
+ Log.i(TAG, "Fail to get config")
+ continuation.resume(null)
+ }
}
private val settingIdToItemMapping =
@@ -298,13 +314,16 @@ class DeviceSettingServiceConnection(
val serviceConnection =
object : ServiceConnection {
override fun onServiceConnected(name: ComponentName, service: IBinder) {
+ Log.i(TAG, "Service connected for $intent")
launch { send(ServiceConnectionStatus.Connected(transform(service))) }
}
override fun onServiceDisconnected(name: ComponentName?) {
+ Log.i(TAG, "Service disconnected for $intent")
launch { send(ServiceConnectionStatus.Connecting) }
}
}
+ Log.i(TAG, "Try to bind service for $intent")
if (
!context.bindService(
intent,
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InputMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/InputMediaDevice.java
index 1d17b004fb6a..6335e712f904 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/InputMediaDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/InputMediaDevice.java
@@ -49,19 +49,23 @@ public class InputMediaDevice extends MediaDevice {
private final boolean mIsVolumeFixed;
+ private final String mProductName;
+
private InputMediaDevice(
@NonNull Context context,
@NonNull String id,
@AudioDeviceType int audioDeviceInfoType,
int maxVolume,
int currentVolume,
- boolean isVolumeFixed) {
+ boolean isVolumeFixed,
+ @Nullable String productName) {
super(context, /* info= */ null, /* item= */ null);
mId = id;
mAudioDeviceInfoType = audioDeviceInfoType;
mMaxVolume = maxVolume;
mCurrentVolume = currentVolume;
mIsVolumeFixed = isVolumeFixed;
+ mProductName = productName;
initDeviceRecord();
}
@@ -72,13 +76,20 @@ public class InputMediaDevice extends MediaDevice {
@AudioDeviceType int audioDeviceInfoType,
int maxVolume,
int currentVolume,
- boolean isVolumeFixed) {
+ boolean isVolumeFixed,
+ @Nullable String productName) {
if (!isSupportedInputDevice(audioDeviceInfoType)) {
return null;
}
return new InputMediaDevice(
- context, id, audioDeviceInfoType, maxVolume, currentVolume, isVolumeFixed);
+ context,
+ id,
+ audioDeviceInfoType,
+ maxVolume,
+ currentVolume,
+ isVolumeFixed,
+ productName);
}
public @AudioDeviceType int getAudioDeviceInfoType() {
@@ -98,18 +109,25 @@ public class InputMediaDevice extends MediaDevice {
};
}
+ @Nullable
+ public String getProductName() {
+ return mProductName;
+ }
+
@Override
public @NonNull String getName() {
- CharSequence name = switch (mAudioDeviceInfoType) {
- case TYPE_WIRED_HEADSET -> mContext.getString(
- R.string.media_transfer_wired_device_mic_name);
- case TYPE_USB_DEVICE, TYPE_USB_HEADSET, TYPE_USB_ACCESSORY -> mContext.getString(
- R.string.media_transfer_usb_device_mic_name);
- case TYPE_BLUETOOTH_SCO -> mContext.getString(
- R.string.media_transfer_bt_device_mic_name);
+ return switch (mAudioDeviceInfoType) {
+ case TYPE_WIRED_HEADSET ->
+ mContext.getString(R.string.media_transfer_wired_device_mic_name);
+ case TYPE_USB_DEVICE, TYPE_USB_HEADSET, TYPE_USB_ACCESSORY ->
+ // The product name is assumed to be a well-formed string if it's not null.
+ mProductName != null
+ ? mProductName
+ : mContext.getString(R.string.media_transfer_usb_device_mic_name);
+ case TYPE_BLUETOOTH_SCO ->
+ mContext.getString(R.string.media_transfer_bt_device_mic_name);
default -> mContext.getString(R.string.media_transfer_this_device_name_desktop);
};
- return name.toString();
}
@Override
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InputRouteManager.java b/packages/SettingsLib/src/com/android/settingslib/media/InputRouteManager.java
index a72ba8db848d..727662b80d98 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/InputRouteManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/InputRouteManager.java
@@ -151,7 +151,8 @@ public final class InputRouteManager {
info.getType(),
getMaxInputGain(),
getCurrentInputGain(),
- isInputGainFixed());
+ isInputGainFixed(),
+ getProductNameFromAudioDeviceInfo(info));
if (mediaDevice != null) {
if (info.getType() == selectedInputDeviceAttributesType) {
mediaDevice.setState(STATE_SELECTED);
@@ -169,6 +170,25 @@ public final class InputRouteManager {
}
}
+ /**
+ * Gets the product name for the given {@link AudioDeviceInfo}.
+ *
+ * @return The product name for the given {@link AudioDeviceInfo}, or null if a suitable name
+ * cannot be found.
+ */
+ @Nullable
+ private String getProductNameFromAudioDeviceInfo(AudioDeviceInfo deviceInfo) {
+ CharSequence productName = deviceInfo.getProductName();
+ if (productName == null) {
+ return null;
+ }
+ String productNameString = productName.toString();
+ if (productNameString.isBlank()) {
+ return null;
+ }
+ return productNameString;
+ }
+
public void selectDevice(@NonNull MediaDevice device) {
if (!(device instanceof InputMediaDevice)) {
Slog.w(TAG, "This device is not an InputMediaDevice: " + device.getName());
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InputMediaDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InputMediaDeviceTest.java
index 30e4637f59ff..6c1cb7015225 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InputMediaDeviceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InputMediaDeviceTest.java
@@ -41,6 +41,9 @@ public class InputMediaDeviceTest {
private final int MAX_VOLUME = 1;
private final int CURRENT_VOLUME = 0;
private final boolean IS_VOLUME_FIXED = true;
+ private static final String PRODUCT_NAME_BUILTIN_MIC = "Built-in Mic";
+ private static final String PRODUCT_NAME_WIRED_HEADSET = "My Wired Headset";
+ private static final String PRODUCT_NAME_USB_HEADSET = "My USB Headset";
@Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
@@ -60,7 +63,8 @@ public class InputMediaDeviceTest {
AudioDeviceInfo.TYPE_BUILTIN_MIC,
MAX_VOLUME,
CURRENT_VOLUME,
- IS_VOLUME_FIXED);
+ IS_VOLUME_FIXED,
+ PRODUCT_NAME_BUILTIN_MIC);
assertThat(builtinMediaDevice).isNotNull();
assertThat(builtinMediaDevice.getDrawableResId()).isEqualTo(R.drawable.ic_media_microphone);
}
@@ -74,7 +78,8 @@ public class InputMediaDeviceTest {
AudioDeviceInfo.TYPE_BUILTIN_MIC,
MAX_VOLUME,
CURRENT_VOLUME,
- IS_VOLUME_FIXED);
+ IS_VOLUME_FIXED,
+ PRODUCT_NAME_BUILTIN_MIC);
assertThat(builtinMediaDevice).isNotNull();
assertThat(builtinMediaDevice.getName())
.isEqualTo(mContext.getString(R.string.media_transfer_this_device_name_desktop));
@@ -89,7 +94,8 @@ public class InputMediaDeviceTest {
AudioDeviceInfo.TYPE_WIRED_HEADSET,
MAX_VOLUME,
CURRENT_VOLUME,
- IS_VOLUME_FIXED);
+ IS_VOLUME_FIXED,
+ PRODUCT_NAME_WIRED_HEADSET);
assertThat(wiredMediaDevice).isNotNull();
assertThat(wiredMediaDevice.getName())
.isEqualTo(mContext.getString(R.string.media_transfer_wired_device_mic_name));
@@ -104,7 +110,23 @@ public class InputMediaDeviceTest {
AudioDeviceInfo.TYPE_USB_HEADSET,
MAX_VOLUME,
CURRENT_VOLUME,
- IS_VOLUME_FIXED);
+ IS_VOLUME_FIXED,
+ PRODUCT_NAME_USB_HEADSET);
+ assertThat(usbMediaDevice).isNotNull();
+ assertThat(usbMediaDevice.getName()).isEqualTo(PRODUCT_NAME_USB_HEADSET);
+ }
+
+ @Test
+ public void getName_returnCorrectName_usbHeadset_nullProductName() {
+ InputMediaDevice usbMediaDevice =
+ InputMediaDevice.create(
+ mContext,
+ String.valueOf(USB_HEADSET_ID),
+ AudioDeviceInfo.TYPE_USB_HEADSET,
+ MAX_VOLUME,
+ CURRENT_VOLUME,
+ IS_VOLUME_FIXED,
+ null);
assertThat(usbMediaDevice).isNotNull();
assertThat(usbMediaDevice.getName())
.isEqualTo(mContext.getString(R.string.media_transfer_usb_device_mic_name));
@@ -119,7 +141,8 @@ public class InputMediaDeviceTest {
AudioDeviceInfo.TYPE_BLUETOOTH_SCO,
MAX_VOLUME,
CURRENT_VOLUME,
- IS_VOLUME_FIXED);
+ IS_VOLUME_FIXED,
+ null);
assertThat(btMediaDevice).isNotNull();
assertThat(btMediaDevice.getName())
.isEqualTo(mContext.getString(R.string.media_transfer_bt_device_mic_name));
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InputRouteManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InputRouteManagerTest.java
index 29cc4034d4b4..f63bfc70140d 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InputRouteManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InputRouteManagerTest.java
@@ -58,6 +58,11 @@ public class InputRouteManagerTest {
private static final int MAX_VOLUME = 1;
private static final int CURRENT_VOLUME = 0;
private static final boolean VOLUME_FIXED_TRUE = true;
+ private static final String PRODUCT_NAME_BUILTIN_MIC = "Built-in Mic";
+ private static final String PRODUCT_NAME_WIRED_HEADSET = "My Wired Headset";
+ private static final String PRODUCT_NAME_USB_HEADSET = "My USB Headset";
+ private static final String PRODUCT_NAME_USB_DEVICE = "My USB Device";
+ private static final String PRODUCT_NAME_USB_ACCESSORY = "My USB Accessory";
private final Context mContext = spy(RuntimeEnvironment.application);
private InputRouteManager mInputRouteManager;
@@ -75,25 +80,31 @@ public class InputRouteManagerTest {
final AudioDeviceInfo info1 = mock(AudioDeviceInfo.class);
when(info1.getType()).thenReturn(AudioDeviceInfo.TYPE_BUILTIN_MIC);
when(info1.getId()).thenReturn(BUILTIN_MIC_ID);
+ when(info1.getProductName()).thenReturn(PRODUCT_NAME_BUILTIN_MIC);
final AudioDeviceInfo info2 = mock(AudioDeviceInfo.class);
when(info2.getType()).thenReturn(AudioDeviceInfo.TYPE_WIRED_HEADSET);
when(info2.getId()).thenReturn(INPUT_WIRED_HEADSET_ID);
+ when(info2.getProductName()).thenReturn(PRODUCT_NAME_WIRED_HEADSET);
final AudioDeviceInfo info3 = mock(AudioDeviceInfo.class);
when(info3.getType()).thenReturn(AudioDeviceInfo.TYPE_USB_DEVICE);
when(info3.getId()).thenReturn(INPUT_USB_DEVICE_ID);
+ when(info3.getProductName()).thenReturn(PRODUCT_NAME_USB_DEVICE);
final AudioDeviceInfo info4 = mock(AudioDeviceInfo.class);
when(info4.getType()).thenReturn(AudioDeviceInfo.TYPE_USB_HEADSET);
when(info4.getId()).thenReturn(INPUT_USB_HEADSET_ID);
+ when(info4.getProductName()).thenReturn(PRODUCT_NAME_USB_HEADSET);
final AudioDeviceInfo info5 = mock(AudioDeviceInfo.class);
when(info5.getType()).thenReturn(AudioDeviceInfo.TYPE_USB_ACCESSORY);
when(info5.getId()).thenReturn(INPUT_USB_ACCESSORY_ID);
+ when(info5.getProductName()).thenReturn(PRODUCT_NAME_USB_ACCESSORY);
final AudioDeviceInfo unsupportedInfo = mock(AudioDeviceInfo.class);
when(unsupportedInfo.getType()).thenReturn(AudioDeviceInfo.TYPE_HDMI);
+ when(unsupportedInfo.getProductName()).thenReturn("HDMI device");
final AudioManager audioManager = mock(AudioManager.class);
AudioDeviceInfo[] devices = {info1, info2, info3, info4, info5, unsupportedInfo};
@@ -142,10 +153,12 @@ public class InputRouteManagerTest {
final AudioDeviceInfo info1 = mock(AudioDeviceInfo.class);
when(info1.getType()).thenReturn(AudioDeviceInfo.TYPE_WIRED_HEADSET);
when(info1.getId()).thenReturn(INPUT_WIRED_HEADSET_ID);
+ when(info1.getProductName()).thenReturn(PRODUCT_NAME_WIRED_HEADSET);
final AudioDeviceInfo info2 = mock(AudioDeviceInfo.class);
when(info2.getType()).thenReturn(AudioDeviceInfo.TYPE_BUILTIN_MIC);
when(info2.getId()).thenReturn(BUILTIN_MIC_ID);
+ when(info2.getProductName()).thenReturn(PRODUCT_NAME_BUILTIN_MIC);
final AudioManager audioManager = mock(AudioManager.class);
AudioDeviceInfo[] devices = {info1, info2};
@@ -171,10 +184,12 @@ public class InputRouteManagerTest {
final AudioDeviceInfo info1 = mock(AudioDeviceInfo.class);
when(info1.getType()).thenReturn(AudioDeviceInfo.TYPE_WIRED_HEADSET);
when(info1.getId()).thenReturn(INPUT_WIRED_HEADSET_ID);
+ when(info1.getProductName()).thenReturn(PRODUCT_NAME_WIRED_HEADSET);
final AudioDeviceInfo info2 = mock(AudioDeviceInfo.class);
when(info2.getType()).thenReturn(AudioDeviceInfo.TYPE_BUILTIN_MIC);
when(info2.getId()).thenReturn(BUILTIN_MIC_ID);
+ when(info2.getProductName()).thenReturn(PRODUCT_NAME_BUILTIN_MIC);
final AudioManager audioManager = mock(AudioManager.class);
AudioDeviceInfo[] devices = {info1, info2};
@@ -204,10 +219,12 @@ public class InputRouteManagerTest {
final AudioDeviceInfo info1 = mock(AudioDeviceInfo.class);
when(info1.getType()).thenReturn(AudioDeviceInfo.TYPE_WIRED_HEADSET);
when(info1.getId()).thenReturn(INPUT_WIRED_HEADSET_ID);
+ when(info1.getProductName()).thenReturn(PRODUCT_NAME_WIRED_HEADSET);
final AudioDeviceInfo info2 = mock(AudioDeviceInfo.class);
when(info2.getType()).thenReturn(AudioDeviceInfo.TYPE_BUILTIN_MIC);
when(info2.getId()).thenReturn(BUILTIN_MIC_ID);
+ when(info2.getProductName()).thenReturn(PRODUCT_NAME_BUILTIN_MIC);
final AudioManager audioManager = mock(AudioManager.class);
AudioDeviceInfo[] devices = {info1, info2};
@@ -239,7 +256,8 @@ public class InputRouteManagerTest {
AudioDeviceInfo.TYPE_BUILTIN_MIC,
MAX_VOLUME,
CURRENT_VOLUME,
- VOLUME_FIXED_TRUE);
+ VOLUME_FIXED_TRUE,
+ PRODUCT_NAME_BUILTIN_MIC);
inputRouteManager.selectDevice(inputMediaDevice);
AudioDeviceAttributes deviceAttributes =
@@ -267,4 +285,51 @@ public class InputRouteManagerTest {
public void isInputGainFixed() {
assertThat(mInputRouteManager.isInputGainFixed()).isTrue();
}
+
+ @Test
+ public void onAudioDevicesAdded_shouldSetProductNameCorrectly() {
+ final AudioDeviceInfo info1 = mock(AudioDeviceInfo.class);
+ when(info1.getType()).thenReturn(AudioDeviceInfo.TYPE_WIRED_HEADSET);
+ when(info1.getId()).thenReturn(INPUT_WIRED_HEADSET_ID);
+ String firstProductName = "My first headset";
+ when(info1.getProductName()).thenReturn(firstProductName);
+
+ final AudioDeviceInfo info2 = mock(AudioDeviceInfo.class);
+ when(info2.getType()).thenReturn(AudioDeviceInfo.TYPE_WIRED_HEADSET);
+ when(info2.getId()).thenReturn(INPUT_WIRED_HEADSET_ID);
+ String secondProductName = "My second headset";
+ when(info2.getProductName()).thenReturn(secondProductName);
+
+ final AudioDeviceInfo infoWithNullProductName = mock(AudioDeviceInfo.class);
+ when(infoWithNullProductName.getType()).thenReturn(AudioDeviceInfo.TYPE_WIRED_HEADSET);
+ when(infoWithNullProductName.getId()).thenReturn(INPUT_WIRED_HEADSET_ID);
+ when(infoWithNullProductName.getProductName()).thenReturn(null);
+
+ final AudioDeviceInfo infoWithBlankProductName = mock(AudioDeviceInfo.class);
+ when(infoWithBlankProductName.getType()).thenReturn(AudioDeviceInfo.TYPE_WIRED_HEADSET);
+ when(infoWithBlankProductName.getId()).thenReturn(INPUT_WIRED_HEADSET_ID);
+ when(infoWithBlankProductName.getProductName()).thenReturn("");
+
+ final AudioManager audioManager = mock(AudioManager.class);
+ AudioDeviceInfo[] devices = {
+ info1, info2, infoWithNullProductName, infoWithBlankProductName
+ };
+ when(audioManager.getDevices(AudioManager.GET_DEVICES_INPUTS)).thenReturn(devices);
+
+ InputRouteManager inputRouteManager = new InputRouteManager(mContext, audioManager);
+
+ assertThat(inputRouteManager.mInputMediaDevices).isEmpty();
+
+ inputRouteManager.mAudioDeviceCallback.onAudioDevicesAdded(devices);
+
+ assertThat(getProductNameAtIndex(inputRouteManager, 1)).isEqualTo(firstProductName);
+ assertThat(getProductNameAtIndex(inputRouteManager, 2)).isEqualTo(secondProductName);
+ assertThat(getProductNameAtIndex(inputRouteManager, 3)).isNull();
+ assertThat(getProductNameAtIndex(inputRouteManager, 4)).isNull();
+ }
+
+ private String getProductNameAtIndex(InputRouteManager inputRouteManager, int index) {
+ return ((InputMediaDevice) inputRouteManager.mInputMediaDevices.get(index))
+ .getProductName();
+ }
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiUtilsTest.java
index 529301138da3..d8b6707b9118 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiUtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiUtilsTest.java
@@ -207,20 +207,6 @@ public class WifiUtilsTest {
}
@Test
- public void getHotspotIconResource_deviceTypeExists_shouldNotNull() {
- assertThat(WifiUtils.getHotspotIconResource(NetworkProviderInfo.DEVICE_TYPE_PHONE))
- .isNotNull();
- assertThat(WifiUtils.getHotspotIconResource(NetworkProviderInfo.DEVICE_TYPE_TABLET))
- .isNotNull();
- assertThat(WifiUtils.getHotspotIconResource(NetworkProviderInfo.DEVICE_TYPE_LAPTOP))
- .isNotNull();
- assertThat(WifiUtils.getHotspotIconResource(NetworkProviderInfo.DEVICE_TYPE_WATCH))
- .isNotNull();
- assertThat(WifiUtils.getHotspotIconResource(NetworkProviderInfo.DEVICE_TYPE_AUTO))
- .isNotNull();
- }
-
- @Test
public void testInternetIconInjector_getIcon_returnsCorrectValues() {
WifiUtils.InternetIconInjector iconInjector = new WifiUtils.InternetIconInjector(mContext);
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
index 2cdd0aee3d85..3530e0f5f9de 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
@@ -106,6 +106,8 @@ public class SystemSettings {
Settings.System.UNREAD_NOTIFICATION_DOT_INDICATOR,
Settings.System.AUTO_LAUNCH_MEDIA_CONTROLS,
Settings.System.LOCALE_PREFERENCES,
+ Settings.System.MOUSE_REVERSE_VERTICAL_SCROLLING,
+ Settings.System.MOUSE_SWAP_PRIMARY_BUTTON,
Settings.System.TOUCHPAD_POINTER_SPEED,
Settings.System.TOUCHPAD_NATURAL_SCROLLING,
Settings.System.TOUCHPAD_TAP_TO_CLICK,
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
index 282327739fc6..509b88b257fe 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
@@ -221,6 +221,8 @@ public class SystemSettingsValidators {
POINTER_ICON_VECTOR_STYLE_STROKE_END));
VALIDATORS.put(System.POINTER_SCALE,
new InclusiveFloatRangeValidator(DEFAULT_POINTER_SCALE, LARGE_POINTER_SCALE));
+ VALIDATORS.put(System.MOUSE_REVERSE_VERTICAL_SCROLLING, BOOLEAN_VALIDATOR);
+ VALIDATORS.put(System.MOUSE_SWAP_PRIMARY_BUTTON, BOOLEAN_VALIDATOR);
VALIDATORS.put(System.TOUCHPAD_POINTER_SPEED, new InclusiveIntegerRangeValidator(-7, 7));
VALIDATORS.put(System.TOUCHPAD_NATURAL_SCROLLING, BOOLEAN_VALIDATOR);
VALIDATORS.put(System.TOUCHPAD_TAP_TO_CLICK, BOOLEAN_VALIDATOR);
diff --git a/packages/SystemUI/TEST_MAPPING b/packages/SystemUI/TEST_MAPPING
index 07a1e630e1ad..380344a23c99 100644
--- a/packages/SystemUI/TEST_MAPPING
+++ b/packages/SystemUI/TEST_MAPPING
@@ -148,5 +148,10 @@
{
"name": "SystemUIGoogleRobo2RNGTests"
}
+ ],
+ "imports": [
+ {
+ "path": "cts/tests/tests/multiuser"
+ }
]
}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ComposedDigitalLayerController.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ComposedDigitalLayerController.kt
new file mode 100644
index 000000000000..9b94c91a348c
--- /dev/null
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ComposedDigitalLayerController.kt
@@ -0,0 +1,203 @@
+/*
+ * 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.shared.clocks
+
+import android.content.Context
+import android.content.res.Resources
+import android.graphics.Rect
+import androidx.annotation.VisibleForTesting
+import com.android.systemui.log.core.Logger
+import com.android.systemui.log.core.MessageBuffer
+import com.android.systemui.plugins.clocks.AlarmData
+import com.android.systemui.plugins.clocks.ClockAnimations
+import com.android.systemui.plugins.clocks.ClockEvents
+import com.android.systemui.plugins.clocks.ClockFaceConfig
+import com.android.systemui.plugins.clocks.ClockFaceEvents
+import com.android.systemui.plugins.clocks.ClockReactiveSetting
+import com.android.systemui.plugins.clocks.WeatherData
+import com.android.systemui.plugins.clocks.ZenData
+import com.android.systemui.shared.clocks.view.DigitalClockFaceView
+import com.android.systemui.shared.clocks.view.FlexClockView
+import java.util.Locale
+import java.util.TimeZone
+
+class ComposedDigitalLayerController(
+ private val ctx: Context,
+ private val assets: AssetLoader,
+ private val layer: ComposedDigitalHandLayer,
+ private val isLargeClock: Boolean,
+ messageBuffer: MessageBuffer,
+) : SimpleClockLayerController {
+ private val logger = Logger(messageBuffer, ComposedDigitalLayerController::class.simpleName!!)
+
+ val layerControllers = mutableListOf<SimpleClockLayerController>()
+ val dozeState = DefaultClockController.AnimationState(1F)
+ var isRegionDark = true
+
+ override var view: DigitalClockFaceView =
+ when (layer.customizedView) {
+ "FlexClockView" -> FlexClockView(ctx, assets, messageBuffer)
+ else -> {
+ throw IllegalStateException("CustomizedView string is not valid")
+ }
+ }
+
+ // Matches LayerControllerConstructor
+ internal constructor(
+ ctx: Context,
+ assets: AssetLoader,
+ layer: ClockLayer,
+ isLargeClock: Boolean,
+ messageBuffer: MessageBuffer,
+ ) : this(ctx, assets, layer as ComposedDigitalHandLayer, isLargeClock, messageBuffer)
+
+ init {
+ layer.digitalLayers.forEach {
+ val controller =
+ SimpleClockLayerController.Factory.create(
+ ctx,
+ assets,
+ it,
+ isLargeClock,
+ messageBuffer,
+ )
+ view.addView(controller.view)
+ layerControllers.add(controller)
+ }
+ }
+
+ private fun refreshTime() {
+ layerControllers.forEach { it.faceEvents.onTimeTick() }
+ view.refreshTime()
+ }
+
+ override val events =
+ object : ClockEvents {
+ override fun onTimeZoneChanged(timeZone: TimeZone) {
+ layerControllers.forEach { it.events.onTimeZoneChanged(timeZone) }
+ refreshTime()
+ }
+
+ override fun onTimeFormatChanged(is24Hr: Boolean) {
+ layerControllers.forEach { it.events.onTimeFormatChanged(is24Hr) }
+ refreshTime()
+ }
+
+ override fun onLocaleChanged(locale: Locale) {
+ layerControllers.forEach { it.events.onLocaleChanged(locale) }
+ view.onLocaleChanged(locale)
+ refreshTime()
+ }
+
+ override fun onWeatherDataChanged(data: WeatherData) {
+ view.onWeatherDataChanged(data)
+ }
+
+ override fun onAlarmDataChanged(data: AlarmData) {
+ view.onAlarmDataChanged(data)
+ }
+
+ override fun onZenDataChanged(data: ZenData) {
+ view.onZenDataChanged(data)
+ }
+
+ override fun onColorPaletteChanged(resources: Resources) {}
+
+ override fun onSeedColorChanged(seedColor: Int?) {}
+
+ override fun onReactiveAxesChanged(axes: List<ClockReactiveSetting>) {}
+
+ override var isReactiveTouchInteractionEnabled
+ get() = view.isReactiveTouchInteractionEnabled
+ set(value) {
+ view.isReactiveTouchInteractionEnabled = value
+ }
+ }
+
+ override fun updateColors() {
+ view.updateColors(assets, isRegionDark)
+ }
+
+ override val animations =
+ object : ClockAnimations {
+ override fun enter() {
+ refreshTime()
+ }
+
+ override fun doze(fraction: Float) {
+ val (hasChanged, hasJumped) = dozeState.update(fraction)
+ if (hasChanged) view.animateDoze(dozeState.isActive, !hasJumped)
+ view.dozeFraction = fraction
+ view.invalidate()
+ }
+
+ override fun fold(fraction: Float) {
+ refreshTime()
+ }
+
+ override fun charge() {
+ view.animateCharge()
+ }
+
+ override fun onPositionUpdated(fromLeft: Int, direction: Int, fraction: Float) {
+ view.onPositionUpdated(fromLeft, direction, fraction)
+ }
+
+ override fun onPositionUpdated(distance: Float, fraction: Float) {}
+
+ override fun onPickerCarouselSwiping(swipingFraction: Float) {
+ view.onPickerCarouselSwiping(swipingFraction)
+ }
+ }
+
+ override val faceEvents =
+ object : ClockFaceEvents {
+ override fun onTimeTick() {
+ refreshTime()
+ }
+
+ override fun onRegionDarknessChanged(isRegionDark: Boolean) {
+ this@ComposedDigitalLayerController.isRegionDark = isRegionDark
+ updateColors()
+ }
+
+ override fun onFontSettingChanged(fontSizePx: Float) {
+ view.onFontSettingChanged(fontSizePx)
+ }
+
+ override fun onTargetRegionChanged(targetRegion: Rect?) {}
+
+ override fun onSecondaryDisplayChanged(onSecondaryDisplay: Boolean) {}
+ }
+
+ override val config =
+ ClockFaceConfig(
+ hasCustomWeatherDataDisplay = view.hasCustomWeatherDataDisplay,
+ hasCustomPositionUpdatedAnimation = view.hasCustomPositionUpdatedAnimation,
+ useCustomClockScene = view.useCustomClockScene,
+ )
+
+ @VisibleForTesting
+ override var fakeTimeMills: Long? = null
+ get() = field
+ set(timeInMills) {
+ field = timeInMills
+ for (layerController in layerControllers) {
+ layerController.fakeTimeMills = timeInMills
+ }
+ }
+}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
index 07191c671a34..ac268420fb75 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
@@ -24,6 +24,8 @@ import com.android.systemui.plugins.clocks.ClockMetadata
import com.android.systemui.plugins.clocks.ClockPickerConfig
import com.android.systemui.plugins.clocks.ClockProvider
import com.android.systemui.plugins.clocks.ClockSettings
+import com.android.systemui.shared.clocks.view.HorizontalAlignment
+import com.android.systemui.shared.clocks.view.VerticalAlignment
private val TAG = DefaultClockProvider::class.simpleName
const val DEFAULT_CLOCK_ID = "DEFAULT"
@@ -33,8 +35,9 @@ class DefaultClockProvider(
val ctx: Context,
val layoutInflater: LayoutInflater,
val resources: Resources,
- val hasStepClockAnimation: Boolean = false,
- val migratedClocks: Boolean = false,
+ private val hasStepClockAnimation: Boolean = false,
+ private val migratedClocks: Boolean = false,
+ private val clockReactiveVariants: Boolean = false,
) : ClockProvider {
private var messageBuffers: ClockMessageBuffers? = null
@@ -49,15 +52,23 @@ class DefaultClockProvider(
throw IllegalArgumentException("${settings.clockId} is unsupported by $TAG")
}
- return DefaultClockController(
- ctx,
- layoutInflater,
- resources,
- settings,
- hasStepClockAnimation,
- migratedClocks,
- messageBuffers,
- )
+ return if (clockReactiveVariants) {
+ // TODO handle the case here where only the smallClock message buffer is added
+ val assetLoader =
+ AssetLoader(ctx, ctx, "clocks/", messageBuffers?.smallClockMessageBuffer!!)
+
+ SimpleClockController(ctx, assetLoader, FLEX_DESIGN, messageBuffers)
+ } else {
+ DefaultClockController(
+ ctx,
+ layoutInflater,
+ resources,
+ settings,
+ hasStepClockAnimation,
+ migratedClocks,
+ messageBuffers,
+ )
+ }
}
override fun getClockPickerConfig(id: ClockId): ClockPickerConfig {
@@ -73,4 +84,163 @@ class DefaultClockProvider(
resources.getDrawable(R.drawable.clock_default_thumbnail, null),
)
}
+
+ companion object {
+ val FLEX_DESIGN = run {
+ val largeLayer =
+ listOf(
+ ComposedDigitalHandLayer(
+ layerBounds = LayerBounds.FIT,
+ customizedView = "FlexClockView",
+ digitalLayers =
+ listOf(
+ DigitalHandLayer(
+ layerBounds = LayerBounds.FIT,
+ timespec = DigitalTimespec.FIRST_DIGIT,
+ style =
+ FontTextStyle(
+ fontFamily = "google_sans_flex.ttf",
+ lineHeight = 147.25f,
+ fontVariation =
+ "'wght' 603, 'wdth' 100, 'opsz' 144, 'ROND' 100",
+ ),
+ aodStyle =
+ FontTextStyle(
+ fontVariation =
+ "'wght' 74, 'wdth' 43, 'opsz' 144, 'ROND' 100",
+ fontFamily = "google_sans_flex.ttf",
+ fillColorLight = "#FFFFFFFF",
+ outlineColor = "#00000000",
+ renderType = RenderType.CHANGE_WEIGHT,
+ transitionInterpolator = InterpolatorEnum.EMPHASIZED,
+ transitionDuration = 750,
+ ),
+ alignment =
+ DigitalAlignment(
+ HorizontalAlignment.CENTER,
+ VerticalAlignment.CENTER
+ ),
+ dateTimeFormat = "hh"
+ ),
+ DigitalHandLayer(
+ layerBounds = LayerBounds.FIT,
+ timespec = DigitalTimespec.SECOND_DIGIT,
+ style =
+ FontTextStyle(
+ fontFamily = "google_sans_flex.ttf",
+ lineHeight = 147.25f,
+ fontVariation =
+ "'wght' 603, 'wdth' 100, 'opsz' 144, 'ROND' 100",
+ ),
+ aodStyle =
+ FontTextStyle(
+ fontVariation =
+ "'wght' 74, 'wdth' 43, 'opsz' 144, 'ROND' 100",
+ fontFamily = "google_sans_flex.ttf",
+ fillColorLight = "#FFFFFFFF",
+ outlineColor = "#00000000",
+ renderType = RenderType.CHANGE_WEIGHT,
+ transitionInterpolator = InterpolatorEnum.EMPHASIZED,
+ transitionDuration = 750,
+ ),
+ alignment =
+ DigitalAlignment(
+ HorizontalAlignment.CENTER,
+ VerticalAlignment.CENTER
+ ),
+ dateTimeFormat = "hh"
+ ),
+ DigitalHandLayer(
+ layerBounds = LayerBounds.FIT,
+ timespec = DigitalTimespec.FIRST_DIGIT,
+ style =
+ FontTextStyle(
+ fontFamily = "google_sans_flex.ttf",
+ lineHeight = 147.25f,
+ fontVariation =
+ "'wght' 603, 'wdth' 100, 'opsz' 144, 'ROND' 100",
+ ),
+ aodStyle =
+ FontTextStyle(
+ fontVariation =
+ "'wght' 74, 'wdth' 43, 'opsz' 144, 'ROND' 100",
+ fontFamily = "google_sans_flex.ttf",
+ fillColorLight = "#FFFFFFFF",
+ outlineColor = "#00000000",
+ renderType = RenderType.CHANGE_WEIGHT,
+ transitionInterpolator = InterpolatorEnum.EMPHASIZED,
+ transitionDuration = 750,
+ ),
+ alignment =
+ DigitalAlignment(
+ HorizontalAlignment.CENTER,
+ VerticalAlignment.CENTER
+ ),
+ dateTimeFormat = "mm"
+ ),
+ DigitalHandLayer(
+ layerBounds = LayerBounds.FIT,
+ timespec = DigitalTimespec.SECOND_DIGIT,
+ style =
+ FontTextStyle(
+ fontFamily = "google_sans_flex.ttf",
+ lineHeight = 147.25f,
+ fontVariation =
+ "'wght' 603, 'wdth' 100, 'opsz' 144, 'ROND' 100",
+ ),
+ aodStyle =
+ FontTextStyle(
+ fontVariation =
+ "'wght' 74, 'wdth' 43, 'opsz' 144, 'ROND' 100",
+ fontFamily = "google_sans_flex.ttf",
+ fillColorLight = "#FFFFFFFF",
+ outlineColor = "#00000000",
+ renderType = RenderType.CHANGE_WEIGHT,
+ transitionInterpolator = InterpolatorEnum.EMPHASIZED,
+ transitionDuration = 750,
+ ),
+ alignment =
+ DigitalAlignment(
+ HorizontalAlignment.CENTER,
+ VerticalAlignment.CENTER
+ ),
+ dateTimeFormat = "mm"
+ )
+ )
+ )
+ )
+
+ val smallLayer =
+ listOf(
+ DigitalHandLayer(
+ layerBounds = LayerBounds.FIT,
+ timespec = DigitalTimespec.TIME_FULL_FORMAT,
+ style =
+ FontTextStyle(
+ fontFamily = "google_sans_flex.ttf",
+ fontVariation = "'wght' 600, 'wdth' 100, 'opsz' 144, 'ROND' 100",
+ fontSizeScale = 0.98f,
+ ),
+ aodStyle =
+ FontTextStyle(
+ fontFamily = "google_sans_flex.ttf",
+ fontVariation = "'wght' 133, 'wdth' 43, 'opsz' 144, 'ROND' 100",
+ fillColorLight = "#FFFFFFFF",
+ outlineColor = "#00000000",
+ renderType = RenderType.CHANGE_WEIGHT,
+ ),
+ alignment = DigitalAlignment(HorizontalAlignment.LEFT, null),
+ dateTimeFormat = "h:mm"
+ )
+ )
+
+ ClockDesign(
+ id = DEFAULT_CLOCK_ID,
+ name = "@string/clock_default_name",
+ description = "@string/clock_default_description",
+ large = ClockFace(layers = largeLayer),
+ small = ClockFace(layers = smallLayer)
+ )
+ }
+ }
}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/LayoutUtils.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/LayoutUtils.kt
new file mode 100644
index 000000000000..ef8bee0875d2
--- /dev/null
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/LayoutUtils.kt
@@ -0,0 +1,35 @@
+/*
+ * 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.shared.clocks
+
+import android.graphics.Rect
+import android.view.View
+
+fun computeLayoutDiff(
+ view: View,
+ targetRegion: Rect,
+ isLargeClock: Boolean,
+): Pair<Float, Float> {
+ val parent = view.parent
+ if (parent is View && parent.isLaidOut() && isLargeClock) {
+ return Pair(
+ targetRegion.centerX() - parent.width / 2f,
+ targetRegion.centerY() - parent.height / 2f
+ )
+ }
+ return Pair(0f, 0f)
+}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/SimpleClockController.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/SimpleClockController.kt
new file mode 100644
index 000000000000..ec7779825bda
--- /dev/null
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/SimpleClockController.kt
@@ -0,0 +1,152 @@
+/*
+ * 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.shared.clocks
+
+import android.content.Context
+import android.content.res.Resources
+import com.android.systemui.monet.Style as MonetStyle
+import com.android.systemui.plugins.clocks.AlarmData
+import com.android.systemui.plugins.clocks.ClockConfig
+import com.android.systemui.plugins.clocks.ClockController
+import com.android.systemui.plugins.clocks.ClockEvents
+import com.android.systemui.plugins.clocks.ClockMessageBuffers
+import com.android.systemui.plugins.clocks.ClockReactiveSetting
+import com.android.systemui.plugins.clocks.WeatherData
+import com.android.systemui.plugins.clocks.ZenData
+import java.io.PrintWriter
+import java.util.Locale
+import java.util.TimeZone
+
+/** Controller for a simple json specified clock */
+class SimpleClockController(
+ private val ctx: Context,
+ private val assets: AssetLoader,
+ val design: ClockDesign,
+ val messageBuffers: ClockMessageBuffers?,
+) : ClockController {
+ override val smallClock = run {
+ val buffer = messageBuffers?.smallClockMessageBuffer ?: LogUtil.DEFAULT_MESSAGE_BUFFER
+ SimpleClockFaceController(
+ ctx,
+ assets.copy(messageBuffer = buffer),
+ design.small ?: design.large!!,
+ false,
+ buffer,
+ )
+ }
+
+ override val largeClock = run {
+ val buffer = messageBuffers?.largeClockMessageBuffer ?: LogUtil.DEFAULT_MESSAGE_BUFFER
+ SimpleClockFaceController(
+ ctx,
+ assets.copy(messageBuffer = buffer),
+ design.large ?: design.small!!,
+ true,
+ buffer,
+ )
+ }
+
+ override val config: ClockConfig by lazy {
+ ClockConfig(
+ design.id,
+ design.name?.let { assets.tryReadString(it) ?: it } ?: "",
+ design.description?.let { assets.tryReadString(it) ?: it } ?: "",
+ isReactiveToTone =
+ design.colorPalette == null || design.colorPalette == MonetStyle.CLOCK,
+ useAlternateSmartspaceAODTransition =
+ smallClock.config.hasCustomWeatherDataDisplay ||
+ largeClock.config.hasCustomWeatherDataDisplay,
+ useCustomClockScene =
+ smallClock.config.useCustomClockScene || largeClock.config.useCustomClockScene,
+ )
+ }
+
+ override val events =
+ object : ClockEvents {
+ override var isReactiveTouchInteractionEnabled = false
+ set(value) {
+ field = value
+ smallClock.events.isReactiveTouchInteractionEnabled = value
+ largeClock.events.isReactiveTouchInteractionEnabled = value
+ }
+
+ override fun onTimeZoneChanged(timeZone: TimeZone) {
+ smallClock.events.onTimeZoneChanged(timeZone)
+ largeClock.events.onTimeZoneChanged(timeZone)
+ }
+
+ override fun onTimeFormatChanged(is24Hr: Boolean) {
+ smallClock.events.onTimeFormatChanged(is24Hr)
+ largeClock.events.onTimeFormatChanged(is24Hr)
+ }
+
+ override fun onLocaleChanged(locale: Locale) {
+ smallClock.events.onLocaleChanged(locale)
+ largeClock.events.onLocaleChanged(locale)
+ }
+
+ override fun onColorPaletteChanged(resources: Resources) {
+ assets.refreshColorPalette(design.colorPalette)
+ smallClock.assets.refreshColorPalette(design.colorPalette)
+ largeClock.assets.refreshColorPalette(design.colorPalette)
+
+ smallClock.events.onColorPaletteChanged(resources)
+ largeClock.events.onColorPaletteChanged(resources)
+ }
+
+ override fun onSeedColorChanged(seedColor: Int?) {
+ assets.setSeedColor(seedColor, design.colorPalette)
+ smallClock.assets.setSeedColor(seedColor, design.colorPalette)
+ largeClock.assets.setSeedColor(seedColor, design.colorPalette)
+
+ smallClock.events.onSeedColorChanged(seedColor)
+ largeClock.events.onSeedColorChanged(seedColor)
+ }
+
+ override fun onWeatherDataChanged(data: WeatherData) {
+ smallClock.events.onWeatherDataChanged(data)
+ largeClock.events.onWeatherDataChanged(data)
+ }
+
+ override fun onAlarmDataChanged(data: AlarmData) {
+ smallClock.events.onAlarmDataChanged(data)
+ largeClock.events.onAlarmDataChanged(data)
+ }
+
+ override fun onZenDataChanged(data: ZenData) {
+ smallClock.events.onZenDataChanged(data)
+ largeClock.events.onZenDataChanged(data)
+ }
+
+ override fun onReactiveAxesChanged(axes: List<ClockReactiveSetting>) {
+ smallClock.events.onReactiveAxesChanged(axes)
+ largeClock.events.onReactiveAxesChanged(axes)
+ }
+ }
+
+ override fun initialize(resources: Resources, dozeFraction: Float, foldFraction: Float) {
+ events.onColorPaletteChanged(resources)
+ smallClock.animations.doze(dozeFraction)
+ largeClock.animations.doze(dozeFraction)
+ smallClock.animations.fold(foldFraction)
+ largeClock.animations.fold(foldFraction)
+ smallClock.events.onTimeTick()
+ largeClock.events.onTimeTick()
+ }
+
+ override fun dump(pw: PrintWriter) {}
+}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/SimpleClockFaceController.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/SimpleClockFaceController.kt
new file mode 100644
index 000000000000..ef398d1a52a0
--- /dev/null
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/SimpleClockFaceController.kt
@@ -0,0 +1,314 @@
+/*
+ * 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.shared.clocks
+
+import android.content.Context
+import android.content.res.Resources
+import android.graphics.Rect
+import android.view.Gravity
+import android.view.View
+import android.view.ViewGroup.LayoutParams.MATCH_PARENT
+import android.widget.FrameLayout
+import com.android.systemui.log.core.MessageBuffer
+import com.android.systemui.plugins.clocks.AlarmData
+import com.android.systemui.plugins.clocks.ClockAnimations
+import com.android.systemui.plugins.clocks.ClockEvents
+import com.android.systemui.plugins.clocks.ClockFaceConfig
+import com.android.systemui.plugins.clocks.ClockFaceController
+import com.android.systemui.plugins.clocks.ClockFaceEvents
+import com.android.systemui.plugins.clocks.ClockFaceLayout
+import com.android.systemui.plugins.clocks.ClockReactiveSetting
+import com.android.systemui.plugins.clocks.ClockTickRate
+import com.android.systemui.plugins.clocks.DefaultClockFaceLayout
+import com.android.systemui.plugins.clocks.WeatherData
+import com.android.systemui.plugins.clocks.ZenData
+import com.android.systemui.shared.clocks.view.DigitalClockFaceView
+import java.util.Locale
+import java.util.TimeZone
+import kotlin.math.max
+
+interface ClockEventUnion : ClockEvents, ClockFaceEvents
+
+class SimpleClockFaceController(
+ ctx: Context,
+ val assets: AssetLoader,
+ face: ClockFace,
+ isLargeClock: Boolean,
+ messageBuffer: MessageBuffer,
+) : ClockFaceController {
+ override val view: View
+ override val config: ClockFaceConfig by lazy {
+ ClockFaceConfig(
+ hasCustomWeatherDataDisplay = layers.any { it.config.hasCustomWeatherDataDisplay },
+ hasCustomPositionUpdatedAnimation =
+ layers.any { it.config.hasCustomPositionUpdatedAnimation },
+ tickRate = getTickRate(),
+ useCustomClockScene = layers.any { it.config.useCustomClockScene },
+ )
+ }
+
+ val layers = mutableListOf<SimpleClockLayerController>()
+
+ val timespecHandler = DigitalTimespecHandler(DigitalTimespec.TIME_FULL_FORMAT, "hh:mm")
+
+ init {
+ val lp = FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT)
+ lp.gravity = Gravity.CENTER
+ view =
+ if (face.layers.size == 1) {
+ // Optimize a clocks with a single layer by excluding the face level view group. We
+ // expect the view container from the host process to always be a FrameLayout.
+ val layer = face.layers[0]
+ val controller =
+ SimpleClockLayerController.Factory.create(
+ ctx,
+ assets,
+ layer,
+ isLargeClock,
+ messageBuffer,
+ )
+ layers.add(controller)
+ controller.view.layoutParams = lp
+ controller.view
+ } else {
+ // For multiple views, we use an intermediate RelativeLayout so that we can do some
+ // intelligent laying out between the children views.
+ val group = SimpleClockRelativeLayout(ctx, face.faceLayout)
+ group.layoutParams = lp
+ group.gravity = Gravity.CENTER
+ group.clipChildren = false
+ for (layer in face.layers) {
+ face.faceLayout?.let {
+ if (layer is DigitalHandLayer) {
+ layer.faceLayout = it
+ }
+ }
+ val controller =
+ SimpleClockLayerController.Factory.create(
+ ctx,
+ assets,
+ layer,
+ isLargeClock,
+ messageBuffer,
+ )
+ group.addView(controller.view)
+ layers.add(controller)
+ }
+ group
+ }
+ }
+
+ override val layout: ClockFaceLayout =
+ DefaultClockFaceLayout(view).apply {
+ views[0].id =
+ if (isLargeClock) {
+ assets.getResourcesId("lockscreen_clock_view_large")
+ } else {
+ assets.getResourcesId("lockscreen_clock_view")
+ }
+ }
+
+ override val events =
+ object : ClockEventUnion {
+ override var isReactiveTouchInteractionEnabled = false
+ get() = field
+ set(value) {
+ field = value
+ layers.forEach { it.events.isReactiveTouchInteractionEnabled = value }
+ }
+
+ override fun onTimeTick() {
+ timespecHandler.updateTime()
+ if (
+ config.tickRate == ClockTickRate.PER_MINUTE ||
+ view.contentDescription != timespecHandler.getContentDescription()
+ ) {
+ view.contentDescription = timespecHandler.getContentDescription()
+ }
+ layers.forEach { it.faceEvents.onTimeTick() }
+ }
+
+ override fun onTimeZoneChanged(timeZone: TimeZone) {
+ timespecHandler.timeZone = timeZone
+ layers.forEach { it.events.onTimeZoneChanged(timeZone) }
+ }
+
+ override fun onTimeFormatChanged(is24Hr: Boolean) {
+ timespecHandler.is24Hr = is24Hr
+ layers.forEach { it.events.onTimeFormatChanged(is24Hr) }
+ }
+
+ override fun onLocaleChanged(locale: Locale) {
+ timespecHandler.updateLocale(locale)
+ layers.forEach { it.events.onLocaleChanged(locale) }
+ }
+
+ override fun onFontSettingChanged(fontSizePx: Float) {
+ layers.forEach { it.faceEvents.onFontSettingChanged(fontSizePx) }
+ }
+
+ override fun onColorPaletteChanged(resources: Resources) {
+ layers.forEach {
+ it.events.onColorPaletteChanged(resources)
+ it.updateColors()
+ }
+ }
+
+ override fun onSeedColorChanged(seedColor: Int?) {
+ layers.forEach {
+ it.events.onSeedColorChanged(seedColor)
+ it.updateColors()
+ }
+ }
+
+ override fun onRegionDarknessChanged(isRegionDark: Boolean) {
+ layers.forEach { it.faceEvents.onRegionDarknessChanged(isRegionDark) }
+ }
+
+ override fun onReactiveAxesChanged(axes: List<ClockReactiveSetting>) {}
+
+ /**
+ * targetRegion passed to all customized clock applies counter translationY of
+ * KeyguardStatusView and keyguard_large_clock_top_margin from default clock
+ */
+ override fun onTargetRegionChanged(targetRegion: Rect?) {
+ // When a clock needs to be aligned with screen, like weather clock
+ // it needs to offset back the translation of keyguard_large_clock_top_margin
+ if (view is DigitalClockFaceView && view.isAlignedWithScreen()) {
+ val topMargin = getKeyguardLargeClockTopMargin(assets)
+ targetRegion?.let {
+ val (_, yDiff) = computeLayoutDiff(view, it, isLargeClock)
+ // In LS, we use yDiff to counter translate
+ // the translation of KeyguardLargeClockTopMargin
+ // With the targetRegion passed from picker,
+ // we will have yDiff = 0, no translation is needed for weather clock
+ if (yDiff.toInt() != 0) view.translationY = yDiff - topMargin / 2
+ }
+ return
+ }
+
+ var maxWidth = 0f
+ var maxHeight = 0f
+
+ for (layer in layers) {
+ layer.faceEvents.onTargetRegionChanged(targetRegion)
+ maxWidth = max(maxWidth, layer.view.layoutParams.width.toFloat())
+ maxHeight = max(maxHeight, layer.view.layoutParams.height.toFloat())
+ }
+
+ val lp =
+ if (maxHeight <= 0 || maxWidth <= 0 || targetRegion == null) {
+ // No specified width/height. Just match parent size.
+ FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT)
+ } else {
+ // Scale to fit in targetRegion based on largest child elements.
+ val ratio = maxWidth / maxHeight
+ val targetRatio = targetRegion.width() / targetRegion.height().toFloat()
+ val scale =
+ if (ratio > targetRatio) targetRegion.width() / maxWidth
+ else targetRegion.height() / maxHeight
+
+ FrameLayout.LayoutParams(
+ (maxWidth * scale).toInt(),
+ (maxHeight * scale).toInt(),
+ )
+ }
+
+ lp.gravity = Gravity.CENTER
+ view.layoutParams = lp
+ targetRegion?.let {
+ val (xDiff, yDiff) = computeLayoutDiff(view, it, isLargeClock)
+ view.translationX = xDiff
+ view.translationY = yDiff
+ }
+ }
+
+ override fun onSecondaryDisplayChanged(onSecondaryDisplay: Boolean) {}
+
+ override fun onWeatherDataChanged(data: WeatherData) {
+ layers.forEach { it.events.onWeatherDataChanged(data) }
+ }
+
+ override fun onAlarmDataChanged(data: AlarmData) {
+ layers.forEach { it.events.onAlarmDataChanged(data) }
+ }
+
+ override fun onZenDataChanged(data: ZenData) {
+ layers.forEach { it.events.onZenDataChanged(data) }
+ }
+ }
+
+ override val animations =
+ object : ClockAnimations {
+ override fun enter() {
+ layers.forEach { it.animations.enter() }
+ }
+
+ override fun doze(fraction: Float) {
+ layers.forEach { it.animations.doze(fraction) }
+ }
+
+ override fun fold(fraction: Float) {
+ layers.forEach { it.animations.fold(fraction) }
+ }
+
+ override fun charge() {
+ layers.forEach { it.animations.charge() }
+ }
+
+ override fun onPickerCarouselSwiping(swipingFraction: Float) {
+ face.pickerScale?.let {
+ view.scaleX = swipingFraction * (1 - it.scaleX) + it.scaleX
+ view.scaleY = swipingFraction * (1 - it.scaleY) + it.scaleY
+ }
+ if (!(view is DigitalClockFaceView && view.isAlignedWithScreen())) {
+ val topMargin = getKeyguardLargeClockTopMargin(assets)
+ view.translationY = topMargin / 2F * swipingFraction
+ }
+ layers.forEach { it.animations.onPickerCarouselSwiping(swipingFraction) }
+ view.invalidate()
+ }
+
+ override fun onPositionUpdated(fromLeft: Int, direction: Int, fraction: Float) {
+ layers.forEach { it.animations.onPositionUpdated(fromLeft, direction, fraction) }
+ }
+
+ override fun onPositionUpdated(distance: Float, fraction: Float) {
+ layers.forEach { it.animations.onPositionUpdated(distance, fraction) }
+ }
+ }
+
+ private fun getTickRate(): ClockTickRate {
+ var tickRate = ClockTickRate.PER_MINUTE
+ for (layer in layers) {
+ if (layer.config.tickRate.value < tickRate.value) {
+ tickRate = layer.config.tickRate
+ }
+ }
+ return tickRate
+ }
+
+ private fun getKeyguardLargeClockTopMargin(assets: AssetLoader): Int {
+ val topMarginRes =
+ assets.resolveResourceId(null, "dimen", "keyguard_large_clock_top_margin")
+ if (topMarginRes != null) {
+ val (res, id) = topMarginRes
+ return res.getDimensionPixelSize(id)
+ }
+ return 0
+ }
+}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/SimpleClockLayerController.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/SimpleClockLayerController.kt
new file mode 100644
index 000000000000..f71543efa650
--- /dev/null
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/SimpleClockLayerController.kt
@@ -0,0 +1,102 @@
+/*
+ * 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.shared.clocks
+
+import android.content.Context
+import android.view.View
+import androidx.annotation.VisibleForTesting
+import com.android.systemui.log.core.MessageBuffer
+import com.android.systemui.plugins.clocks.ClockAnimations
+import com.android.systemui.plugins.clocks.ClockEvents
+import com.android.systemui.plugins.clocks.ClockFaceConfig
+import com.android.systemui.plugins.clocks.ClockFaceEvents
+import com.android.systemui.shared.clocks.view.SimpleDigitalClockTextView
+import kotlin.reflect.KClass
+
+typealias LayerControllerConstructor =
+ (
+ ctx: Context,
+ assets: AssetLoader,
+ layer: ClockLayer,
+ isLargeClock: Boolean,
+ messageBuffer: MessageBuffer,
+ ) -> SimpleClockLayerController
+
+interface SimpleClockLayerController {
+ val view: View
+ val events: ClockEvents
+ val animations: ClockAnimations
+ val faceEvents: ClockFaceEvents
+ val config: ClockFaceConfig
+
+ @VisibleForTesting var fakeTimeMills: Long?
+
+ // Called immediately after either onColorPaletteChanged or onSeedColorChanged is called.
+ // Provided for convience to not duplicate color update logic after state updated.
+ fun updateColors() {}
+
+ companion object Factory {
+ val constructorMap = mutableMapOf<Pair<KClass<*>, KClass<*>?>, LayerControllerConstructor>()
+
+ internal inline fun <reified TLayer> registerConstructor(
+ noinline constructor: LayerControllerConstructor,
+ ) where TLayer : ClockLayer {
+ constructorMap[Pair(TLayer::class, null)] = constructor
+ }
+
+ inline fun <reified TLayer, reified TStyle> registerTextConstructor(
+ noinline constructor: LayerControllerConstructor,
+ ) where TLayer : ClockLayer, TStyle : TextStyle {
+ constructorMap[Pair(TLayer::class, TStyle::class)] = constructor
+ }
+
+ init {
+ registerConstructor<ComposedDigitalHandLayer>(::ComposedDigitalLayerController)
+ registerTextConstructor<DigitalHandLayer, FontTextStyle>(::createSimpleDigitalLayer)
+ }
+
+ private fun createSimpleDigitalLayer(
+ ctx: Context,
+ assets: AssetLoader,
+ layer: ClockLayer,
+ isLargeClock: Boolean,
+ messageBuffer: MessageBuffer
+ ): SimpleClockLayerController {
+ val view = SimpleDigitalClockTextView(ctx, messageBuffer)
+ return SimpleDigitalHandLayerController(
+ ctx,
+ assets,
+ layer as DigitalHandLayer,
+ view,
+ messageBuffer
+ )
+ }
+
+ fun create(
+ ctx: Context,
+ assets: AssetLoader,
+ layer: ClockLayer,
+ isLargeClock: Boolean,
+ messageBuffer: MessageBuffer
+ ): SimpleClockLayerController {
+ val styleClass = if (layer is DigitalHandLayer) layer.style::class else null
+ val key = Pair(layer::class, styleClass)
+ return constructorMap[key]?.invoke(ctx, assets, layer, isLargeClock, messageBuffer)
+ ?: throw IllegalArgumentException("Unrecognized ClockLayer type: $key")
+ }
+ }
+}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/SimpleClockRelativeLayout.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/SimpleClockRelativeLayout.kt
new file mode 100644
index 000000000000..6e1b9aabf86d
--- /dev/null
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/SimpleClockRelativeLayout.kt
@@ -0,0 +1,47 @@
+/*
+ * 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.shared.clocks
+
+import android.content.Context
+import android.view.View.MeasureSpec.EXACTLY
+import android.widget.RelativeLayout
+import androidx.core.view.children
+import com.android.systemui.shared.clocks.view.SimpleDigitalClockView
+
+class SimpleClockRelativeLayout(context: Context, val faceLayout: DigitalFaceLayout?) :
+ RelativeLayout(context) {
+ override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
+ // For migrate_clocks_to_blueprint, mode is EXACTLY
+ // when the flag is turned off, we won't execute this codes
+ if (MeasureSpec.getMode(heightMeasureSpec) == EXACTLY) {
+ if (
+ faceLayout == DigitalFaceLayout.TWO_PAIRS_VERTICAL ||
+ faceLayout == DigitalFaceLayout.FOUR_DIGITS_ALIGN_CENTER
+ ) {
+ val constrainedHeight = MeasureSpec.getSize(heightMeasureSpec) / 2F
+ children.forEach {
+ // The assumption here is the height of text view is linear to font size
+ (it as SimpleDigitalClockView).applyTextSize(
+ constrainedHeight,
+ constrainedByHeight = true,
+ )
+ }
+ }
+ }
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec)
+ }
+}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/SimpleDigitalHandLayerController.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/SimpleDigitalHandLayerController.kt
new file mode 100644
index 000000000000..a3240f81e499
--- /dev/null
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/SimpleDigitalHandLayerController.kt
@@ -0,0 +1,328 @@
+/*
+ * 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.shared.clocks
+
+import android.content.Context
+import android.content.res.Resources
+import android.graphics.Rect
+import android.view.View
+import android.view.ViewGroup
+import android.widget.RelativeLayout
+import androidx.annotation.VisibleForTesting
+import com.android.systemui.customization.R
+import com.android.systemui.log.core.Logger
+import com.android.systemui.log.core.MessageBuffer
+import com.android.systemui.plugins.clocks.AlarmData
+import com.android.systemui.plugins.clocks.ClockAnimations
+import com.android.systemui.plugins.clocks.ClockEvents
+import com.android.systemui.plugins.clocks.ClockFaceConfig
+import com.android.systemui.plugins.clocks.ClockFaceEvents
+import com.android.systemui.plugins.clocks.ClockReactiveSetting
+import com.android.systemui.plugins.clocks.WeatherData
+import com.android.systemui.plugins.clocks.ZenData
+import com.android.systemui.shared.clocks.view.SimpleDigitalClockView
+import java.util.Locale
+import java.util.TimeZone
+
+private val TAG = SimpleDigitalHandLayerController::class.simpleName!!
+
+open class SimpleDigitalHandLayerController<T>(
+ private val ctx: Context,
+ private val assets: AssetLoader,
+ private val layer: DigitalHandLayer,
+ override val view: T,
+ messageBuffer: MessageBuffer,
+) : SimpleClockLayerController where T : View, T : SimpleDigitalClockView {
+ private val logger = Logger(messageBuffer, TAG)
+ val timespec = DigitalTimespecHandler(layer.timespec, layer.dateTimeFormat)
+
+ @VisibleForTesting
+ fun hasLeadingZero() = layer.dateTimeFormat.startsWith("hh") || timespec.is24Hr
+
+ @VisibleForTesting
+ override var fakeTimeMills: Long?
+ get() = timespec.fakeTimeMills
+ set(value) {
+ timespec.fakeTimeMills = value
+ }
+
+ override val config = ClockFaceConfig()
+ var dozeState: DefaultClockController.AnimationState? = null
+ var isRegionDark: Boolean = true
+
+ init {
+ view.layoutParams =
+ RelativeLayout.LayoutParams(
+ ViewGroup.LayoutParams.WRAP_CONTENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT
+ )
+ if (layer.alignment != null) {
+ layer.alignment.verticalAlignment?.let { view.verticalAlignment = it }
+ layer.alignment.horizontalAlignment?.let { view.horizontalAlignment = it }
+ }
+ view.applyStyles(assets, layer.style, layer.aodStyle)
+ view.id =
+ ctx.resources.getIdentifier(
+ generateDigitalLayerIdString(layer),
+ "id",
+ ctx.getPackageName(),
+ )
+ }
+
+ fun applyLayout(layout: DigitalFaceLayout?) {
+ when (layout) {
+ DigitalFaceLayout.FOUR_DIGITS_ALIGN_CENTER,
+ DigitalFaceLayout.FOUR_DIGITS_HORIZONTAL -> applyFourDigitsLayout(layout)
+ DigitalFaceLayout.TWO_PAIRS_HORIZONTAL,
+ DigitalFaceLayout.TWO_PAIRS_VERTICAL -> applyTwoPairsLayout(layout)
+ else -> {
+ // one view always use FrameLayout
+ // no need to change here
+ }
+ }
+ applyMargin()
+ }
+
+ private fun applyMargin() {
+ if (view.layoutParams is RelativeLayout.LayoutParams) {
+ val lp = view.layoutParams as RelativeLayout.LayoutParams
+ layer.marginRatio?.let {
+ lp.setMargins(
+ /* left = */ (it.left * view.measuredWidth).toInt(),
+ /* top = */ (it.top * view.measuredHeight).toInt(),
+ /* right = */ (it.right * view.measuredWidth).toInt(),
+ /* bottom = */ (it.bottom * view.measuredHeight).toInt(),
+ )
+ }
+ view.layoutParams = lp
+ }
+ }
+
+ private fun applyTwoPairsLayout(twoPairsLayout: DigitalFaceLayout) {
+ val lp = view.layoutParams as RelativeLayout.LayoutParams
+ lp.addRule(RelativeLayout.TEXT_ALIGNMENT_CENTER)
+ if (twoPairsLayout == DigitalFaceLayout.TWO_PAIRS_HORIZONTAL) {
+ when (view.id) {
+ R.id.HOUR_DIGIT_PAIR -> {
+ lp.addRule(RelativeLayout.CENTER_VERTICAL)
+ lp.addRule(RelativeLayout.ALIGN_PARENT_START)
+ }
+ R.id.MINUTE_DIGIT_PAIR -> {
+ lp.addRule(RelativeLayout.CENTER_VERTICAL)
+ lp.addRule(RelativeLayout.END_OF, R.id.HOUR_DIGIT_PAIR)
+ }
+ else -> {
+ throw Exception("cannot apply two pairs layout to view ${view.id}")
+ }
+ }
+ } else {
+ when (view.id) {
+ R.id.HOUR_DIGIT_PAIR -> {
+ lp.addRule(RelativeLayout.CENTER_HORIZONTAL)
+ lp.addRule(RelativeLayout.ALIGN_PARENT_TOP)
+ }
+ R.id.MINUTE_DIGIT_PAIR -> {
+ lp.addRule(RelativeLayout.CENTER_HORIZONTAL)
+ lp.addRule(RelativeLayout.BELOW, R.id.HOUR_DIGIT_PAIR)
+ }
+ else -> {
+ throw Exception("cannot apply two pairs layout to view ${view.id}")
+ }
+ }
+ }
+ view.layoutParams = lp
+ }
+
+ private fun applyFourDigitsLayout(fourDigitsfaceLayout: DigitalFaceLayout) {
+ val lp = view.layoutParams as RelativeLayout.LayoutParams
+ when (fourDigitsfaceLayout) {
+ DigitalFaceLayout.FOUR_DIGITS_ALIGN_CENTER -> {
+ when (view.id) {
+ R.id.HOUR_FIRST_DIGIT -> {
+ lp.addRule(RelativeLayout.ALIGN_PARENT_START)
+ lp.addRule(RelativeLayout.ALIGN_PARENT_TOP)
+ }
+ R.id.HOUR_SECOND_DIGIT -> {
+ lp.addRule(RelativeLayout.END_OF, R.id.HOUR_FIRST_DIGIT)
+ lp.addRule(RelativeLayout.ALIGN_TOP, R.id.HOUR_FIRST_DIGIT)
+ }
+ R.id.MINUTE_FIRST_DIGIT -> {
+ lp.addRule(RelativeLayout.ALIGN_START, R.id.HOUR_FIRST_DIGIT)
+ lp.addRule(RelativeLayout.BELOW, R.id.HOUR_FIRST_DIGIT)
+ }
+ R.id.MINUTE_SECOND_DIGIT -> {
+ lp.addRule(RelativeLayout.ALIGN_START, R.id.HOUR_SECOND_DIGIT)
+ lp.addRule(RelativeLayout.BELOW, R.id.HOUR_SECOND_DIGIT)
+ }
+ else -> {
+ throw Exception("cannot apply four digits layout to view ${view.id}")
+ }
+ }
+ }
+ DigitalFaceLayout.FOUR_DIGITS_HORIZONTAL -> {
+ when (view.id) {
+ R.id.HOUR_FIRST_DIGIT -> {
+ lp.addRule(RelativeLayout.CENTER_VERTICAL)
+ lp.addRule(RelativeLayout.ALIGN_PARENT_START)
+ }
+ R.id.HOUR_SECOND_DIGIT -> {
+ lp.addRule(RelativeLayout.CENTER_VERTICAL)
+ lp.addRule(RelativeLayout.END_OF, R.id.HOUR_FIRST_DIGIT)
+ }
+ R.id.MINUTE_FIRST_DIGIT -> {
+ lp.addRule(RelativeLayout.CENTER_VERTICAL)
+ lp.addRule(RelativeLayout.END_OF, R.id.HOUR_SECOND_DIGIT)
+ }
+ R.id.MINUTE_SECOND_DIGIT -> {
+ lp.addRule(RelativeLayout.CENTER_VERTICAL)
+ lp.addRule(RelativeLayout.END_OF, R.id.MINUTE_FIRST_DIGIT)
+ }
+ else -> {
+ throw Exception("cannot apply FOUR_DIGITS_HORIZONTAL to view ${view.id}")
+ }
+ }
+ }
+ else -> {
+ throw IllegalArgumentException(
+ "applyFourDigitsLayout function should not " +
+ "have parameters as ${layer.faceLayout}"
+ )
+ }
+ }
+ if (lp == view.layoutParams) {
+ return
+ }
+ view.layoutParams = lp
+ }
+
+ fun refreshTime() {
+ timespec.updateTime()
+ val text = timespec.getDigitString()
+ if (view.text != text) {
+ view.text = text
+ view.refreshTime()
+ logger.d({ "refreshTime: new text=$str1" }) { str1 = text }
+ }
+ }
+
+ override val events =
+ object : ClockEvents {
+ override var isReactiveTouchInteractionEnabled = false
+
+ override fun onLocaleChanged(locale: Locale) {
+ timespec.updateLocale(locale)
+ refreshTime()
+ }
+
+ /** Call whenever the text time format changes (12hr vs 24hr) */
+ override fun onTimeFormatChanged(is24Hr: Boolean) {
+ timespec.is24Hr = is24Hr
+ refreshTime()
+ }
+
+ override fun onTimeZoneChanged(timeZone: TimeZone) {
+ timespec.timeZone = timeZone
+ refreshTime()
+ }
+
+ override fun onColorPaletteChanged(resources: Resources) {}
+
+ override fun onSeedColorChanged(seedColor: Int?) {}
+
+ override fun onWeatherDataChanged(data: WeatherData) {}
+
+ override fun onAlarmDataChanged(data: AlarmData) {}
+
+ override fun onZenDataChanged(data: ZenData) {}
+
+ override fun onReactiveAxesChanged(axes: List<ClockReactiveSetting>) {}
+ }
+
+ override fun updateColors() {
+ view.updateColors(assets, isRegionDark)
+ refreshTime()
+ }
+
+ override val animations =
+ object : ClockAnimations {
+ override fun enter() {
+ applyLayout(layer.faceLayout)
+ refreshTime()
+ }
+
+ override fun doze(fraction: Float) {
+ if (dozeState == null) {
+ dozeState = DefaultClockController.AnimationState(fraction)
+ view.animateDoze(dozeState!!.isActive, false)
+ } else {
+ val (hasChanged, hasJumped) = dozeState!!.update(fraction)
+ if (hasChanged) view.animateDoze(dozeState!!.isActive, !hasJumped)
+ }
+ view.dozeFraction = fraction
+ }
+
+ override fun fold(fraction: Float) {
+ applyLayout(layer.faceLayout)
+ refreshTime()
+ }
+
+ override fun charge() {
+ view.animateCharge()
+ }
+
+ override fun onPickerCarouselSwiping(swipingFraction: Float) {}
+
+ override fun onPositionUpdated(fromLeft: Int, direction: Int, fraction: Float) {}
+
+ override fun onPositionUpdated(distance: Float, fraction: Float) {}
+ }
+
+ override val faceEvents =
+ object : ClockFaceEvents {
+ override fun onTimeTick() {
+ refreshTime()
+ if (
+ layer.timespec == DigitalTimespec.TIME_FULL_FORMAT ||
+ layer.timespec == DigitalTimespec.DATE_FORMAT
+ ) {
+ view.contentDescription = timespec.getContentDescription()
+ }
+ }
+
+ override fun onFontSettingChanged(fontSizePx: Float) {
+ view.applyTextSize(fontSizePx)
+ applyMargin()
+ }
+
+ override fun onRegionDarknessChanged(isRegionDark: Boolean) {
+ this@SimpleDigitalHandLayerController.isRegionDark = isRegionDark
+ updateColors()
+ }
+
+ override fun onTargetRegionChanged(targetRegion: Rect?) {}
+
+ override fun onSecondaryDisplayChanged(onSecondaryDisplay: Boolean) {}
+ }
+
+ companion object {
+ private val DEFAULT_LIGHT_COLOR = "@android:color/system_accent1_100+0"
+ private val DEFAULT_DARK_COLOR = "@android:color/system_accent2_600+0"
+
+ fun getDefaultColor(assets: AssetLoader, isRegionDark: Boolean) =
+ assets.readColor(if (isRegionDark) DEFAULT_LIGHT_COLOR else DEFAULT_DARK_COLOR)
+ }
+}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/TimespecHandler.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/TimespecHandler.kt
new file mode 100644
index 000000000000..ed6a403a7c7e
--- /dev/null
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/TimespecHandler.kt
@@ -0,0 +1,161 @@
+/*
+ * 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.shared.clocks
+
+import android.icu.text.DateFormat
+import android.icu.text.SimpleDateFormat
+import android.icu.util.TimeZone as IcuTimeZone
+import android.icu.util.ULocale
+import androidx.annotation.VisibleForTesting
+import java.util.Calendar
+import java.util.Locale
+import java.util.TimeZone
+
+open class TimespecHandler(
+ val cal: Calendar,
+) {
+ var timeZone: TimeZone
+ get() = cal.timeZone
+ set(value) {
+ cal.timeZone = value
+ onTimeZoneChanged()
+ }
+
+ @VisibleForTesting var fakeTimeMills: Long? = null
+
+ fun updateTime() {
+ var timeMs = fakeTimeMills ?: System.currentTimeMillis()
+ cal.timeInMillis = (timeMs * TIME_TRAVEL_SCALE).toLong()
+ }
+
+ protected open fun onTimeZoneChanged() {}
+
+ companion object {
+ // Modifying this will cause the clock to run faster or slower. This is a useful way of
+ // manually checking that clocks are correctly animating through time.
+ private const val TIME_TRAVEL_SCALE = 1.0
+ }
+}
+
+class DigitalTimespecHandler(
+ val timespec: DigitalTimespec,
+ private val timeFormat: String,
+ cal: Calendar = Calendar.getInstance(),
+) : TimespecHandler(cal) {
+ var is24Hr = false
+ set(value) {
+ field = value
+ applyPattern()
+ }
+
+ private var dateFormat = updateSimpleDateFormat(Locale.getDefault())
+ private var contentDescriptionFormat = getContentDescriptionFormat(Locale.getDefault())
+
+ init {
+ applyPattern()
+ }
+
+ override fun onTimeZoneChanged() {
+ dateFormat.timeZone = IcuTimeZone.getTimeZone(cal.timeZone.id)
+ contentDescriptionFormat?.timeZone = IcuTimeZone.getTimeZone(cal.timeZone.id)
+ applyPattern()
+ }
+
+ fun updateLocale(locale: Locale) {
+ dateFormat = updateSimpleDateFormat(locale)
+ contentDescriptionFormat = getContentDescriptionFormat(locale)
+ onTimeZoneChanged()
+ }
+
+ private fun updateSimpleDateFormat(locale: Locale): DateFormat {
+ if (
+ locale.language.equals(Locale.ENGLISH.language) ||
+ timespec != DigitalTimespec.DATE_FORMAT
+ ) {
+ // force date format in English, and time format to use format defined in json
+ return SimpleDateFormat(timeFormat, timeFormat, ULocale.forLocale(locale))
+ } else {
+ return SimpleDateFormat.getInstanceForSkeleton(timeFormat, locale)
+ }
+ }
+
+ private fun getContentDescriptionFormat(locale: Locale): DateFormat? {
+ return when (timespec) {
+ DigitalTimespec.TIME_FULL_FORMAT ->
+ SimpleDateFormat.getInstanceForSkeleton("hh:mm", locale)
+ DigitalTimespec.DATE_FORMAT ->
+ SimpleDateFormat.getInstanceForSkeleton("EEEE MMMM d", locale)
+ else -> {
+ null
+ }
+ }
+ }
+
+ private fun applyPattern() {
+ val timeFormat24Hour = timeFormat.replace("hh", "h").replace("h", "HH")
+ val format = if (is24Hr) timeFormat24Hour else timeFormat
+ if (timespec != DigitalTimespec.DATE_FORMAT) {
+ (dateFormat as SimpleDateFormat).applyPattern(format)
+ (contentDescriptionFormat as? SimpleDateFormat)?.applyPattern(
+ if (is24Hr) CONTENT_DESCRIPTION_TIME_FORMAT_24_HOUR
+ else CONTENT_DESCRIPTION_TIME_FORMAT_12_HOUR
+ )
+ }
+ }
+
+ private fun getSingleDigit(): String {
+ val isFirstDigit = timespec == DigitalTimespec.FIRST_DIGIT
+ val text = dateFormat.format(cal.time).toString()
+ return text.substring(
+ if (isFirstDigit) 0 else text.length - 1,
+ if (isFirstDigit) text.length - 1 else text.length
+ )
+ }
+
+ fun getDigitString(): String {
+ return when (timespec) {
+ DigitalTimespec.FIRST_DIGIT,
+ DigitalTimespec.SECOND_DIGIT -> getSingleDigit()
+ DigitalTimespec.DIGIT_PAIR -> {
+ dateFormat.format(cal.time).toString()
+ }
+ DigitalTimespec.TIME_FULL_FORMAT -> {
+ dateFormat.format(cal.time).toString()
+ }
+ DigitalTimespec.DATE_FORMAT -> {
+ dateFormat.format(cal.time).toString().uppercase()
+ }
+ }
+ }
+
+ fun getContentDescription(): String? {
+ return when (timespec) {
+ DigitalTimespec.TIME_FULL_FORMAT,
+ DigitalTimespec.DATE_FORMAT -> {
+ contentDescriptionFormat?.format(cal.time).toString()
+ }
+ else -> {
+ return null
+ }
+ }
+ }
+
+ companion object {
+ const val CONTENT_DESCRIPTION_TIME_FORMAT_12_HOUR = "hh:mm"
+ const val CONTENT_DESCRIPTION_TIME_FORMAT_24_HOUR = "HH:mm"
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
index d63e728cf443..d63e728cf443 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
index 2c1dacdfae73..2c1dacdfae73 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
index 5fe5cb3fe43c..5fe5cb3fe43c 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityViewFlipperControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityViewFlipperControllerTest.java
index 7bb6ef1c8895..7bb6ef1c8895 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityViewFlipperControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityViewFlipperControllerTest.java
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt
index 9cd52153eff6..9cd52153eff6 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt
index 3c229975eef5..3c229975eef5 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSliceViewControllerTest.java
index 8b5372a1f035..8b5372a1f035 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSliceViewControllerTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlaySuppressionControllerImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/clipboardoverlay/ClipboardOverlaySuppressionControllerImplTest.kt
index 86788d35a670..86788d35a670 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlaySuppressionControllerImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/clipboardoverlay/ClipboardOverlaySuppressionControllerImplTest.kt
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
index 179ba2256442..cecc11e5ffd4 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
@@ -19,7 +19,6 @@ package com.android.systemui.communal.view.viewmodel
import android.appwidget.AppWidgetProviderInfo
import android.content.ActivityNotFoundException
import android.content.ComponentName
-import android.content.Intent
import android.content.pm.PackageManager
import android.content.pm.UserInfo
import android.provider.Settings
@@ -27,7 +26,6 @@ import android.view.accessibility.AccessibilityEvent
import android.view.accessibility.AccessibilityManager
import android.view.accessibility.accessibilityManager
import android.widget.RemoteViews
-import androidx.activity.result.ActivityResultLauncher
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.internal.logging.UiEventLogger
@@ -88,7 +86,6 @@ class CommunalEditModeViewModelTest : SysuiTestCase() {
@Mock private lateinit var mediaHost: MediaHost
@Mock private lateinit var uiEventLogger: UiEventLogger
@Mock private lateinit var packageManager: PackageManager
- @Mock private lateinit var activityResultLauncher: ActivityResultLauncher<Intent>
@Mock private lateinit var metricsLogger: CommunalMetricsLogger
private val kosmos = testKosmos()
@@ -117,10 +114,7 @@ class CommunalEditModeViewModelTest : SysuiTestCase() {
communalSceneInteractor = kosmos.communalSceneInteractor
communalInteractor = spy(kosmos.communalInteractor)
kosmos.fakeUserRepository.setUserInfos(listOf(MAIN_USER_INFO))
- kosmos.fakeUserTracker.set(
- userInfos = listOf(MAIN_USER_INFO),
- selectedUserIndex = 0,
- )
+ kosmos.fakeUserTracker.set(userInfos = listOf(MAIN_USER_INFO), selectedUserIndex = 0)
kosmos.fakeFeatureFlagsClassic.set(Flags.COMMUNAL_SERVICE_ENABLED, true)
accessibilityManager = kosmos.accessibilityManager
@@ -257,10 +251,13 @@ class CommunalEditModeViewModelTest : SysuiTestCase() {
@Test
fun onOpenWidgetPicker_launchesWidgetPickerActivity() {
testScope.runTest {
+ var activityStarted = false
val success =
- underTest.onOpenWidgetPicker(testableResources.resources, activityResultLauncher)
+ underTest.onOpenWidgetPicker(testableResources.resources) { _ ->
+ run { activityStarted = true }
+ }
- verify(activityResultLauncher).launch(any())
+ assertTrue(activityStarted)
assertTrue(success)
}
}
@@ -268,14 +265,10 @@ class CommunalEditModeViewModelTest : SysuiTestCase() {
@Test
fun onOpenWidgetPicker_activityLaunchThrowsException_failure() {
testScope.runTest {
- whenever(activityResultLauncher.launch(any()))
- .thenThrow(ActivityNotFoundException::class.java)
-
val success =
- underTest.onOpenWidgetPicker(
- testableResources.resources,
- activityResultLauncher,
- )
+ underTest.onOpenWidgetPicker(testableResources.resources) { _ ->
+ run { throw ActivityNotFoundException() }
+ }
assertFalse(success)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/lifecycle/HydratorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/lifecycle/HydratorTest.kt
index b0e93fbecbb9..b0e93fbecbb9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/lifecycle/HydratorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/lifecycle/HydratorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/views/NavigationBarButtonTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/views/NavigationBarButtonTest.java
index 00e79f5a3ac2..00e79f5a3ac2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/views/NavigationBarButtonTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/views/NavigationBarButtonTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RecordIssueTileTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/RecordIssueTileTest.kt
index c7da03dbbf30..c7da03dbbf30 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RecordIssueTileTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/RecordIssueTileTest.kt
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/screenrecord/data/model/ScreenRecordModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenrecord/data/model/ScreenRecordModelTest.kt
index 9331c8df46d4..0bbf47c2275c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/screenrecord/data/model/ScreenRecordModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenrecord/data/model/ScreenRecordModelTest.kt
@@ -16,13 +16,16 @@
package com.android.systemui.screenrecord.data.model
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.screenrecord.data.model.ScreenRecordModel.Starting.Companion.toCountdownSeconds
import com.google.common.truth.Truth.assertThat
+import org.junit.runner.RunWith
import kotlin.test.Test
@SmallTest
+@RunWith(AndroidJUnit4::class)
class ScreenRecordModelTest : SysuiTestCase() {
@Test
fun countdownSeconds_millis0_is0() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotDataTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/ScreenshotDataTest.kt
index 4ede90ec4466..4ede90ec4466 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotDataTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/ScreenshotDataTest.kt
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 0e9ef06aa1b9..0454317b5f04 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
@@ -22,6 +22,10 @@ import static com.android.systemui.log.LogBufferHelperKt.logcatLogBuffer;
import static com.google.common.truth.Truth.assertThat;
+import static kotlinx.coroutines.flow.FlowKt.emptyFlow;
+import static kotlinx.coroutines.flow.SharedFlowKt.MutableSharedFlow;
+import static kotlinx.coroutines.flow.StateFlowKt.MutableStateFlow;
+
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyFloat;
@@ -36,10 +40,6 @@ import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import static kotlinx.coroutines.flow.FlowKt.emptyFlow;
-import static kotlinx.coroutines.flow.SharedFlowKt.MutableSharedFlow;
-import static kotlinx.coroutines.flow.StateFlowKt.MutableStateFlow;
-
import android.animation.Animator;
import android.annotation.IdRes;
import android.content.ContentResolver;
@@ -201,6 +201,12 @@ import com.android.systemui.util.time.FakeSystemClock;
import com.android.systemui.util.time.SystemClock;
import com.android.wm.shell.animation.FlingAnimationUtils;
+import dagger.Lazy;
+
+import kotlinx.coroutines.CoroutineDispatcher;
+import kotlinx.coroutines.channels.BufferOverflow;
+import kotlinx.coroutines.test.TestScope;
+
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
@@ -215,11 +221,6 @@ import java.util.HashSet;
import java.util.List;
import java.util.Optional;
-import dagger.Lazy;
-import kotlinx.coroutines.CoroutineDispatcher;
-import kotlinx.coroutines.channels.BufferOverflow;
-import kotlinx.coroutines.test.TestScope;
-
public class NotificationPanelViewControllerBaseTest extends SysuiTestCase {
protected static final int SPLIT_SHADE_FULL_TRANSITION_DISTANCE = 400;
@@ -461,7 +462,8 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase {
() -> mKosmos.getSceneInteractor(),
() -> mKosmos.getSceneContainerOcclusionInteractor(),
() -> mKosmos.getKeyguardClockInteractor(),
- () -> mKosmos.getSceneBackInteractor());
+ () -> mKosmos.getSceneBackInteractor(),
+ () -> mKosmos.getAlternateBouncerInteractor());
KeyguardStatusView keyguardStatusView = new KeyguardStatusView(mContext);
keyguardStatusView.setId(R.id.keyguard_status_view);
@@ -622,7 +624,8 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase {
() -> mKosmos.getSceneInteractor(),
() -> mKosmos.getSceneContainerOcclusionInteractor(),
() -> mKosmos.getKeyguardClockInteractor(),
- () -> mKosmos.getSceneBackInteractor()),
+ () -> mKosmos.getSceneBackInteractor(),
+ () -> mKosmos.getAlternateBouncerInteractor()),
mKeyguardBypassController,
mDozeParameters,
mScreenOffAnimationController,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/OperatorNameViewControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/OperatorNameViewControllerTest.kt
index d6b3b919913f..d6b3b919913f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/OperatorNameViewControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/OperatorNameViewControllerTest.kt
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 db274cc311dd..f8720b4fe5f8 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
@@ -28,6 +28,8 @@ import com.android.internal.logging.testing.UiEventLoggerFake
import com.android.systemui.SysuiTestCase
import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
+import com.android.systemui.bouncer.domain.interactor.alternateBouncerInteractor
+import com.android.systemui.bouncer.domain.interactor.givenCanShowAlternateBouncer
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor
import com.android.systemui.flags.DisableSceneContainer
@@ -83,8 +85,9 @@ class StatusBarStateControllerImplTest(flags: FlagsParameterization) : SysuiTest
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
- private val sceneInteractor = kosmos.sceneInteractor
- private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
+ private val sceneInteractor by lazy { kosmos.sceneInteractor }
+ private val keyguardTransitionRepository by lazy { kosmos.fakeKeyguardTransitionRepository }
+ private val alternateBouncerInteractor by lazy { kosmos.alternateBouncerInteractor }
private val mockDarkAnimator = mock<ObjectAnimator>()
private lateinit var underTest: StatusBarStateControllerImpl
@@ -121,6 +124,7 @@ class StatusBarStateControllerImplTest(flags: FlagsParameterization) : SysuiTest
{ kosmos.sceneContainerOcclusionInteractor },
{ kosmos.keyguardClockInteractor },
{ kosmos.sceneBackInteractor },
+ { kosmos.alternateBouncerInteractor },
) {
override fun createDarkAnimator(): ObjectAnimator {
return mockDarkAnimator
@@ -299,6 +303,52 @@ class StatusBarStateControllerImplTest(flags: FlagsParameterization) : SysuiTest
@Test
@EnableSceneContainer
+ @DisableFlags(DualShade.FLAG_NAME)
+ fun start_hydratesStatusBarState_withAlternateBouncer() =
+ testScope.runTest {
+ var statusBarState = underTest.state
+ val listener =
+ object : StatusBarStateController.StateListener {
+ override fun onStateChanged(newState: Int) {
+ statusBarState = newState
+ }
+ }
+ underTest.addCallback(listener)
+
+ val currentScene by collectLastValue(sceneInteractor.currentScene)
+ val deviceUnlockStatus by
+ collectLastValue(kosmos.deviceUnlockedInteractor.deviceUnlockStatus)
+ val alternateBouncerIsVisible by collectLastValue(alternateBouncerInteractor.isVisible)
+
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+ AuthenticationMethodModel.Password
+ )
+ kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
+ SuccessFingerprintAuthenticationStatus(0, true)
+ )
+ runCurrent()
+ assertThat(deviceUnlockStatus!!.isUnlocked).isTrue()
+
+ sceneInteractor.changeScene(toScene = Scenes.Lockscreen, loggingReason = "reason")
+ runCurrent()
+ assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
+
+ kosmos.givenCanShowAlternateBouncer()
+ alternateBouncerInteractor.forceShow()
+ runCurrent()
+ assertThat(alternateBouncerIsVisible).isTrue()
+
+ // Call start to begin hydrating based on the scene framework:
+ underTest.start()
+
+ sceneInteractor.changeScene(toScene = Scenes.Gone, loggingReason = "reason")
+ runCurrent()
+ assertThat(currentScene).isEqualTo(Scenes.Gone)
+ assertThat(statusBarState).isEqualTo(StatusBarState.KEYGUARD)
+ }
+
+ @Test
+ @EnableSceneContainer
@EnableFlags(DualShade.FLAG_NAME)
fun start_hydratesStatusBarState_dualShade_whileLocked() =
testScope.runTest {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/call/domain/interactor/CallChipInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/call/domain/interactor/CallChipInteractorTest.kt
index 8f41caf54ec8..8f41caf54ec8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/call/domain/interactor/CallChipInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/call/domain/interactor/CallChipInteractorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelTest.kt
index 7bc6d4ae2816..7bc6d4ae2816 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/casttootherdevice/domian/interactor/MediaRouterChipInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/casttootherdevice/domian/interactor/MediaRouterChipInteractorTest.kt
index ecb1a6d44b22..ecb1a6d44b22 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/casttootherdevice/domian/interactor/MediaRouterChipInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/casttootherdevice/domian/interactor/MediaRouterChipInteractorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndCastScreenToOtherDeviceDialogDelegateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndCastScreenToOtherDeviceDialogDelegateTest.kt
index 5005d1609113..5005d1609113 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndCastScreenToOtherDeviceDialogDelegateTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndCastScreenToOtherDeviceDialogDelegateTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndGenericCastToOtherDeviceDialogDelegateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndGenericCastToOtherDeviceDialogDelegateTest.kt
index e6101f500ad1..e6101f500ad1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndGenericCastToOtherDeviceDialogDelegateTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/view/EndGenericCastToOtherDeviceDialogDelegateTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModelTest.kt
index 77992dbaecc2..77992dbaecc2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModelTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/mediaprojection/domain/interactor/MediaProjectionChipInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/mediaprojection/domain/interactor/MediaProjectionChipInteractorTest.kt
index d0c5e7a102e0..d0c5e7a102e0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/mediaprojection/domain/interactor/MediaProjectionChipInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/mediaprojection/domain/interactor/MediaProjectionChipInteractorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndMediaProjectionDialogHelperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndMediaProjectionDialogHelperTest.kt
index c62e40414121..c62e40414121 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndMediaProjectionDialogHelperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndMediaProjectionDialogHelperTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ron/demo/ui/viewmodel/DemoRonChipViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ron/demo/ui/viewmodel/DemoRonChipViewModelTest.kt
index 118dea6376bb..118dea6376bb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ron/demo/ui/viewmodel/DemoRonChipViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ron/demo/ui/viewmodel/DemoRonChipViewModelTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/screenrecord/domain/interactor/ScreenRecordChipInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/screenrecord/domain/interactor/ScreenRecordChipInteractorTest.kt
index 6bfb40fa17c5..6bfb40fa17c5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/screenrecord/domain/interactor/ScreenRecordChipInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/screenrecord/domain/interactor/ScreenRecordChipInteractorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/screenrecord/ui/view/EndScreenRecordingDialogDelegateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/screenrecord/ui/view/EndScreenRecordingDialogDelegateTest.kt
index bfb63ac66d3d..bfb63ac66d3d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/screenrecord/ui/view/EndScreenRecordingDialogDelegateTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/screenrecord/ui/view/EndScreenRecordingDialogDelegateTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModelTest.kt
index 16101bfe387c..16101bfe387c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModelTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/view/EndShareToAppDialogDelegateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/view/EndShareToAppDialogDelegateTest.kt
index 325a42bca7d1..325a42bca7d1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/view/EndShareToAppDialogDelegateTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/view/EndShareToAppDialogDelegateTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModelTest.kt
index 791a21d0fb63..791a21d0fb63 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModelTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/viewmodel/ChipTransitionHelperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/ChipTransitionHelperTest.kt
index 4977c548fb92..4977c548fb92 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/viewmodel/ChipTransitionHelperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/ChipTransitionHelperTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipViewModelTest.kt
index 6e4d8863fee2..6e4d8863fee2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipViewModelTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt
index 26ce7b956fde..26ce7b956fde 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithRonsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithRonsViewModelTest.kt
index 631120b39805..631120b39805 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithRonsViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithRonsViewModelTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/AboveShelfObserverTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/AboveShelfObserverTest.java
index 01a0fd020bda..01a0fd020bda 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/AboveShelfObserverTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/AboveShelfObserverTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationTransitionAnimatorControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationTransitionAnimatorControllerTest.kt
index cb92b7745961..cb92b7745961 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationTransitionAnimatorControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationTransitionAnimatorControllerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractorTest.kt
index d717fe4c1e04..d717fe4c1e04 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragControllerTest.java
index 1c5f37cc60c3..1c5f37cc60c3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragControllerTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationMenuRowTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationMenuRowTest.java
index 027e899e20df..027e899e20df 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationMenuRowTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationMenuRowTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImplTest.kt
index 48608ebd6de0..48608ebd6de0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImplTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationSnoozeTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationSnoozeTest.java
index 22f1e4604bbd..22f1e4604bbd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationSnoozeTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationSnoozeTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
index 2340d0289db4..2340d0289db4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationBigPictureTemplateViewWrapperTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationBigPictureTemplateViewWrapperTest.java
index ec280a1d6d01..ec280a1d6d01 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationBigPictureTemplateViewWrapperTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationBigPictureTemplateViewWrapperTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapperTest.kt
index 9d990b1d7edf..9d990b1d7edf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapperTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCustomViewWrapperTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCustomViewWrapperTest.java
index f9a9704334a0..f9a9704334a0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCustomViewWrapperTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCustomViewWrapperTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMessagingTemplateViewWrapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMessagingTemplateViewWrapperTest.kt
index fc829d53a6b4..fc829d53a6b4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMessagingTemplateViewWrapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMessagingTemplateViewWrapperTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapperTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapperTest.java
index d17c8dbcf38d..d17c8dbcf38d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapperTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapperTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java
index 14bbd38ece2c..14bbd38ece2c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationTargetsHelperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationTargetsHelperTest.kt
index 660eb308fdf3..660eb308fdf3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationTargetsHelperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationTargetsHelperTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java
index dd03ab393ce9..dd03ab393ce9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
index 68e17c1b2d73..68e17c1b2d73 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewTest.java
index 0932a0c9307c..0932a0c9307c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarTransitionsControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/LightBarTransitionsControllerTest.java
index 7dfdb9228936..7dfdb9228936 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarTransitionsControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/LightBarTransitionsControllerTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.kt
index 83600422bda4..83600422bda4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
index 1d74331e429b..1d74331e429b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialogTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialogTest.kt
index b560c591af1e..b560c591af1e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialogTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialogTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt
index 624c070e95e0..624c070e95e0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelImplTest.kt
index bd857807851c..bd857807851c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelImplTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/CastDeviceTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/CastDeviceTest.kt
index 16061df1fa89..16061df1fa89 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/CastDeviceTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/CastDeviceTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegateTest.kt
index 06b3b57bd133..06b3b57bd133 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegateTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegateTest.kt
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/BackGestureMonitorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/BackGestureMonitorTest.kt
index ba9fa926947c..cd18925eb44f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/BackGestureMonitorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/BackGestureMonitorTest.kt
@@ -25,6 +25,7 @@ import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState.InProgress
import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState.NotStarted
import com.android.systemui.touchpad.tutorial.ui.gesture.MultiFingerGesture.Companion.SWIPE_DISTANCE
import com.google.common.truth.Truth.assertThat
+import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -34,10 +35,12 @@ class BackGestureMonitorTest : SysuiTestCase() {
private var gestureState: GestureState = NotStarted
private val gestureMonitor =
- BackGestureMonitor(
- gestureDistanceThresholdPx = SWIPE_DISTANCE.toInt(),
- gestureStateChangedCallback = { gestureState = it },
- )
+ BackGestureMonitor(gestureDistanceThresholdPx = SWIPE_DISTANCE.toInt())
+
+ @Before
+ fun before() {
+ gestureMonitor.addGestureStateCallback { gestureState = it }
+ }
@Test
fun triggersGestureFinishedForThreeFingerGestureRight() {
@@ -82,7 +85,7 @@ class BackGestureMonitorTest : SysuiTestCase() {
}
private fun assertStateAfterEvents(events: List<MotionEvent>, expectedState: GestureState) {
- events.forEach { gestureMonitor.processTouchpadEvent(it) }
+ events.forEach { gestureMonitor.accept(it) }
assertThat(gestureState).isEqualTo(expectedState)
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/EasterEggGestureTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/EasterEggGestureTest.kt
index a83ed5649889..3f1633a8972f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/EasterEggGestureTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/EasterEggGestureTest.kt
@@ -36,10 +36,7 @@ class EasterEggGestureTest : SysuiTestCase() {
private var triggered = false
private val handler =
TouchpadGestureHandler(
- BackGestureMonitor(
- gestureDistanceThresholdPx = SWIPE_DISTANCE.toInt(),
- gestureStateChangedCallback = {},
- ),
+ BackGestureMonitor(gestureDistanceThresholdPx = SWIPE_DISTANCE.toInt()),
EasterEggGestureMonitor(callback = { triggered = true }),
)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/HomeGestureMonitorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/HomeGestureMonitorTest.kt
index 59cc026e82ee..edf0e5698bf0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/HomeGestureMonitorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/HomeGestureMonitorTest.kt
@@ -25,6 +25,7 @@ import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState.InProgress
import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState.NotStarted
import com.android.systemui.touchpad.tutorial.ui.gesture.MultiFingerGesture.Companion.SWIPE_DISTANCE
import com.google.common.truth.Truth.assertThat
+import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -34,10 +35,12 @@ class HomeGestureMonitorTest : SysuiTestCase() {
private var gestureState: GestureState = GestureState.NotStarted
private val gestureMonitor =
- HomeGestureMonitor(
- gestureDistanceThresholdPx = SWIPE_DISTANCE.toInt(),
- gestureStateChangedCallback = { gestureState = it },
- )
+ HomeGestureMonitor(gestureDistanceThresholdPx = SWIPE_DISTANCE.toInt())
+
+ @Before
+ fun before() {
+ gestureMonitor.addGestureStateCallback { gestureState = it }
+ }
@Test
fun triggersGestureFinishedForThreeFingerGestureUp() {
@@ -78,7 +81,7 @@ class HomeGestureMonitorTest : SysuiTestCase() {
}
private fun assertStateAfterEvents(events: List<MotionEvent>, expectedState: GestureState) {
- events.forEach { gestureMonitor.processTouchpadEvent(it) }
+ events.forEach { gestureMonitor.accept(it) }
assertThat(gestureState).isEqualTo(expectedState)
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/RecentAppsGestureMonitorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/RecentAppsGestureMonitorTest.kt
index 7eac6bb09264..f68e7732d04e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/RecentAppsGestureMonitorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/RecentAppsGestureMonitorTest.kt
@@ -26,6 +26,7 @@ import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState.InProgress
import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState.NotStarted
import com.android.systemui.touchpad.tutorial.ui.gesture.MultiFingerGesture.Companion.SWIPE_DISTANCE
import com.google.common.truth.Truth.assertThat
+import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.doReturn
@@ -44,7 +45,7 @@ class RecentAppsGestureMonitorTest : SysuiTestCase() {
}
private var gestureState: GestureState = GestureState.NotStarted
- private val velocityTracker =
+ private val velocityTracker1D =
mock<VelocityTracker1D> {
// by default return correct speed for the gesture - as if pointer is slowing down
on { calculateVelocity() } doReturn SLOW
@@ -52,11 +53,15 @@ class RecentAppsGestureMonitorTest : SysuiTestCase() {
private val gestureMonitor =
RecentAppsGestureMonitor(
gestureDistanceThresholdPx = SWIPE_DISTANCE.toInt(),
- gestureStateChangedCallback = { gestureState = it },
velocityThresholdPxPerMs = THRESHOLD_VELOCITY_PX_PER_MS,
- velocityTracker = velocityTracker,
+ velocityTracker = VerticalVelocityTracker(velocityTracker1D),
)
+ @Before
+ fun before() {
+ gestureMonitor.addGestureStateCallback { gestureState = it }
+ }
+
@Test
fun triggersGestureFinishedForThreeFingerGestureUp() {
assertStateAfterEvents(events = ThreeFingerGesture.swipeUp(), expectedState = Finished)
@@ -64,7 +69,7 @@ class RecentAppsGestureMonitorTest : SysuiTestCase() {
@Test
fun doesntTriggerGestureFinished_onGestureSpeedTooHigh() {
- whenever(velocityTracker.calculateVelocity()).thenReturn(FAST)
+ whenever(velocityTracker1D.calculateVelocity()).thenReturn(FAST)
assertStateAfterEvents(events = ThreeFingerGesture.swipeUp(), expectedState = NotStarted)
}
@@ -102,7 +107,7 @@ class RecentAppsGestureMonitorTest : SysuiTestCase() {
}
private fun assertStateAfterEvents(events: List<MotionEvent>, expectedState: GestureState) {
- events.forEach { gestureMonitor.processTouchpadEvent(it) }
+ events.forEach { gestureMonitor.accept(it) }
assertThat(gestureState).isEqualTo(expectedState)
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureHandlerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureHandlerTest.kt
index 4d26366614f6..9f7ea679b822 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureHandlerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureHandlerTest.kt
@@ -27,6 +27,7 @@ import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.touchpad.tutorial.ui.gesture.MultiFingerGesture.Companion.SWIPE_DISTANCE
import com.google.common.truth.Truth.assertThat
+import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -35,14 +36,14 @@ import org.junit.runner.RunWith
class TouchpadGestureHandlerTest : SysuiTestCase() {
private var gestureState: GestureState = GestureState.NotStarted
- private val handler =
- TouchpadGestureHandler(
- BackGestureMonitor(
- gestureDistanceThresholdPx = SWIPE_DISTANCE.toInt(),
- gestureStateChangedCallback = { gestureState = it },
- ),
- EasterEggGestureMonitor {},
- )
+ private val gestureMonitor =
+ BackGestureMonitor(gestureDistanceThresholdPx = SWIPE_DISTANCE.toInt())
+ private val handler = TouchpadGestureHandler(gestureMonitor, EasterEggGestureMonitor {})
+
+ @Before
+ fun before() {
+ gestureMonitor.addGestureStateCallback { gestureState = it }
+ }
@Test
fun handlesEventsFromTouchpad() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wallpapers/data/repository/WallpaperRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/wallpapers/data/repository/WallpaperRepositoryImplTest.kt
index b8dd334dcad9..b8dd334dcad9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wallpapers/data/repository/WallpaperRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/wallpapers/data/repository/WallpaperRepositoryImplTest.kt
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/VolumeDialogController.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/VolumeDialogController.java
index b1736b16875d..c09509d8690a 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/VolumeDialogController.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/VolumeDialogController.java
@@ -14,7 +14,6 @@
package com.android.systemui.plugins;
-import android.annotation.IntegerRes;
import android.content.ComponentName;
import android.media.AudioManager;
import android.media.AudioSystem;
@@ -22,6 +21,8 @@ import android.os.Handler;
import android.os.VibrationEffect;
import android.util.SparseArray;
+import androidx.annotation.StringRes;
+
import com.android.systemui.plugins.VolumeDialogController.Callbacks;
import com.android.systemui.plugins.VolumeDialogController.State;
import com.android.systemui.plugins.VolumeDialogController.StreamState;
@@ -90,7 +91,7 @@ public interface VolumeDialogController {
public int levelMax;
public boolean muted;
public boolean muteSupported;
- public @IntegerRes int name;
+ public @StringRes int name;
public String remoteLabel;
public boolean routedToBluetooth;
diff --git a/packages/SystemUI/res/drawable/volume_dialog_background.xml b/packages/SystemUI/res/drawable/volume_dialog_background.xml
new file mode 100644
index 000000000000..7d7498feeba6
--- /dev/null
+++ b/packages/SystemUI/res/drawable/volume_dialog_background.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ 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.
+-->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ android:shape="rectangle">
+ <corners android:radius="@dimen/volume_dialog_background_corner_radius" />
+ <solid android:color="?androidprv:attr/materialColorSurface" />
+</shape>
diff --git a/packages/SystemUI/res/drawable/volume_dialog_floating_slider_background.xml b/packages/SystemUI/res/drawable/volume_dialog_floating_slider_background.xml
new file mode 100644
index 000000000000..2694435bcc78
--- /dev/null
+++ b/packages/SystemUI/res/drawable/volume_dialog_floating_slider_background.xml
@@ -0,0 +1,21 @@
+<!--
+ ~ 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.
+ -->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ android:shape="rectangle">
+ <corners android:radius="20dp" />
+ <solid android:color="?androidprv:attr/colorSurface" />
+</shape>
diff --git a/packages/SystemUI/res/drawable/volume_dialog_floating_sliders_spacer.xml b/packages/SystemUI/res/drawable/volume_dialog_floating_sliders_spacer.xml
new file mode 100644
index 000000000000..66a205a57c61
--- /dev/null
+++ b/packages/SystemUI/res/drawable/volume_dialog_floating_sliders_spacer.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ 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.
+-->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+ <size
+ android:width="@dimen/volume_dialog_floating_sliders_spacing"
+ android:height="@dimen/volume_dialog_floating_sliders_spacing" />
+ <solid android:color="@color/transparent" />
+</shape>
diff --git a/packages/SystemUI/res/drawable/volume_dialog_spacer.xml b/packages/SystemUI/res/drawable/volume_dialog_spacer.xml
new file mode 100644
index 000000000000..3c60784cf6b6
--- /dev/null
+++ b/packages/SystemUI/res/drawable/volume_dialog_spacer.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ 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.
+-->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+ <size
+ android:width="@dimen/volume_dialog_spacing"
+ android:height="@dimen/volume_dialog_spacing" />
+ <solid android:color="@color/transparent" />
+</shape>
diff --git a/packages/SystemUI/res/drawable/volume_row_seekbar_progress.xml b/packages/SystemUI/res/drawable/volume_row_seekbar_progress.xml
index 21b177bead11..fa06bd6abeea 100644
--- a/packages/SystemUI/res/drawable/volume_row_seekbar_progress.xml
+++ b/packages/SystemUI/res/drawable/volume_row_seekbar_progress.xml
@@ -22,7 +22,7 @@
android:autoMirrored="true">
<item android:id="@+id/volume_seekbar_progress_solid">
<shape>
- <size android:height="@dimen/volume_dialog_slider_width" />
+ <size android:height="@dimen/volume_dialog_slider_width_legacy" />
<solid android:color="?android:attr/colorAccent" />
<corners android:radius="@dimen/volume_dialog_slider_corner_radius"/>
</shape>
diff --git a/packages/SystemUI/res/layout-land-television/volume_dialog.xml b/packages/SystemUI/res/layout-land-television/volume_dialog.xml
index 0fbc519ca8dd..f77db956a493 100644
--- a/packages/SystemUI/res/layout-land-television/volume_dialog.xml
+++ b/packages/SystemUI/res/layout-land-television/volume_dialog.xml
@@ -1,92 +1,67 @@
<!--
- ~ Copyright (C) 2020 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License
- -->
-<FrameLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:sysui="http://schemas.android.com/apk/res-auto"
+ 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.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
android:id="@+id/volume_dialog_container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:background="@android:color/transparent"
+ android:layout_gravity="right"
+ android:divider="@drawable/volume_dialog_floating_sliders_spacer"
+ android:orientation="horizontal"
+ android:showDividers="middle|end|beginning"
android:theme="@style/volume_dialog_theme">
- <FrameLayout
- android:id="@+id/volume_dialog"
+ <LinearLayout
+ android:id="@+id/volume_dialog_floating_sliders_container"
android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="right"
- android:background="@android:color/transparent"
- android:padding="@dimen/volume_dialog_panel_transparent_padding"
- android:clipToPadding="false">
-
- <LinearLayout
- android:id="@+id/volume_dialog_rows_container"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="right"
- android:orientation="vertical"
- android:translationZ="@dimen/volume_dialog_elevation"
- android:clipChildren="false"
- android:clipToPadding="false"
- android:background="@android:color/transparent">
+ android:layout_height="match_parent"
+ android:background="@drawable/volume_dialog_background"
+ android:divider="@drawable/volume_dialog_floating_sliders_spacer"
+ android:gravity="bottom"
+ android:orientation="horizontal"
+ android:paddingBottom="@dimen/volume_dialog_floating_sliders_bottom_padding"
+ android:showDividers="middle" />
- <LinearLayout
- android:id="@+id/volume_dialog_rows"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:gravity="center"
- android:orientation="horizontal"
- android:background="@drawable/tv_volume_dialog_background">
- <!-- volume rows added and removed here! :-) -->
- </LinearLayout>
-
- </LinearLayout>
+ <LinearLayout
+ android:layout_width="@dimen/volume_dialog_width"
+ android:layout_height="wrap_content"
+ android:background="@drawable/volume_dialog_background"
+ android:divider="@drawable/volume_dialog_spacer"
+ android:gravity="center_horizontal"
+ android:orientation="vertical"
+ android:paddingVertical="@dimen/volume_dialog_vertical_padding"
+ android:showDividers="middle">
<FrameLayout
- android:id="@+id/odi_captions"
- android:layout_width="@dimen/volume_dialog_caption_size"
- android:layout_height="@dimen/volume_dialog_caption_size"
- android:layout_marginRight="68dp"
- android:layout_gravity="right"
- android:clipToPadding="false"
- android:translationZ="@dimen/volume_dialog_elevation"
- android:background="@drawable/rounded_bg_full">
-
- <com.android.systemui.volume.CaptionsToggleImageButton
- android:id="@+id/odi_captions_icon"
- android:src="@drawable/ic_volume_odi_captions_disabled"
- style="@style/VolumeButtons"
- android:background="@drawable/rounded_ripple"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:tint="@color/caption_tint_color_selector"
- android:layout_gravity="center"
- android:soundEffectsEnabled="false"/>
-
- </FrameLayout>
-
- <ViewStub
- android:id="@+id/odi_captions_tooltip_stub"
- android:inflatedId="@+id/odi_captions_tooltip_view"
- android:layout="@layout/volume_tool_tip_view"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginRight="@dimen/volume_tool_tip_right_margin"
- android:layout_marginTop="@dimen/volume_tool_tip_top_margin"
- android:layout_gravity="right"/>
+ android:id="@+id/volume_dialog_ringer_button"
+ android:layout_width="@dimen/volume_dialog_button_size"
+ android:layout_height="@dimen/volume_dialog_button_size" />
- </FrameLayout>
+ <include
+ android:id="@+id/volume_dialog_slider"
+ layout="@layout/volume_dialog_slider" />
-</FrameLayout>
+ <Button
+ android:id="@+id/volume_dialog_settings"
+ android:layout_width="@dimen/volume_dialog_button_size"
+ android:layout_height="@dimen/volume_dialog_button_size"
+ android:background="@drawable/ripple_drawable_20dp"
+ android:contentDescription="@string/accessibility_volume_settings"
+ android:soundEffectsEnabled="false"
+ android:src="@drawable/horizontal_ellipsis"
+ android:tint="?androidprv:attr/materialColorPrimary" />
+ </LinearLayout>
+</LinearLayout> \ No newline at end of file
diff --git a/packages/SystemUI/res/layout-land-television/volume_dialog_legacy.xml b/packages/SystemUI/res/layout-land-television/volume_dialog_legacy.xml
new file mode 100644
index 000000000000..0fbc519ca8dd
--- /dev/null
+++ b/packages/SystemUI/res/layout-land-television/volume_dialog_legacy.xml
@@ -0,0 +1,92 @@
+<!--
+ ~ Copyright (C) 2020 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:sysui="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/volume_dialog_container"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:background="@android:color/transparent"
+ android:theme="@style/volume_dialog_theme">
+
+ <FrameLayout
+ android:id="@+id/volume_dialog"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="right"
+ android:background="@android:color/transparent"
+ android:padding="@dimen/volume_dialog_panel_transparent_padding"
+ android:clipToPadding="false">
+
+ <LinearLayout
+ android:id="@+id/volume_dialog_rows_container"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="right"
+ android:orientation="vertical"
+ android:translationZ="@dimen/volume_dialog_elevation"
+ android:clipChildren="false"
+ android:clipToPadding="false"
+ android:background="@android:color/transparent">
+
+ <LinearLayout
+ android:id="@+id/volume_dialog_rows"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:gravity="center"
+ android:orientation="horizontal"
+ android:background="@drawable/tv_volume_dialog_background">
+ <!-- volume rows added and removed here! :-) -->
+ </LinearLayout>
+
+ </LinearLayout>
+
+ <FrameLayout
+ android:id="@+id/odi_captions"
+ android:layout_width="@dimen/volume_dialog_caption_size"
+ android:layout_height="@dimen/volume_dialog_caption_size"
+ android:layout_marginRight="68dp"
+ android:layout_gravity="right"
+ android:clipToPadding="false"
+ android:translationZ="@dimen/volume_dialog_elevation"
+ android:background="@drawable/rounded_bg_full">
+
+ <com.android.systemui.volume.CaptionsToggleImageButton
+ android:id="@+id/odi_captions_icon"
+ android:src="@drawable/ic_volume_odi_captions_disabled"
+ style="@style/VolumeButtons"
+ android:background="@drawable/rounded_ripple"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:tint="@color/caption_tint_color_selector"
+ android:layout_gravity="center"
+ android:soundEffectsEnabled="false"/>
+
+ </FrameLayout>
+
+ <ViewStub
+ android:id="@+id/odi_captions_tooltip_stub"
+ android:inflatedId="@+id/odi_captions_tooltip_view"
+ android:layout="@layout/volume_tool_tip_view"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginRight="@dimen/volume_tool_tip_right_margin"
+ android:layout_marginTop="@dimen/volume_tool_tip_top_margin"
+ android:layout_gravity="right"/>
+
+ </FrameLayout>
+
+</FrameLayout>
diff --git a/packages/SystemUI/res/layout-land-television/volume_dialog_row.xml b/packages/SystemUI/res/layout-land-television/volume_dialog_row.xml
index cf301c96a9f8..eb89489bdd83 100644
--- a/packages/SystemUI/res/layout-land-television/volume_dialog_row.xml
+++ b/packages/SystemUI/res/layout-land-television/volume_dialog_row.xml
@@ -63,8 +63,8 @@
android:layout_height="match_parent"
android:layout_gravity="center"
android:layoutDirection="ltr"
- android:maxHeight="@dimen/volume_dialog_slider_width"
- android:minHeight="@dimen/volume_dialog_slider_width"
+ android:maxHeight="@dimen/volume_dialog_slider_width_legacy"
+ android:minHeight="@dimen/volume_dialog_slider_width_legacy"
android:progressDrawable="@drawable/volume_row_seekbar"
android:thumb="@drawable/tv_volume_row_seek_thumb"
android:splitTrack="false"
diff --git a/packages/SystemUI/res/layout-land/volume_dialog.xml b/packages/SystemUI/res/layout-land/volume_dialog.xml
index 08edf59000b8..f77db956a493 100644
--- a/packages/SystemUI/res/layout-land/volume_dialog.xml
+++ b/packages/SystemUI/res/layout-land/volume_dialog.xml
@@ -1,146 +1,67 @@
<!--
- ~ Copyright (C) 2019 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License
- -->
-<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ 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.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
android:id="@+id/volume_dialog_container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:gravity="right"
android:layout_gravity="right"
- android:background="@android:color/transparent"
+ android:divider="@drawable/volume_dialog_floating_sliders_spacer"
+ android:orientation="horizontal"
+ android:showDividers="middle|end|beginning"
android:theme="@style/volume_dialog_theme">
- <!-- right-aligned to be physically near volume button -->
<LinearLayout
- android:id="@+id/volume_dialog"
+ android:id="@+id/volume_dialog_floating_sliders_container"
android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:background="@drawable/volume_dialog_background"
+ android:divider="@drawable/volume_dialog_floating_sliders_spacer"
+ android:gravity="bottom"
+ android:orientation="horizontal"
+ android:paddingBottom="@dimen/volume_dialog_floating_sliders_bottom_padding"
+ android:showDividers="middle" />
+
+ <LinearLayout
+ android:layout_width="@dimen/volume_dialog_width"
android:layout_height="wrap_content"
- android:gravity="right"
- android:layout_gravity="right"
- android:layout_marginRight="@dimen/volume_dialog_panel_transparent_padding_right"
+ android:background="@drawable/volume_dialog_background"
+ android:divider="@drawable/volume_dialog_spacer"
+ android:gravity="center_horizontal"
android:orientation="vertical"
- android:clipToPadding="false"
- android:clipChildren="false">
-
-
- <LinearLayout
- android:id="@+id/volume_dialog_top_container"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:orientation="vertical"
- android:clipChildren="false"
- android:gravity="right">
-
- <include layout="@layout/volume_ringer_drawer" />
-
- <FrameLayout
- android:visibility="gone"
- android:id="@+id/ringer"
- android:layout_width="@dimen/volume_dialog_ringer_size"
- android:layout_height="@dimen/volume_dialog_ringer_size"
- android:layout_marginBottom="@dimen/volume_dialog_spacer"
- android:gravity="right"
- android:layout_gravity="right"
- android:translationZ="@dimen/volume_dialog_elevation"
- android:clipToPadding="false"
- android:background="@drawable/rounded_bg_full">
- <com.android.keyguard.AlphaOptimizedImageButton
- android:id="@+id/ringer_icon"
- style="@style/VolumeButtons"
- android:background="@drawable/rounded_ripple"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:scaleType="fitCenter"
- android:padding="@dimen/volume_dialog_ringer_icon_padding"
- android:tint="?android:attr/textColorPrimary"
- android:layout_gravity="center"
- android:soundEffectsEnabled="false" />
- </FrameLayout>
-
- <LinearLayout
- android:id="@+id/volume_dialog_rows_container"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:gravity="right"
- android:layout_gravity="right"
- android:orientation="vertical"
- android:clipChildren="false"
- android:clipToPadding="false" >
- <LinearLayout
- android:id="@+id/volume_dialog_rows"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:gravity="center"
- android:orientation="horizontal">
- <!-- volume rows added and removed here! :-) -->
- </LinearLayout>
- <FrameLayout
- android:id="@+id/settings_container"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:background="@drawable/volume_background_bottom"
- android:paddingLeft="@dimen/volume_dialog_ringer_rows_padding"
- android:paddingBottom="@dimen/volume_dialog_ringer_rows_padding"
- android:paddingRight="@dimen/volume_dialog_ringer_rows_padding">
-
- <com.android.keyguard.AlphaOptimizedImageButton
- android:id="@+id/settings"
- android:layout_width="@dimen/volume_dialog_tap_target_size"
- android:layout_height="@dimen/volume_dialog_tap_target_size"
- android:layout_gravity="center"
- android:background="@drawable/ripple_drawable_20dp"
- android:contentDescription="@string/accessibility_volume_settings"
- android:scaleType="centerInside"
- android:soundEffectsEnabled="false"
- android:src="@drawable/horizontal_ellipsis"
- android:tint="?androidprv:attr/colorAccent" />
- </FrameLayout>
- </LinearLayout>
-
- </LinearLayout>
+ android:paddingVertical="@dimen/volume_dialog_vertical_padding"
+ android:showDividers="middle">
<FrameLayout
- android:id="@+id/odi_captions"
- android:layout_width="@dimen/volume_dialog_caption_size"
- android:layout_height="@dimen/volume_dialog_caption_size"
- android:layout_marginTop="@dimen/volume_dialog_row_margin_bottom"
- android:gravity="right"
- android:layout_gravity="right"
- android:clipToPadding="false"
- android:clipToOutline="true"
- android:background="@drawable/volume_row_rounded_background">
- <com.android.systemui.volume.CaptionsToggleImageButton
- android:id="@+id/odi_captions_icon"
- android:src="@drawable/ic_volume_odi_captions_disabled"
- style="@style/VolumeButtons"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:tint="?android:attr/colorAccent"
- android:layout_gravity="center"
- android:soundEffectsEnabled="false" />
- </FrameLayout>
- </LinearLayout>
+ android:id="@+id/volume_dialog_ringer_button"
+ android:layout_width="@dimen/volume_dialog_button_size"
+ android:layout_height="@dimen/volume_dialog_button_size" />
- <ViewStub
- android:id="@+id/odi_captions_tooltip_stub"
- android:inflatedId="@+id/odi_captions_tooltip_view"
- android:layout="@layout/volume_tool_tip_view"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="bottom | right"
- android:layout_marginRight="@dimen/volume_tool_tip_right_margin"/>
+ <include
+ android:id="@+id/volume_dialog_slider"
+ layout="@layout/volume_dialog_slider" />
-</FrameLayout> \ No newline at end of file
+ <Button
+ android:id="@+id/volume_dialog_settings"
+ android:layout_width="@dimen/volume_dialog_button_size"
+ android:layout_height="@dimen/volume_dialog_button_size"
+ android:background="@drawable/ripple_drawable_20dp"
+ android:contentDescription="@string/accessibility_volume_settings"
+ android:soundEffectsEnabled="false"
+ android:src="@drawable/horizontal_ellipsis"
+ android:tint="?androidprv:attr/materialColorPrimary" />
+ </LinearLayout>
+</LinearLayout> \ No newline at end of file
diff --git a/packages/SystemUI/res/layout-land/volume_dialog_legacy.xml b/packages/SystemUI/res/layout-land/volume_dialog_legacy.xml
new file mode 100644
index 000000000000..08edf59000b8
--- /dev/null
+++ b/packages/SystemUI/res/layout-land/volume_dialog_legacy.xml
@@ -0,0 +1,146 @@
+<!--
+ ~ Copyright (C) 2019 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ android:id="@+id/volume_dialog_container"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:gravity="right"
+ android:layout_gravity="right"
+ android:background="@android:color/transparent"
+ android:theme="@style/volume_dialog_theme">
+
+ <!-- right-aligned to be physically near volume button -->
+ <LinearLayout
+ android:id="@+id/volume_dialog"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:gravity="right"
+ android:layout_gravity="right"
+ android:layout_marginRight="@dimen/volume_dialog_panel_transparent_padding_right"
+ android:orientation="vertical"
+ android:clipToPadding="false"
+ android:clipChildren="false">
+
+
+ <LinearLayout
+ android:id="@+id/volume_dialog_top_container"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:clipChildren="false"
+ android:gravity="right">
+
+ <include layout="@layout/volume_ringer_drawer" />
+
+ <FrameLayout
+ android:visibility="gone"
+ android:id="@+id/ringer"
+ android:layout_width="@dimen/volume_dialog_ringer_size"
+ android:layout_height="@dimen/volume_dialog_ringer_size"
+ android:layout_marginBottom="@dimen/volume_dialog_spacer"
+ android:gravity="right"
+ android:layout_gravity="right"
+ android:translationZ="@dimen/volume_dialog_elevation"
+ android:clipToPadding="false"
+ android:background="@drawable/rounded_bg_full">
+ <com.android.keyguard.AlphaOptimizedImageButton
+ android:id="@+id/ringer_icon"
+ style="@style/VolumeButtons"
+ android:background="@drawable/rounded_ripple"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:scaleType="fitCenter"
+ android:padding="@dimen/volume_dialog_ringer_icon_padding"
+ android:tint="?android:attr/textColorPrimary"
+ android:layout_gravity="center"
+ android:soundEffectsEnabled="false" />
+ </FrameLayout>
+
+ <LinearLayout
+ android:id="@+id/volume_dialog_rows_container"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:gravity="right"
+ android:layout_gravity="right"
+ android:orientation="vertical"
+ android:clipChildren="false"
+ android:clipToPadding="false" >
+ <LinearLayout
+ android:id="@+id/volume_dialog_rows"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:gravity="center"
+ android:orientation="horizontal">
+ <!-- volume rows added and removed here! :-) -->
+ </LinearLayout>
+ <FrameLayout
+ android:id="@+id/settings_container"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:background="@drawable/volume_background_bottom"
+ android:paddingLeft="@dimen/volume_dialog_ringer_rows_padding"
+ android:paddingBottom="@dimen/volume_dialog_ringer_rows_padding"
+ android:paddingRight="@dimen/volume_dialog_ringer_rows_padding">
+
+ <com.android.keyguard.AlphaOptimizedImageButton
+ android:id="@+id/settings"
+ android:layout_width="@dimen/volume_dialog_tap_target_size"
+ android:layout_height="@dimen/volume_dialog_tap_target_size"
+ android:layout_gravity="center"
+ android:background="@drawable/ripple_drawable_20dp"
+ android:contentDescription="@string/accessibility_volume_settings"
+ android:scaleType="centerInside"
+ android:soundEffectsEnabled="false"
+ android:src="@drawable/horizontal_ellipsis"
+ android:tint="?androidprv:attr/colorAccent" />
+ </FrameLayout>
+ </LinearLayout>
+
+ </LinearLayout>
+
+ <FrameLayout
+ android:id="@+id/odi_captions"
+ android:layout_width="@dimen/volume_dialog_caption_size"
+ android:layout_height="@dimen/volume_dialog_caption_size"
+ android:layout_marginTop="@dimen/volume_dialog_row_margin_bottom"
+ android:gravity="right"
+ android:layout_gravity="right"
+ android:clipToPadding="false"
+ android:clipToOutline="true"
+ android:background="@drawable/volume_row_rounded_background">
+ <com.android.systemui.volume.CaptionsToggleImageButton
+ android:id="@+id/odi_captions_icon"
+ android:src="@drawable/ic_volume_odi_captions_disabled"
+ style="@style/VolumeButtons"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:tint="?android:attr/colorAccent"
+ android:layout_gravity="center"
+ android:soundEffectsEnabled="false" />
+ </FrameLayout>
+ </LinearLayout>
+
+ <ViewStub
+ android:id="@+id/odi_captions_tooltip_stub"
+ android:inflatedId="@+id/odi_captions_tooltip_view"
+ android:layout="@layout/volume_tool_tip_view"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="bottom | right"
+ android:layout_marginRight="@dimen/volume_tool_tip_right_margin"/>
+
+</FrameLayout> \ No newline at end of file
diff --git a/packages/SystemUI/res/layout/volume_dialog.xml b/packages/SystemUI/res/layout/volume_dialog.xml
index 39a1f1f9b85d..f77db956a493 100644
--- a/packages/SystemUI/res/layout/volume_dialog.xml
+++ b/packages/SystemUI/res/layout/volume_dialog.xml
@@ -1,5 +1,5 @@
<!--
- Copyright (C) 2015 The Android Open Source Project
+ Copyright (C) 2024 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -13,133 +13,55 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<FrameLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:sysui="http://schemas.android.com/apk/res-auto"
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
android:id="@+id/volume_dialog_container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:gravity="right"
android:layout_gravity="right"
- android:clipToPadding="false"
+ android:divider="@drawable/volume_dialog_floating_sliders_spacer"
+ android:orientation="horizontal"
+ android:showDividers="middle|end|beginning"
android:theme="@style/volume_dialog_theme">
- <!-- right-aligned to be physically near volume button -->
<LinearLayout
- android:id="@+id/volume_dialog"
+ android:id="@+id/volume_dialog_floating_sliders_container"
android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:background="@drawable/volume_dialog_background"
+ android:divider="@drawable/volume_dialog_floating_sliders_spacer"
+ android:gravity="bottom"
+ android:orientation="horizontal"
+ android:paddingBottom="@dimen/volume_dialog_floating_sliders_bottom_padding"
+ android:showDividers="middle" />
+
+ <LinearLayout
+ android:layout_width="@dimen/volume_dialog_width"
android:layout_height="wrap_content"
- android:gravity="right"
- android:layout_gravity="right"
- android:layout_marginRight="@dimen/volume_dialog_panel_transparent_padding_right"
+ android:background="@drawable/volume_dialog_background"
+ android:divider="@drawable/volume_dialog_spacer"
+ android:gravity="center_horizontal"
android:orientation="vertical"
- android:clipToPadding="false"
- android:clipChildren="false">
-
- <LinearLayout
- android:id="@+id/volume_dialog_top_container"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:clipChildren="false"
- android:orientation="vertical"
- android:gravity="right">
-
- <include layout="@layout/volume_ringer_drawer" />
-
- <FrameLayout
- android:visibility="gone"
- android:id="@+id/ringer"
- android:layout_width="@dimen/volume_dialog_ringer_size"
- android:layout_height="@dimen/volume_dialog_ringer_size"
- android:layout_marginBottom="@dimen/volume_dialog_spacer"
- android:gravity="right"
- android:layout_gravity="right"
- android:translationZ="@dimen/volume_dialog_elevation"
- android:clipToPadding="false"
- android:background="@drawable/rounded_bg_full">
- <com.android.keyguard.AlphaOptimizedImageButton
- android:id="@+id/ringer_icon"
- style="@style/VolumeButtons"
- android:background="@drawable/rounded_ripple"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:scaleType="fitCenter"
- android:padding="@dimen/volume_dialog_ringer_icon_padding"
- android:tint="?android:attr/textColorPrimary"
- android:layout_gravity="center"
- android:soundEffectsEnabled="false" />
- </FrameLayout>
-
- <LinearLayout
- android:id="@+id/volume_dialog_rows_container"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:gravity="right"
- android:layout_gravity="right"
- android:orientation="vertical"
- android:clipChildren="false"
- android:clipToPadding="false" >
- <LinearLayout
- android:id="@+id/volume_dialog_rows"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:gravity="center"
- android:orientation="horizontal">
- <!-- volume rows added and removed here! :-) -->
- </LinearLayout>
- <FrameLayout
- android:id="@+id/settings_container"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:background="@drawable/volume_background_bottom"
- android:paddingLeft="@dimen/volume_dialog_ringer_rows_padding"
- android:paddingBottom="@dimen/volume_dialog_ringer_rows_padding"
- android:paddingRight="@dimen/volume_dialog_ringer_rows_padding">
- <com.android.keyguard.AlphaOptimizedImageButton
- android:id="@+id/settings"
- android:src="@drawable/horizontal_ellipsis"
- android:layout_width="@dimen/volume_dialog_tap_target_size"
- android:layout_height="@dimen/volume_dialog_tap_target_size"
- android:layout_gravity="center"
- android:contentDescription="@string/accessibility_volume_settings"
- android:background="@drawable/ripple_drawable_20dp"
- android:tint="?androidprv:attr/colorAccent"
- android:soundEffectsEnabled="false" />
- </FrameLayout>
- </LinearLayout>
-
- </LinearLayout>
+ android:paddingVertical="@dimen/volume_dialog_vertical_padding"
+ android:showDividers="middle">
<FrameLayout
- android:id="@+id/odi_captions"
- android:layout_width="@dimen/volume_dialog_caption_size"
- android:layout_height="@dimen/volume_dialog_caption_size"
- android:layout_marginTop="@dimen/volume_dialog_row_margin_bottom"
- android:gravity="right"
- android:layout_gravity="right"
- android:clipToPadding="false"
- android:clipToOutline="true"
- android:background="@drawable/volume_row_rounded_background">
- <com.android.systemui.volume.CaptionsToggleImageButton
- android:id="@+id/odi_captions_icon"
- android:src="@drawable/ic_volume_odi_captions_disabled"
- style="@style/VolumeButtons"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:tint="?android:attr/colorAccent"
- android:layout_gravity="center"
- android:soundEffectsEnabled="false"/>
- </FrameLayout>
- </LinearLayout>
+ android:id="@+id/volume_dialog_ringer_button"
+ android:layout_width="@dimen/volume_dialog_button_size"
+ android:layout_height="@dimen/volume_dialog_button_size" />
- <ViewStub
- android:id="@+id/odi_captions_tooltip_stub"
- android:inflatedId="@+id/odi_captions_tooltip_view"
- android:layout="@layout/volume_tool_tip_view"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="bottom | right"
- android:layout_marginRight="@dimen/volume_tool_tip_right_margin"/>
+ <include
+ android:id="@+id/volume_dialog_slider"
+ layout="@layout/volume_dialog_slider" />
-</FrameLayout> \ No newline at end of file
+ <Button
+ android:id="@+id/volume_dialog_settings"
+ android:layout_width="@dimen/volume_dialog_button_size"
+ android:layout_height="@dimen/volume_dialog_button_size"
+ android:background="@drawable/ripple_drawable_20dp"
+ android:contentDescription="@string/accessibility_volume_settings"
+ android:soundEffectsEnabled="false"
+ android:src="@drawable/horizontal_ellipsis"
+ android:tint="?androidprv:attr/materialColorPrimary" />
+ </LinearLayout>
+</LinearLayout> \ No newline at end of file
diff --git a/packages/SystemUI/res/layout/volume_dialog_legacy.xml b/packages/SystemUI/res/layout/volume_dialog_legacy.xml
new file mode 100644
index 000000000000..39a1f1f9b85d
--- /dev/null
+++ b/packages/SystemUI/res/layout/volume_dialog_legacy.xml
@@ -0,0 +1,145 @@
+<!--
+ Copyright (C) 2015 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"
+ xmlns:sysui="http://schemas.android.com/apk/res-auto"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ android:id="@+id/volume_dialog_container"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:gravity="right"
+ android:layout_gravity="right"
+ android:clipToPadding="false"
+ android:theme="@style/volume_dialog_theme">
+
+ <!-- right-aligned to be physically near volume button -->
+ <LinearLayout
+ android:id="@+id/volume_dialog"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:gravity="right"
+ android:layout_gravity="right"
+ android:layout_marginRight="@dimen/volume_dialog_panel_transparent_padding_right"
+ android:orientation="vertical"
+ android:clipToPadding="false"
+ android:clipChildren="false">
+
+ <LinearLayout
+ android:id="@+id/volume_dialog_top_container"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:clipChildren="false"
+ android:orientation="vertical"
+ android:gravity="right">
+
+ <include layout="@layout/volume_ringer_drawer" />
+
+ <FrameLayout
+ android:visibility="gone"
+ android:id="@+id/ringer"
+ android:layout_width="@dimen/volume_dialog_ringer_size"
+ android:layout_height="@dimen/volume_dialog_ringer_size"
+ android:layout_marginBottom="@dimen/volume_dialog_spacer"
+ android:gravity="right"
+ android:layout_gravity="right"
+ android:translationZ="@dimen/volume_dialog_elevation"
+ android:clipToPadding="false"
+ android:background="@drawable/rounded_bg_full">
+ <com.android.keyguard.AlphaOptimizedImageButton
+ android:id="@+id/ringer_icon"
+ style="@style/VolumeButtons"
+ android:background="@drawable/rounded_ripple"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:scaleType="fitCenter"
+ android:padding="@dimen/volume_dialog_ringer_icon_padding"
+ android:tint="?android:attr/textColorPrimary"
+ android:layout_gravity="center"
+ android:soundEffectsEnabled="false" />
+ </FrameLayout>
+
+ <LinearLayout
+ android:id="@+id/volume_dialog_rows_container"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:gravity="right"
+ android:layout_gravity="right"
+ android:orientation="vertical"
+ android:clipChildren="false"
+ android:clipToPadding="false" >
+ <LinearLayout
+ android:id="@+id/volume_dialog_rows"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:gravity="center"
+ android:orientation="horizontal">
+ <!-- volume rows added and removed here! :-) -->
+ </LinearLayout>
+ <FrameLayout
+ android:id="@+id/settings_container"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:background="@drawable/volume_background_bottom"
+ android:paddingLeft="@dimen/volume_dialog_ringer_rows_padding"
+ android:paddingBottom="@dimen/volume_dialog_ringer_rows_padding"
+ android:paddingRight="@dimen/volume_dialog_ringer_rows_padding">
+ <com.android.keyguard.AlphaOptimizedImageButton
+ android:id="@+id/settings"
+ android:src="@drawable/horizontal_ellipsis"
+ android:layout_width="@dimen/volume_dialog_tap_target_size"
+ android:layout_height="@dimen/volume_dialog_tap_target_size"
+ android:layout_gravity="center"
+ android:contentDescription="@string/accessibility_volume_settings"
+ android:background="@drawable/ripple_drawable_20dp"
+ android:tint="?androidprv:attr/colorAccent"
+ android:soundEffectsEnabled="false" />
+ </FrameLayout>
+ </LinearLayout>
+
+ </LinearLayout>
+
+ <FrameLayout
+ android:id="@+id/odi_captions"
+ android:layout_width="@dimen/volume_dialog_caption_size"
+ android:layout_height="@dimen/volume_dialog_caption_size"
+ android:layout_marginTop="@dimen/volume_dialog_row_margin_bottom"
+ android:gravity="right"
+ android:layout_gravity="right"
+ android:clipToPadding="false"
+ android:clipToOutline="true"
+ android:background="@drawable/volume_row_rounded_background">
+ <com.android.systemui.volume.CaptionsToggleImageButton
+ android:id="@+id/odi_captions_icon"
+ android:src="@drawable/ic_volume_odi_captions_disabled"
+ style="@style/VolumeButtons"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:tint="?android:attr/colorAccent"
+ android:layout_gravity="center"
+ android:soundEffectsEnabled="false"/>
+ </FrameLayout>
+ </LinearLayout>
+
+ <ViewStub
+ android:id="@+id/odi_captions_tooltip_stub"
+ android:inflatedId="@+id/odi_captions_tooltip_view"
+ android:layout="@layout/volume_tool_tip_view"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="bottom | right"
+ android:layout_marginRight="@dimen/volume_tool_tip_right_margin"/>
+
+</FrameLayout> \ No newline at end of file
diff --git a/packages/SystemUI/res/layout/volume_dialog_slider.xml b/packages/SystemUI/res/layout/volume_dialog_slider.xml
new file mode 100644
index 000000000000..8acdd39faaa2
--- /dev/null
+++ b/packages/SystemUI/res/layout/volume_dialog_slider.xml
@@ -0,0 +1,27 @@
+<!--
+ 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.
+-->
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="@dimen/volume_dialog_slider_width"
+ android:layout_height="@dimen/volume_dialog_slider_height">
+
+ <com.google.android.material.slider.Slider
+ android:id="@+id/volume_dialog_slider"
+ android:layout_width="@dimen/volume_dialog_slider_height"
+ android:layout_height="match_parent"
+ android:layout_gravity="center"
+ android:rotation="270"
+ android:theme="@style/Theme.MaterialComponents.DayNight" />
+</FrameLayout>
diff --git a/packages/SystemUI/res/layout/volume_dialog_slider_floating.xml b/packages/SystemUI/res/layout/volume_dialog_slider_floating.xml
new file mode 100644
index 000000000000..db800aa4a873
--- /dev/null
+++ b/packages/SystemUI/res/layout/volume_dialog_slider_floating.xml
@@ -0,0 +1,24 @@
+<!--
+ 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.
+-->
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:background="@drawable/volume_dialog_floating_slider_background"
+ android:paddingHorizontal="@dimen/volume_dialog_floating_sliders_horizontal_padding"
+ android:paddingVertical="@dimen/volume_dialog_floating_sliders_vertical_padding">
+
+ <include layout="@layout/volume_dialog_slider" />
+</FrameLayout> \ No newline at end of file
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 1727a5fec0a2..6c8a7403953e 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -591,7 +591,7 @@
<dimen name="volume_dialog_panel_width_half">28dp</dimen>
- <dimen name="volume_dialog_slider_width">42dp</dimen>
+ <dimen name="volume_dialog_slider_width_legacy">42dp</dimen>
<dimen name="volume_dialog_slider_corner_radius">21dp</dimen>
@@ -622,10 +622,6 @@
<dimen name="volume_tool_tip_arrow_corner_radius">2dp</dimen>
- <!-- Volume panel slices dimensions -->
- <dimen name="volume_panel_slice_vertical_padding">8dp</dimen>
- <dimen name="volume_panel_slice_horizontal_padding">24dp</dimen>
-
<dimen name="bottom_sheet_corner_radius">28dp</dimen>
<!-- Size of each item in the ringer selector drawer. -->
@@ -2050,4 +2046,22 @@
<dimen name="contextual_edu_dialog_bottom_margin">80dp</dimen>
<dimen name="contextual_edu_dialog_elevation">2dp</dimen>
+
+ <!-- Volume start -->
+ <dimen name="volume_dialog_background_corner_radius">30dp</dimen>
+ <dimen name="volume_dialog_width">60dp</dimen>
+ <dimen name="volume_dialog_vertical_padding">6dp</dimen>
+ <dimen name="volume_dialog_components_spacing">8dp</dimen>
+ <dimen name="volume_dialog_floating_sliders_spacing">8dp</dimen>
+ <dimen name="volume_dialog_floating_sliders_vertical_padding">10dp</dimen>
+ <dimen name="volume_dialog_floating_sliders_horizontal_padding">4dp</dimen>
+ <dimen name="volume_dialog_spacing">4dp</dimen>
+ <dimen name="volume_dialog_button_size">48dp</dimen>
+ <dimen name="volume_dialog_floating_sliders_bottom_padding">48dp</dimen>
+ <dimen name="volume_dialog_slider_width">52dp</dimen>
+ <dimen name="volume_dialog_slider_height">254dp</dimen>
+
+ <dimen name="volume_panel_slice_vertical_padding">8dp</dimen>
+ <dimen name="volume_panel_slice_horizontal_padding">24dp</dimen>
+ <!-- Volume end -->
</resources>
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 431f04882c8d..83ab5245bc31 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -1875,7 +1875,9 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
if (posture == DEVICE_POSTURE_OPENED) {
mLogger.d("Posture changed to open - attempting to request active"
+ " unlock and run face auth");
- getFaceAuthInteractor().onDeviceUnfolded();
+ if (getFaceAuthInteractor() != null) {
+ getFaceAuthInteractor().onDeviceUnfolded();
+ }
requestActiveUnlockFromWakeReason(PowerManager.WAKE_REASON_UNFOLD_DEVICE,
false);
}
diff --git a/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java b/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java
index 831543da3237..ef172a1b24f6 100644
--- a/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java
+++ b/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java
@@ -69,7 +69,9 @@ public abstract class ClockRegistryModule {
layoutInflater,
resources,
featureFlags.isEnabled(Flags.STEP_CLOCK_ANIMATION),
- MigrateClocksToBlueprint.isEnabled()),
+ MigrateClocksToBlueprint.isEnabled(),
+ com.android.systemui.Flags.clockReactiveVariants()
+ ),
context.getString(R.string.lockscreen_clock_id_fallback),
clockBuffers,
/* keepAllLoaded = */ false,
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractor.kt
index 373671d01395..0949ea4d7797 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractor.kt
@@ -16,6 +16,7 @@
package com.android.systemui.bouncer.domain.interactor
+import android.util.Log
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.biometrics.data.repository.FingerprintPropertyRepository
import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepository
@@ -46,6 +47,7 @@ import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.stateIn
/** Encapsulates business logic for interacting with the lock-screen alternate bouncer. */
@@ -137,6 +139,8 @@ constructor(
flowOf(false)
}
}
+ .distinctUntilChanged()
+ .onEach { Log.d(TAG, "canShowAlternateBouncer changed to $it") }
.stateIn(
scope = scope,
started = WhileSubscribed(),
@@ -234,5 +238,7 @@ constructor(
companion object {
private const val MIN_VISIBILITY_DURATION_UNTIL_TOUCHES_DISMISS_ALTERNATE_BOUNCER_MS = 200L
+
+ private const val TAG = "AlternateBouncerInteractor"
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
index 3ae9250b2938..6508e4b574a3 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
@@ -27,7 +27,6 @@ import android.os.UserHandle
import android.util.Log
import android.view.accessibility.AccessibilityEvent
import android.view.accessibility.AccessibilityManager
-import androidx.activity.result.ActivityResultLauncher
import com.android.internal.logging.UiEventLogger
import com.android.systemui.communal.dagger.CommunalModule.Companion.LAUNCHER_PACKAGE
import com.android.systemui.communal.data.model.CommunalWidgetCategories
@@ -184,10 +183,10 @@ constructor(
val isIdleOnCommunal: StateFlow<Boolean> = communalInteractor.isIdleOnCommunal
- /** Launch the widget picker activity using the given {@link ActivityResultLauncher}. */
+ /** Launch the widget picker activity using the given startActivity method. */
suspend fun onOpenWidgetPicker(
resources: Resources,
- activityLauncher: ActivityResultLauncher<Intent>,
+ startActivity: (intent: Intent) -> Unit,
): Boolean =
withContext(backgroundDispatcher) {
val widgets = communalInteractor.widgetContent.first()
@@ -199,7 +198,7 @@ constructor(
}
getWidgetPickerActivityIntent(resources, excludeList)?.let {
try {
- activityLauncher.launch(it)
+ startActivity(it)
return@withContext true
} catch (e: Exception) {
Log.e(TAG, "Failed to launch widget picker activity", e)
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
index 6228ac5f84ae..8c14d63c0e84 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
@@ -27,8 +27,6 @@ import android.view.IWindowManager
import android.view.WindowInsets
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
-import androidx.activity.result.ActivityResultLauncher
-import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
@@ -51,6 +49,7 @@ import com.android.systemui.log.LogBuffer
import com.android.systemui.log.core.Logger
import com.android.systemui.log.dagger.CommunalLog
import com.android.systemui.scene.shared.flag.SceneContainerFlag
+import com.android.systemui.settings.UserTracker
import javax.inject.Inject
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
@@ -64,12 +63,15 @@ constructor(
private val uiEventLogger: UiEventLogger,
private val widgetConfiguratorFactory: WidgetConfigurationController.Factory,
private val widgetSection: CommunalAppWidgetSection,
+ private val userTracker: UserTracker,
@CommunalLog logBuffer: LogBuffer,
) : ComponentActivity() {
companion object {
private const val TAG = "EditWidgetsActivity"
private const val EXTRA_IS_PENDING_WIDGET_DRAG = "is_pending_widget_drag"
const val EXTRA_OPEN_WIDGET_PICKER_ON_START = "open_widget_picker_on_start"
+
+ private const val REQUEST_CODE_WIDGET_PICKER = 200
}
/**
@@ -110,7 +112,7 @@ constructor(
object : ActivityLifecycleCallbacks {
override fun onActivityCreated(
activity: Activity,
- savedInstanceState: Bundle?
+ savedInstanceState: Bundle?,
) {
waitingForResult =
savedInstanceState?.getBoolean(STATE_EXTRA_IS_WAITING_FOR_RESULT)
@@ -172,41 +174,6 @@ constructor(
if (communalEditWidgetsActivityFinishFix()) ActivityControllerImpl(this)
else NopActivityController()
- private val addWidgetActivityLauncher: ActivityResultLauncher<Intent> =
- registerForActivityResult(StartActivityForResult()) { result ->
- when (result.resultCode) {
- RESULT_OK -> {
- uiEventLogger.log(CommunalUiEvent.COMMUNAL_HUB_WIDGET_PICKER_SHOWN)
-
- result.data?.let { intent ->
- val isPendingWidgetDrag =
- intent.getBooleanExtra(EXTRA_IS_PENDING_WIDGET_DRAG, false)
- // Nothing to do when a widget is being dragged & dropped. The drop
- // target in the communal grid will receive the widget to be added (if
- // the user drops it over).
- if (!isPendingWidgetDrag) {
- val (componentName, user) = getWidgetExtraFromIntent(intent)
- if (componentName != null && user != null) {
- // Add widget at the end.
- communalViewModel.onAddWidget(
- componentName,
- user,
- configurator = widgetConfigurator,
- )
- } else {
- run { Log.w(TAG, "No AppWidgetProviderInfo found in result.") }
- }
- }
- } ?: run { Log.w(TAG, "No data in result.") }
- }
- else ->
- Log.w(
- TAG,
- "Failed to receive result from widget picker, code=${result.resultCode}"
- )
- }
- }
-
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -226,8 +193,7 @@ constructor(
PlatformTheme {
Box(
modifier =
- Modifier.fillMaxSize()
- .background(MaterialTheme.colorScheme.surfaceDim),
+ Modifier.fillMaxSize().background(MaterialTheme.colorScheme.surfaceDim)
) {
CommunalHub(
viewModel = communalViewModel,
@@ -274,7 +240,13 @@ constructor(
private fun onOpenWidgetPicker() {
lifecycleScope.launch {
- communalViewModel.onOpenWidgetPicker(resources, addWidgetActivityLauncher)
+ communalViewModel.onOpenWidgetPicker(resources) { intent: Intent ->
+ startActivityForResultAsUser(
+ intent,
+ REQUEST_CODE_WIDGET_PICKER,
+ userTracker.userHandle,
+ )
+ }
}
}
@@ -285,7 +257,7 @@ constructor(
communalViewModel.changeScene(
scene = CommunalScenes.Communal,
loggingReason = "edit mode closing",
- transitionKey = CommunalTransitionKeys.FromEditMode
+ transitionKey = CommunalTransitionKeys.FromEditMode,
)
// Wait for the current scene to be idle on communal.
@@ -309,7 +281,7 @@ constructor(
flagsMask: Int,
flagsValues: Int,
extraFlags: Int,
- options: Bundle?
+ options: Bundle?,
) {
activityController.onWaitingForResult(true)
super.startIntentSenderForResult(
@@ -319,15 +291,46 @@ constructor(
flagsMask,
flagsValues,
extraFlags,
- options
+ options,
)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
activityController.onWaitingForResult(false)
super.onActivityResult(requestCode, resultCode, data)
- if (requestCode == WidgetConfigurationController.REQUEST_CODE) {
- widgetConfigurator.setConfigurationResult(resultCode)
+
+ when (requestCode) {
+ WidgetConfigurationController.REQUEST_CODE ->
+ widgetConfigurator.setConfigurationResult(resultCode)
+ REQUEST_CODE_WIDGET_PICKER -> {
+ if (resultCode != RESULT_OK) {
+ Log.w(TAG, "Failed to receive result from widget picker, code=$resultCode")
+ return
+ }
+
+ uiEventLogger.log(CommunalUiEvent.COMMUNAL_HUB_WIDGET_PICKER_SHOWN)
+
+ data?.let { intent ->
+ val isPendingWidgetDrag =
+ intent.getBooleanExtra(EXTRA_IS_PENDING_WIDGET_DRAG, false)
+ // Nothing to do when a widget is being dragged & dropped. The drop
+ // target in the communal grid will receive the widget to be added (if
+ // the user drops it over).
+ if (!isPendingWidgetDrag) {
+ val (componentName, user) = getWidgetExtraFromIntent(intent)
+ if (componentName != null && user != null) {
+ // Add widget at the end.
+ communalViewModel.onAddWidget(
+ componentName,
+ user,
+ configurator = widgetConfigurator,
+ )
+ } else {
+ run { Log.w(TAG, "No AppWidgetProviderInfo found in result.") }
+ }
+ }
+ } ?: run { Log.w(TAG, "No data in result.") }
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/display/DisplayModule.kt b/packages/SystemUI/src/com/android/systemui/display/DisplayModule.kt
index b56ed8c4c090..589dbf92de38 100644
--- a/packages/SystemUI/src/com/android/systemui/display/DisplayModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/display/DisplayModule.kt
@@ -24,6 +24,8 @@ import com.android.systemui.display.data.repository.DisplayRepository
import com.android.systemui.display.data.repository.DisplayRepositoryImpl
import com.android.systemui.display.data.repository.DisplayScopeRepository
import com.android.systemui.display.data.repository.DisplayScopeRepositoryImpl
+import com.android.systemui.display.data.repository.DisplayWindowPropertiesRepository
+import com.android.systemui.display.data.repository.DisplayWindowPropertiesRepositoryImpl
import com.android.systemui.display.data.repository.FocusedDisplayRepository
import com.android.systemui.display.data.repository.FocusedDisplayRepositoryImpl
import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor
@@ -58,6 +60,11 @@ interface DisplayModule {
@Binds fun displayScopeRepository(impl: DisplayScopeRepositoryImpl): DisplayScopeRepository
+ @Binds
+ fun displayWindowPropertiesRepository(
+ impl: DisplayWindowPropertiesRepositoryImpl
+ ): DisplayWindowPropertiesRepository
+
companion object {
@Provides
@SysUISingleton
@@ -72,5 +79,19 @@ interface DisplayModule {
CoreStartable.NOP
}
}
+
+ @Provides
+ @SysUISingleton
+ @IntoMap
+ @ClassKey(DisplayWindowPropertiesRepository::class)
+ fun displayWindowPropertiesRepoAsCoreStartable(
+ repoLazy: Lazy<DisplayWindowPropertiesRepositoryImpl>
+ ): CoreStartable {
+ return if (StatusBarConnectedDisplays.isEnabled) {
+ return repoLazy.get()
+ } else {
+ CoreStartable.NOP
+ }
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayWindowPropertiesRepository.kt b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayWindowPropertiesRepository.kt
new file mode 100644
index 000000000000..88d3a28669df
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayWindowPropertiesRepository.kt
@@ -0,0 +1,115 @@
+/*
+ * 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.display.data.repository
+
+import android.annotation.SuppressLint
+import android.content.Context
+import android.view.Display
+import android.view.WindowManager
+import com.android.systemui.CoreStartable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.display.shared.model.DisplayWindowProperties
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
+import com.google.common.collect.HashBasedTable
+import com.google.common.collect.Table
+import java.io.PrintWriter
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineName
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+
+/** Provides per display instances of [DisplayWindowProperties]. */
+interface DisplayWindowPropertiesRepository {
+
+ /**
+ * Returns a [DisplayWindowProperties] instance for a given display id and window type.
+ *
+ * @throws IllegalArgumentException if no display with the given display id exists.
+ */
+ fun get(
+ displayId: Int,
+ @WindowManager.LayoutParams.WindowType windowType: Int,
+ ): DisplayWindowProperties
+}
+
+@SysUISingleton
+class DisplayWindowPropertiesRepositoryImpl
+@Inject
+constructor(
+ @Background private val backgroundApplicationScope: CoroutineScope,
+ private val globalContext: Context,
+ private val globalWindowManager: WindowManager,
+ private val displayRepository: DisplayRepository,
+) : DisplayWindowPropertiesRepository, CoreStartable {
+
+ init {
+ StatusBarConnectedDisplays.assertInNewMode()
+ }
+
+ private val properties: Table<Int, Int, DisplayWindowProperties> = HashBasedTable.create()
+
+ override fun get(
+ displayId: Int,
+ @WindowManager.LayoutParams.WindowType windowType: Int,
+ ): DisplayWindowProperties {
+ val display =
+ displayRepository.getDisplay(displayId)
+ ?: throw IllegalArgumentException("Display with id $displayId doesn't exist")
+ return properties.get(displayId, windowType)
+ ?: create(display, windowType).also { properties.put(displayId, windowType, it) }
+ }
+
+ override fun start() {
+ backgroundApplicationScope.launch(
+ CoroutineName("DisplayWindowPropertiesRepositoryImpl#start")
+ ) {
+ displayRepository.displayRemovalEvent.collect { removedDisplayId ->
+ properties.row(removedDisplayId).clear()
+ }
+ }
+ }
+
+ private fun create(display: Display, windowType: Int): DisplayWindowProperties {
+ val displayId = display.displayId
+ return if (displayId == Display.DEFAULT_DISPLAY) {
+ // For the default display, we can just reuse the global/application properties.
+ // Creating a window context is expensive, therefore we avoid it.
+ DisplayWindowProperties(
+ displayId = displayId,
+ windowType = windowType,
+ context = globalContext,
+ windowManager = globalWindowManager,
+ )
+ } else {
+ val context = createWindowContext(display, windowType)
+ @SuppressLint("NonInjectedService") // Need to manually get the service
+ val windowManager = context.getSystemService(WindowManager::class.java) as WindowManager
+ DisplayWindowProperties(displayId, windowType, context, windowManager)
+ }
+ }
+
+ private fun createWindowContext(display: Display, windowType: Int): Context =
+ globalContext.createWindowContext(display, windowType, /* options= */ null).also {
+ it.setTheme(R.style.Theme_SystemUI)
+ }
+
+ override fun dump(pw: PrintWriter, args: Array<out String>) {
+ pw.write("perDisplayContexts: $properties")
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/display/shared/model/DisplayWindowProperties.kt b/packages/SystemUI/src/com/android/systemui/display/shared/model/DisplayWindowProperties.kt
new file mode 100644
index 000000000000..6acc296367a9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/display/shared/model/DisplayWindowProperties.kt
@@ -0,0 +1,43 @@
+/*
+ * 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.display.shared.model
+
+import android.content.Context
+import android.view.WindowManager
+
+/** Represents a display specific group of window related properties. */
+data class DisplayWindowProperties(
+ /** The id of the display associated with this instance. */
+ val displayId: Int,
+ /**
+ * The window type that was used to create the [Context] in this instance, using
+ * [Context.createWindowContext]. This is the window type that can be used when adding views to
+ * the [WindowManager] associated with this instance.
+ */
+ @WindowManager.LayoutParams.WindowType val windowType: Int,
+ /**
+ * The display specific [Context] created using [Context.createWindowContext] with window type
+ * associated with this instance.
+ */
+ val context: Context,
+
+ /**
+ * The display specific [WindowManager] instance to be used when adding windows of the type
+ * associated with this instance.
+ */
+ val windowManager: WindowManager,
+)
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 65c29b829429..9c5231d716da 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
@@ -92,6 +92,7 @@ import com.android.systemui.plugins.qs.QSContainerController
import com.android.systemui.qs.composefragment.SceneKeys.QuickQuickSettings
import com.android.systemui.qs.composefragment.SceneKeys.QuickSettings
import com.android.systemui.qs.composefragment.SceneKeys.toIdleSceneKey
+import com.android.systemui.qs.composefragment.ui.NotificationScrimClipParams
import com.android.systemui.qs.composefragment.ui.notificationScrimClip
import com.android.systemui.qs.composefragment.ui.quickQuickSettingsToQuickSettings
import com.android.systemui.qs.composefragment.viewmodel.QSFragmentComposeViewModel
@@ -149,20 +150,12 @@ constructor(
private val notificationScrimClippingParams =
object {
var isEnabled by mutableStateOf(false)
- var leftInset by mutableStateOf(0)
- var rightInset by mutableStateOf(0)
- var top by mutableStateOf(0)
- var bottom by mutableStateOf(0)
- var radius by mutableStateOf(0)
+ var params by mutableStateOf(NotificationScrimClipParams())
fun dump(pw: IndentingPrintWriter) {
pw.printSection("NotificationScrimClippingParams") {
pw.println("isEnabled", isEnabled)
- pw.println("leftInset", "${leftInset}px")
- pw.println("rightInset", "${rightInset}px")
- pw.println("top", "${top}px")
- pw.println("bottom", "${bottom}px")
- pw.println("radius", "${radius}px")
+ pw.println("params", params)
}
}
}
@@ -216,7 +209,7 @@ constructor(
FrameLayoutTouchPassthrough(
context,
{ notificationScrimClippingParams.isEnabled },
- { notificationScrimClippingParams.top },
+ { notificationScrimClippingParams.params.top },
)
frame.addView(
composeView,
@@ -237,13 +230,7 @@ constructor(
Modifier.windowInsetsPadding(WindowInsets.navigationBars).thenIf(
notificationScrimClippingParams.isEnabled
) {
- Modifier.notificationScrimClip(
- notificationScrimClippingParams.leftInset,
- notificationScrimClippingParams.top,
- notificationScrimClippingParams.rightInset,
- notificationScrimClippingParams.bottom,
- notificationScrimClippingParams.radius,
- )
+ Modifier.notificationScrimClip { notificationScrimClippingParams.params }
},
) {
val isEditing by
@@ -445,13 +432,14 @@ constructor(
fullWidth: Boolean,
) {
notificationScrimClippingParams.isEnabled = visible
- notificationScrimClippingParams.top = top
- notificationScrimClippingParams.bottom = bottom
- // Full width means that QS will show in the entire width allocated to it (for example
- // phone) vs. showing in a narrower column (for example, tablet portrait).
- notificationScrimClippingParams.leftInset = if (fullWidth) 0 else leftInset
- notificationScrimClippingParams.rightInset = if (fullWidth) 0 else rightInset
- notificationScrimClippingParams.radius = cornerRadius
+ notificationScrimClippingParams.params =
+ NotificationScrimClipParams(
+ top,
+ bottom,
+ if (fullWidth) 0 else leftInset,
+ if (fullWidth) 0 else rightInset,
+ cornerRadius,
+ )
}
override fun isFullyCollapsed(): Boolean {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/composefragment/ui/NotificationScrimClip.kt b/packages/SystemUI/src/com/android/systemui/qs/composefragment/ui/NotificationScrimClip.kt
index 93c6445b78ef..c912bd59c19f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/composefragment/ui/NotificationScrimClip.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/ui/NotificationScrimClip.kt
@@ -31,87 +31,73 @@ import androidx.compose.ui.platform.InspectorInfo
* ([ClipOp.Difference]) a `RoundRect(-leftInset, top, width + rightInset, bottom, radius, radius)`
* from the QS container.
*/
-fun Modifier.notificationScrimClip(
- leftInset: Int,
- top: Int,
- rightInset: Int,
- bottom: Int,
- radius: Int
-): Modifier {
- return this then NotificationScrimClipElement(leftInset, top, rightInset, bottom, radius)
+fun Modifier.notificationScrimClip(clipParams: () -> NotificationScrimClipParams): Modifier {
+ return this then NotificationScrimClipElement(clipParams)
}
-private class NotificationScrimClipNode(
- var leftInset: Float,
- var top: Float,
- var rightInset: Float,
- var bottom: Float,
- var radius: Float,
-) : DrawModifierNode, Modifier.Node() {
+private class NotificationScrimClipNode(var clipParams: () -> NotificationScrimClipParams) :
+ DrawModifierNode, Modifier.Node() {
private val path = Path()
- var invalidated = true
+ private var lastClipParams = NotificationScrimClipParams()
override fun ContentDrawScope.draw() {
- if (invalidated) {
+ val newClipParams = clipParams()
+ if (newClipParams != lastClipParams) {
+ lastClipParams = newClipParams
+ applyClipParams(path, lastClipParams)
+ }
+ clipPath(path, ClipOp.Difference) { this@draw.drawContent() }
+ }
+
+ private fun ContentDrawScope.applyClipParams(
+ path: Path,
+ clipParams: NotificationScrimClipParams,
+ ) {
+ with(clipParams) {
path.rewind()
path
.asAndroidPath()
.addRoundRect(
- -leftInset,
- top,
+ -leftInset.toFloat(),
+ top.toFloat(),
size.width + rightInset,
- bottom,
- radius,
- radius,
- android.graphics.Path.Direction.CW
+ bottom.toFloat(),
+ radius.toFloat(),
+ radius.toFloat(),
+ android.graphics.Path.Direction.CW,
)
- invalidated = false
}
- clipPath(path, ClipOp.Difference) { this@draw.drawContent() }
}
}
-private data class NotificationScrimClipElement(
- val leftInset: Int,
- val top: Int,
- val rightInset: Int,
- val bottom: Int,
- val radius: Int,
-) : ModifierNodeElement<NotificationScrimClipNode>() {
+private data class NotificationScrimClipElement(val clipParams: () -> NotificationScrimClipParams) :
+ ModifierNodeElement<NotificationScrimClipNode>() {
override fun create(): NotificationScrimClipNode {
- return NotificationScrimClipNode(
- leftInset.toFloat(),
- top.toFloat(),
- rightInset.toFloat(),
- bottom.toFloat(),
- radius.toFloat(),
- )
+ return NotificationScrimClipNode(clipParams)
}
override fun update(node: NotificationScrimClipNode) {
- val changed =
- node.leftInset != leftInset.toFloat() ||
- node.top != top.toFloat() ||
- node.rightInset != rightInset.toFloat() ||
- node.bottom != bottom.toFloat() ||
- node.radius != radius.toFloat()
- if (changed) {
- node.leftInset = leftInset.toFloat()
- node.top = top.toFloat()
- node.rightInset = rightInset.toFloat()
- node.bottom = bottom.toFloat()
- node.radius = radius.toFloat()
- node.invalidated = true
- }
+ node.clipParams = clipParams
}
override fun InspectorInfo.inspectableProperties() {
name = "notificationScrimClip"
- properties["leftInset"] = leftInset
- properties["top"] = top
- properties["rightInset"] = rightInset
- properties["bottom"] = bottom
- properties["radius"] = radius
+ with(clipParams()) {
+ properties["leftInset"] = leftInset
+ properties["top"] = top
+ properties["rightInset"] = rightInset
+ properties["bottom"] = bottom
+ properties["radius"] = radius
+ }
}
}
+
+/** Params for [notificationScrimClip]. */
+data class NotificationScrimClipParams(
+ val top: Int = 0,
+ val bottom: Int = 0,
+ val leftInset: Int = 0,
+ val rightInset: Int = 0,
+ val radius: Int = 0,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
index 73ad0e50793a..da04f6edf9e2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
@@ -46,6 +46,7 @@ import com.android.internal.jank.InteractionJankMonitor.Configuration;
import com.android.internal.logging.UiEventLogger;
import com.android.keyguard.KeyguardClockSwitch;
import com.android.systemui.DejankUtils;
+import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.deviceentry.domain.interactor.DeviceUnlockedInteractor;
import com.android.systemui.deviceentry.shared.model.DeviceUnlockStatus;
@@ -123,6 +124,7 @@ public class StatusBarStateControllerImpl implements
private final Lazy<SceneContainerOcclusionInteractor> mSceneContainerOcclusionInteractorLazy;
private final Lazy<KeyguardClockInteractor> mKeyguardClockInteractorLazy;
private final Lazy<SceneBackInteractor> mSceneBackInteractorLazy;
+ private final Lazy<AlternateBouncerInteractor> mAlternateBouncerInteractorLazy;
private int mState;
private int mLastState;
private int mUpcomingState;
@@ -193,7 +195,8 @@ public class StatusBarStateControllerImpl implements
Lazy<SceneInteractor> sceneInteractorLazy,
Lazy<SceneContainerOcclusionInteractor> sceneContainerOcclusionInteractor,
Lazy<KeyguardClockInteractor> keyguardClockInteractorLazy,
- Lazy<SceneBackInteractor> sceneBackInteractorLazy) {
+ Lazy<SceneBackInteractor> sceneBackInteractorLazy,
+ Lazy<AlternateBouncerInteractor> alternateBouncerInteractorLazy) {
mUiEventLogger = uiEventLogger;
mInteractionJankMonitorLazy = interactionJankMonitorLazy;
mJavaAdapter = javaAdapter;
@@ -205,6 +208,7 @@ public class StatusBarStateControllerImpl implements
mSceneContainerOcclusionInteractorLazy = sceneContainerOcclusionInteractor;
mKeyguardClockInteractorLazy = keyguardClockInteractorLazy;
mSceneBackInteractorLazy = sceneBackInteractorLazy;
+ mAlternateBouncerInteractorLazy = alternateBouncerInteractorLazy;
for (int i = 0; i < HISTORY_SIZE; i++) {
mHistoricalRecords[i] = new HistoricalState();
}
@@ -233,6 +237,7 @@ public class StatusBarStateControllerImpl implements
mSceneInteractorLazy.get().getCurrentOverlays(),
mSceneBackInteractorLazy.get().getBackStack(),
mSceneContainerOcclusionInteractorLazy.get().getInvisibleDueToOcclusion(),
+ mAlternateBouncerInteractorLazy.get().isVisible(),
this::calculateStateFromSceneFramework),
this::onStatusBarStateChanged);
@@ -693,7 +698,8 @@ public class StatusBarStateControllerImpl implements
SceneKey currentScene,
Set<OverlayKey> currentOverlays,
SceneStack backStack,
- boolean isOccluded) {
+ boolean isOccluded,
+ boolean alternateBouncerIsVisible) {
SceneContainerFlag.isUnexpectedlyInLegacyMode();
final boolean onBouncer = currentScene.equals(Scenes.Bouncer);
@@ -714,7 +720,8 @@ public class StatusBarStateControllerImpl implements
final String inputLogString = "currentScene=" + currentScene.getTestTag()
+ " currentOverlays=" + currentOverlays + " backStack=" + backStack
- + " isUnlocked=" + isUnlocked + " isOccluded=" + isOccluded;
+ + " isUnlocked=" + isUnlocked + " isOccluded=" + isOccluded
+ + " alternateBouncerIsVisible=" + alternateBouncerIsVisible;
int newState;
@@ -722,6 +729,7 @@ public class StatusBarStateControllerImpl implements
// 1. deviceUnlockStatus.isUnlocked changes from false to true.
// 2. Lockscreen changes to Gone, either in currentScene or in backStack.
// 3. Bouncer is removed from currentScene or backStack, if it was present.
+ // 4. the alternate bouncer is hidden, if it was visible.
//
// From this function's perspective, though, deviceUnlockStatus, currentScene, and backStack
// each update separately, and the relative order of those updates is not well-defined. This
@@ -733,6 +741,7 @@ public class StatusBarStateControllerImpl implements
// 1. deviceUnlockStatus.isUnlocked is false.
// 2. currentScene is a keyguardish scene (Lockscreen, Bouncer, or Communal).
// 3. backStack contains a keyguardish scene (Lockscreen or Communal).
+ // 4. the alternate bouncer is visible.
final boolean onKeyguardish = onLockscreen || onBouncer || onCommunal;
final boolean overKeyguardish = overLockscreen || overCommunal;
@@ -741,7 +750,7 @@ public class StatusBarStateControllerImpl implements
// Occlusion is special; even though the device is still technically on the lockscreen,
// the UI behaves as if it is unlocked.
newState = StatusBarState.SHADE;
- } else if (onKeyguardish || overKeyguardish) {
+ } else if (onKeyguardish || overKeyguardish || alternateBouncerIsVisible) {
// We get here if we are on or over a keyguardish scene, even if isUnlocked is true; we
// want to return SHADE_LOCKED or KEYGUARD until we are also neither on nor over a
// keyguardish scene.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt
index cf238d553225..cd1642eee4b4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt
@@ -22,15 +22,20 @@ import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.LogBufferFactory
+import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
import com.android.systemui.statusbar.data.StatusBarDataLayerModule
import com.android.systemui.statusbar.phone.LightBarController
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy
import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController
import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallLog
import com.android.systemui.statusbar.ui.SystemBarUtilsProxyImpl
+import com.android.systemui.statusbar.window.MultiDisplayStatusBarWindowControllerStore
+import com.android.systemui.statusbar.window.SingleDisplayStatusBarWindowControllerStore
import com.android.systemui.statusbar.window.StatusBarWindowController
import com.android.systemui.statusbar.window.StatusBarWindowControllerImpl
+import com.android.systemui.statusbar.window.StatusBarWindowControllerStore
import dagger.Binds
+import dagger.Lazy
import dagger.Module
import dagger.Provides
import dagger.multibindings.ClassKey
@@ -62,13 +67,19 @@ abstract class StatusBarModule {
@ClassKey(StatusBarSignalPolicy::class)
abstract fun bindStatusBarSignalPolicy(impl: StatusBarSignalPolicy): CoreStartable
+ @Binds
+ @SysUISingleton
+ abstract fun statusBarWindowControllerFactory(
+ implFactory: StatusBarWindowControllerImpl.Factory
+ ): StatusBarWindowController.Factory
+
companion object {
@Provides
@SysUISingleton
- fun statusBarWindowController(
- context: Context?,
- viewCaptureAwareWindowManager: ViewCaptureAwareWindowManager?,
+ fun defaultStatusBarWindowController(
+ context: Context,
+ viewCaptureAwareWindowManager: ViewCaptureAwareWindowManager,
factory: StatusBarWindowControllerImpl.Factory,
): StatusBarWindowController {
return factory.create(context, viewCaptureAwareWindowManager)
@@ -76,6 +87,33 @@ abstract class StatusBarModule {
@Provides
@SysUISingleton
+ fun windowControllerStore(
+ multiDisplayImplLazy: Lazy<MultiDisplayStatusBarWindowControllerStore>,
+ singleDisplayImplLazy: Lazy<SingleDisplayStatusBarWindowControllerStore>,
+ ): StatusBarWindowControllerStore {
+ return if (StatusBarConnectedDisplays.isEnabled) {
+ multiDisplayImplLazy.get()
+ } else {
+ singleDisplayImplLazy.get()
+ }
+ }
+
+ @Provides
+ @SysUISingleton
+ @IntoMap
+ @ClassKey(MultiDisplayStatusBarWindowControllerStore::class)
+ fun multiDisplayControllerStoreAsCoreStartable(
+ storeLazy: Lazy<MultiDisplayStatusBarWindowControllerStore>
+ ): CoreStartable {
+ return if (StatusBarConnectedDisplays.isEnabled) {
+ storeLazy.get()
+ } else {
+ CoreStartable.NOP
+ }
+ }
+
+ @Provides
+ @SysUISingleton
@OngoingCallLog
fun provideOngoingCallLogBuffer(factory: LogBufferFactory): LogBuffer {
return factory.create("OngoingCall", 75)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index 0474344ee390..7e5b45543e9e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -33,7 +33,6 @@ import static com.android.systemui.Flags.relockWithPowerButtonImmediately;
import static com.android.systemui.Flags.statusBarSignalPolicyRefactor;
import static com.android.systemui.charging.WirelessChargingAnimation.UNKNOWN_BATTERY_LEVEL;
import static com.android.systemui.flags.Flags.SHORTCUT_LIST_SEARCH_LAYOUT;
-import static com.android.systemui.statusbar.NotificationLockscreenUserManager.PERMISSION_SELF;
import static com.android.systemui.statusbar.StatusBarState.SHADE;
import android.annotation.Nullable;
@@ -41,7 +40,6 @@ import android.app.ActivityOptions;
import android.app.IWallpaperManager;
import android.app.KeyguardManager;
import android.app.Notification;
-import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.StatusBarManager;
import android.app.TaskInfo;
@@ -275,11 +273,6 @@ import javax.inject.Provider;
@SysUISingleton
public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
- private static final String BANNER_ACTION_CANCEL =
- "com.android.systemui.statusbar.banner_action_cancel";
- private static final String BANNER_ACTION_SETUP =
- "com.android.systemui.statusbar.banner_action_setup";
-
private static final int MSG_LAUNCH_TRANSITION_TIMEOUT = 1003;
// 1020-1040 reserved for BaseStatusBar
@@ -963,12 +956,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
}
}
- IntentFilter internalFilter = new IntentFilter();
- internalFilter.addAction(BANNER_ACTION_CANCEL);
- internalFilter.addAction(BANNER_ACTION_SETUP);
- mContext.registerReceiver(mBannerActionBroadcastReceiver, internalFilter, PERMISSION_SELF,
- null, Context.RECEIVER_EXPORTED_UNAUDITED);
-
if (mWallpaperSupported) {
IWallpaperManager wallpaperManager = IWallpaperManager.Stub.asInterface(
ServiceManager.getService(Context.WALLPAPER_SERVICE));
@@ -2948,29 +2935,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
return mDeviceInteractive;
}
- private final BroadcastReceiver mBannerActionBroadcastReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- String action = intent.getAction();
- if (BANNER_ACTION_CANCEL.equals(action) || BANNER_ACTION_SETUP.equals(action)) {
- NotificationManager noMan = (NotificationManager)
- mContext.getSystemService(Context.NOTIFICATION_SERVICE);
- noMan.cancel(com.android.internal.messages.nano.SystemMessageProto.SystemMessage.
- NOTE_HIDDEN_NOTIFICATIONS);
-
- Settings.Secure.putInt(mContext.getContentResolver(),
- Settings.Secure.SHOW_NOTE_ABOUT_NOTIFICATION_HIDING, 0);
- if (BANNER_ACTION_SETUP.equals(action)) {
- mShadeController.animateCollapseShadeForced();
- mContext.startActivity(new Intent(Settings.ACTION_APP_NOTIFICATION_REDACTION)
- .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
-
- );
- }
- }
- }
- };
-
@Override
public void handleExternalShadeWindowTouch(MotionEvent event) {
getNotificationShadeWindowViewController().handleExternalTouch(event);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index 479ffb728eb2..17bd53869ee5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -530,6 +530,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
this::consumeKeyguardAuthenticatedBiometricsHandled
);
} else {
+ // Collector that keeps the AlternateBouncerInteractor#canShowAlternateBouncer flow hot.
mListenForCanShowAlternateBouncer = mJavaAdapter.alwaysCollectFlow(
mAlternateBouncerInteractor.getCanShowAlternateBouncer(),
this::consumeCanShowAlternateBouncer
@@ -578,8 +579,17 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
}
private void consumeCanShowAlternateBouncer(boolean canShow) {
- // do nothing, we only are registering for the flow to ensure that there's at least
- // one subscriber that will update AlternateBouncerInteractor.canShowAlternateBouncer.value
+ // Hack: this is required to fix issues where
+ // KeyguardBouncerRepository#alternateBouncerVisible state is incorrectly set and then never
+ // reset. This is caused by usages of show()/forceShow() that only read this flow to set the
+ // alternate bouncer visible state, if there is a race condition between when that flow
+ // changes to false and when the read happens, the flow will be set to an incorrect value
+ // and not reset on time.
+ if (!canShow) {
+ Log.d(TAG, "canShowAlternateBouncer turned false, maybe try hiding the alternate "
+ + "bouncer if it is already visible");
+ mAlternateBouncerInteractor.maybeHide();
+ }
}
/** Register a callback, to be invoked by the Predictive Back system. */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepository.kt
index f5cfc8c5b307..e0bf00fbe431 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepository.kt
@@ -26,6 +26,7 @@ import android.net.NetworkCapabilities.TRANSPORT_CELLULAR
import android.net.NetworkCapabilities.TRANSPORT_ETHERNET
import android.net.NetworkCapabilities.TRANSPORT_WIFI
import android.net.vcn.VcnTransportInfo
+import android.net.vcn.VcnUtils
import android.net.wifi.WifiInfo
import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
import androidx.annotation.ArrayRes
@@ -161,7 +162,9 @@ constructor(
defaultNetworkCapabilities
.map { networkCapabilities ->
networkCapabilities?.run {
- val subId = (transportInfo as? VcnTransportInfo)?.subId
+ val subId =
+ VcnUtils.getSubIdFromVcnCaps(connectivityManager, networkCapabilities)
+
// Never return an INVALID_SUBSCRIPTION_ID (-1)
if (subId != INVALID_SUBSCRIPTION_ID) {
subId
@@ -245,9 +248,9 @@ constructor(
* info.
*/
fun NetworkCapabilities.getMainOrUnderlyingWifiInfo(
- connectivityManager: ConnectivityManager,
+ connectivityManager: ConnectivityManager
): WifiInfo? {
- val mainWifiInfo = this.getMainWifiInfo()
+ val mainWifiInfo = this.getMainWifiInfo(connectivityManager)
if (mainWifiInfo != null) {
return mainWifiInfo
}
@@ -264,7 +267,9 @@ constructor(
// eventually traced to a wifi or carrier merged connection. So, check those underlying
// networks for possible wifi information as well. See b/225902574.
return this.underlyingNetworks?.firstNotNullOfOrNull { underlyingNetwork ->
- connectivityManager.getNetworkCapabilities(underlyingNetwork)?.getMainWifiInfo()
+ connectivityManager
+ .getNetworkCapabilities(underlyingNetwork)
+ ?.getMainWifiInfo(connectivityManager)
}
}
@@ -272,7 +277,9 @@ constructor(
* Checks the network capabilities for wifi info, but does *not* check the underlying
* networks. See [getMainOrUnderlyingWifiInfo].
*/
- private fun NetworkCapabilities.getMainWifiInfo(): WifiInfo? {
+ private fun NetworkCapabilities.getMainWifiInfo(
+ connectivityManager: ConnectivityManager
+ ): WifiInfo? {
// Wifi info can either come from a WIFI Transport, or from a CELLULAR transport for
// virtual networks like VCN.
val canHaveWifiInfo =
@@ -286,7 +293,7 @@ constructor(
// [com.android.settingslib.Utils.tryGetWifiInfoForVcn]. It's copied instead of
// re-used because it makes the logic here clearer, and because the method will be
// removed once this pipeline is fully launched.
- is VcnTransportInfo -> currentTransportInfo.wifiInfo
+ is VcnTransportInfo -> VcnUtils.getWifiInfoFromVcnCaps(connectivityManager, this)
is WifiInfo -> currentTransportInfo
else -> null
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.kt
index 421e5c45bbfe..e8dc93465685 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.kt
@@ -16,8 +16,10 @@
package com.android.systemui.statusbar.window
+import android.content.Context
import android.view.View
import android.view.ViewGroup
+import com.android.app.viewcapture.ViewCaptureAwareWindowManager
import com.android.systemui.animation.ActivityTransitionAnimator
import com.android.systemui.fragments.FragmentHostManager
import java.util.Optional
@@ -73,4 +75,11 @@ interface StatusBarWindowController {
* this#setForceStatusBarVisible} together and use some sort of ranking system instead.
*/
fun setOngoingProcessRequiresStatusBarVisible(visible: Boolean)
+
+ interface Factory {
+ fun create(
+ context: Context,
+ viewCaptureAwareWindowManager: ViewCaptureAwareWindowManager,
+ ): StatusBarWindowController
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowControllerImpl.java
index 1ee7cf3490f4..d709e5a0cd6c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowControllerImpl.java
@@ -354,11 +354,13 @@ public class StatusBarWindowControllerImpl implements StatusBarWindowController
}
@AssistedFactory
- public interface Factory {
+ public interface Factory extends StatusBarWindowController.Factory {
/** Creates a new instance. */
+ @NonNull
+ @Override
StatusBarWindowControllerImpl create(
- Context context,
- ViewCaptureAwareWindowManager viewCaptureAwareWindowManager);
+ @NonNull Context context,
+ @NonNull ViewCaptureAwareWindowManager viewCaptureAwareWindowManager);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowControllerStore.kt b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowControllerStore.kt
new file mode 100644
index 000000000000..5f30b3719aa7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowControllerStore.kt
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.window
+
+import android.view.Display
+import android.view.WindowManager
+import com.android.app.viewcapture.ViewCaptureAwareWindowManager
+import com.android.systemui.CoreStartable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.display.data.repository.DisplayRepository
+import com.android.systemui.display.data.repository.DisplayWindowPropertiesRepository
+import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
+import java.util.concurrent.ConcurrentHashMap
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineName
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+
+/** Store that allows to retrieve per display instances of [StatusBarWindowController]. */
+interface StatusBarWindowControllerStore {
+ /**
+ * The instance for the default/main display of the device. For example, on a phone or a tablet,
+ * the default display is the internal/built-in display of the device.
+ *
+ * Note that the id of the default display is [Display.DEFAULT_DISPLAY].
+ */
+ val defaultDisplay: StatusBarWindowController
+
+ /**
+ * Returns an instance for a specific display id.
+ *
+ * @throws IllegalArgumentException if [displayId] doesn't match the id of any existing
+ * displays.
+ */
+ fun forDisplay(displayId: Int): StatusBarWindowController
+}
+
+@SysUISingleton
+class MultiDisplayStatusBarWindowControllerStore
+@Inject
+constructor(
+ @Background private val backgroundApplicationScope: CoroutineScope,
+ private val controllerFactory: StatusBarWindowController.Factory,
+ private val displayWindowPropertiesRepository: DisplayWindowPropertiesRepository,
+ private val viewCaptureAwareWindowManagerFactory: ViewCaptureAwareWindowManager.Factory,
+ private val displayRepository: DisplayRepository,
+) : StatusBarWindowControllerStore, CoreStartable {
+
+ init {
+ StatusBarConnectedDisplays.assertInNewMode()
+ }
+
+ private val perDisplayControllers = ConcurrentHashMap<Int, StatusBarWindowController>()
+
+ override fun start() {
+ backgroundApplicationScope.launch(CoroutineName("StatusBarWindowController#start")) {
+ displayRepository.displayRemovalEvent.collect { displayId ->
+ perDisplayControllers.remove(displayId)
+ }
+ }
+ }
+
+ override val defaultDisplay: StatusBarWindowController
+ get() = forDisplay(Display.DEFAULT_DISPLAY)
+
+ override fun forDisplay(displayId: Int): StatusBarWindowController {
+ if (displayRepository.getDisplay(displayId) == null) {
+ throw IllegalArgumentException("Display with id $displayId doesn't exist.")
+ }
+ return perDisplayControllers.computeIfAbsent(displayId) {
+ createControllerForDisplay(displayId)
+ }
+ }
+
+ private fun createControllerForDisplay(displayId: Int): StatusBarWindowController {
+ val statusBarDisplayContext =
+ displayWindowPropertiesRepository.get(
+ displayId = displayId,
+ windowType = WindowManager.LayoutParams.TYPE_STATUS_BAR,
+ )
+ val viewCaptureAwareWindowManager =
+ viewCaptureAwareWindowManagerFactory.create(statusBarDisplayContext.windowManager)
+ return controllerFactory.create(
+ statusBarDisplayContext.context,
+ viewCaptureAwareWindowManager,
+ )
+ }
+}
+
+@SysUISingleton
+class SingleDisplayStatusBarWindowControllerStore
+@Inject
+constructor(private val controller: StatusBarWindowController) : StatusBarWindowControllerStore {
+
+ init {
+ StatusBarConnectedDisplays.assertInLegacyMode()
+ }
+
+ override val defaultDisplay = controller
+
+ override fun forDisplay(displayId: Int) = controller
+}
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/BackGestureTutorialScreen.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/BackGestureTutorialScreen.kt
index bfc5429b59d4..6879a3415238 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/BackGestureTutorialScreen.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/BackGestureTutorialScreen.kt
@@ -27,10 +27,7 @@ import com.android.systemui.res.R
import com.android.systemui.touchpad.tutorial.ui.gesture.BackGestureMonitor
@Composable
-fun BackGestureTutorialScreen(
- onDoneButtonClicked: () -> Unit,
- onBack: () -> Unit,
-) {
+fun BackGestureTutorialScreen(onDoneButtonClicked: () -> Unit, onBack: () -> Unit) {
val screenConfig =
TutorialScreenConfig(
colors = rememberScreenColors(),
@@ -39,18 +36,20 @@ fun BackGestureTutorialScreen(
titleResId = R.string.touchpad_back_gesture_action_title,
bodyResId = R.string.touchpad_back_gesture_guidance,
titleSuccessResId = R.string.touchpad_back_gesture_success_title,
- bodySuccessResId = R.string.touchpad_back_gesture_success_body
+ bodySuccessResId = R.string.touchpad_back_gesture_success_body,
),
animations =
TutorialScreenConfig.Animations(
educationResId = R.raw.trackpad_back_edu,
- successResId = R.raw.trackpad_back_success
- )
+ successResId = R.raw.trackpad_back_success,
+ ),
)
val gestureMonitorProvider =
DistanceBasedGestureMonitorProvider(
monitorFactory = { distanceThresholdPx, gestureStateCallback ->
- BackGestureMonitor(distanceThresholdPx, gestureStateCallback)
+ BackGestureMonitor(distanceThresholdPx).also {
+ it.addGestureStateCallback(gestureStateCallback)
+ }
}
)
GestureTutorialScreen(screenConfig, gestureMonitorProvider, onDoneButtonClicked, onBack)
@@ -67,7 +66,7 @@ private fun rememberScreenColors(): TutorialScreenConfig.Colors {
rememberColorFilterProperty(".tertiaryFixedDim", tertiaryFixedDim),
rememberColorFilterProperty(".onTertiaryFixed", onTertiaryFixed),
rememberColorFilterProperty(".onTertiary", onTertiary),
- rememberColorFilterProperty(".onTertiaryFixedVariant", onTertiaryFixedVariant)
+ rememberColorFilterProperty(".onTertiaryFixedVariant", onTertiaryFixedVariant),
)
val screenColors =
remember(dynamicProperties) {
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/HomeGestureTutorialScreen.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/HomeGestureTutorialScreen.kt
index f2fec5f5d9b1..a55fa442cd96 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/HomeGestureTutorialScreen.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/HomeGestureTutorialScreen.kt
@@ -26,10 +26,7 @@ import com.android.systemui.res.R
import com.android.systemui.touchpad.tutorial.ui.gesture.HomeGestureMonitor
@Composable
-fun HomeGestureTutorialScreen(
- onDoneButtonClicked: () -> Unit,
- onBack: () -> Unit,
-) {
+fun HomeGestureTutorialScreen(onDoneButtonClicked: () -> Unit, onBack: () -> Unit) {
val screenConfig =
TutorialScreenConfig(
colors = rememberScreenColors(),
@@ -38,18 +35,20 @@ fun HomeGestureTutorialScreen(
titleResId = R.string.touchpad_home_gesture_action_title,
bodyResId = R.string.touchpad_home_gesture_guidance,
titleSuccessResId = R.string.touchpad_home_gesture_success_title,
- bodySuccessResId = R.string.touchpad_home_gesture_success_body
+ bodySuccessResId = R.string.touchpad_home_gesture_success_body,
),
animations =
TutorialScreenConfig.Animations(
educationResId = R.raw.trackpad_home_edu,
- successResId = R.raw.trackpad_home_success
- )
+ successResId = R.raw.trackpad_home_success,
+ ),
)
val gestureMonitorProvider =
DistanceBasedGestureMonitorProvider(
monitorFactory = { distanceThresholdPx, gestureStateCallback ->
- HomeGestureMonitor(distanceThresholdPx, gestureStateCallback)
+ HomeGestureMonitor(distanceThresholdPx).also {
+ it.addGestureStateCallback(gestureStateCallback)
+ }
}
)
GestureTutorialScreen(screenConfig, gestureMonitorProvider, onDoneButtonClicked, onBack)
@@ -64,7 +63,7 @@ private fun rememberScreenColors(): TutorialScreenConfig.Colors {
rememberLottieDynamicProperties(
rememberColorFilterProperty(".primaryFixedDim", primaryFixedDim),
rememberColorFilterProperty(".onPrimaryFixed", onPrimaryFixed),
- rememberColorFilterProperty(".onPrimaryFixedVariant", onPrimaryFixedVariant)
+ rememberColorFilterProperty(".onPrimaryFixedVariant", onPrimaryFixedVariant),
)
val screenColors =
remember(dynamicProperties) {
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/RecentAppsGestureTutorialScreen.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/RecentAppsGestureTutorialScreen.kt
index b2fb6cdfcee5..6ee15aa952f4 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/RecentAppsGestureTutorialScreen.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/RecentAppsGestureTutorialScreen.kt
@@ -29,10 +29,7 @@ import com.android.systemui.touchpad.tutorial.ui.gesture.RecentAppsGestureMonito
import com.android.systemui.touchpad.tutorial.ui.gesture.TouchpadGestureMonitor
@Composable
-fun RecentAppsGestureTutorialScreen(
- onDoneButtonClicked: () -> Unit,
- onBack: () -> Unit,
-) {
+fun RecentAppsGestureTutorialScreen(onDoneButtonClicked: () -> Unit, onBack: () -> Unit) {
val screenConfig =
TutorialScreenConfig(
colors = rememberScreenColors(),
@@ -41,20 +38,20 @@ fun RecentAppsGestureTutorialScreen(
titleResId = R.string.touchpad_recent_apps_gesture_action_title,
bodyResId = R.string.touchpad_recent_apps_gesture_guidance,
titleSuccessResId = R.string.touchpad_recent_apps_gesture_success_title,
- bodySuccessResId = R.string.touchpad_recent_apps_gesture_success_body
+ bodySuccessResId = R.string.touchpad_recent_apps_gesture_success_body,
),
animations =
TutorialScreenConfig.Animations(
educationResId = R.raw.trackpad_recent_apps_edu,
- successResId = R.raw.trackpad_recent_apps_success
- )
+ successResId = R.raw.trackpad_recent_apps_success,
+ ),
)
val gestureMonitorProvider =
object : GestureMonitorProvider {
@Composable
override fun rememberGestureMonitor(
resources: Resources,
- gestureStateChangedCallback: (GestureState) -> Unit
+ gestureStateChangedCallback: (GestureState) -> Unit,
): TouchpadGestureMonitor {
val distanceThresholdPx =
resources.getDimensionPixelSize(
@@ -63,11 +60,9 @@ fun RecentAppsGestureTutorialScreen(
val velocityThresholdPxPerMs =
resources.getDimension(R.dimen.touchpad_recent_apps_gesture_velocity_threshold)
return remember(distanceThresholdPx, velocityThresholdPxPerMs) {
- RecentAppsGestureMonitor(
- distanceThresholdPx,
- gestureStateChangedCallback,
- velocityThresholdPxPerMs
- )
+ RecentAppsGestureMonitor(distanceThresholdPx, velocityThresholdPxPerMs).also {
+ it.addGestureStateCallback(gestureStateChangedCallback)
+ }
}
}
}
@@ -83,7 +78,7 @@ private fun rememberScreenColors(): TutorialScreenConfig.Colors {
rememberLottieDynamicProperties(
rememberColorFilterProperty(".secondaryFixedDim", secondaryFixedDim),
rememberColorFilterProperty(".onSecondaryFixed", onSecondaryFixed),
- rememberColorFilterProperty(".onSecondaryFixedVariant", onSecondaryFixedVariant)
+ rememberColorFilterProperty(".onSecondaryFixedVariant", onSecondaryFixedVariant),
)
val screenColors =
remember(dynamicProperties) {
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/TutorialSelectionScreen.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/TutorialSelectionScreen.kt
index 94e19deb0006..3c31efa6265a 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/TutorialSelectionScreen.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/TutorialSelectionScreen.kt
@@ -84,7 +84,7 @@ private fun TutorialSelectionButtons(
) {
TutorialButton(
text = stringResource(R.string.touchpad_tutorial_home_gesture_button),
- icon = Icons.AutoMirrored.Outlined.ArrowBack,
+ icon = ImageVector.vectorResource(id = R.drawable.touchpad_tutorial_home_icon),
iconColor = MaterialTheme.colorScheme.onPrimary,
onClick = onHomeTutorialClicked,
backgroundColor = MaterialTheme.colorScheme.primary,
@@ -92,7 +92,7 @@ private fun TutorialSelectionButtons(
)
TutorialButton(
text = stringResource(R.string.touchpad_tutorial_back_gesture_button),
- icon = ImageVector.vectorResource(id = R.drawable.touchpad_tutorial_home_icon),
+ icon = Icons.AutoMirrored.Outlined.ArrowBack,
iconColor = MaterialTheme.colorScheme.onTertiary,
onClick = onBackTutorialClicked,
backgroundColor = MaterialTheme.colorScheme.tertiary,
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/BackGestureMonitor.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/BackGestureMonitor.kt
index ecb5574ba5df..490f04d55802 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/BackGestureMonitor.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/BackGestureMonitor.kt
@@ -20,18 +20,21 @@ import android.view.MotionEvent
import kotlin.math.abs
/** Monitors for touchpad back gesture, that is three fingers swiping left or right */
-class BackGestureMonitor(
- private val gestureDistanceThresholdPx: Int,
- override val gestureStateChangedCallback: (GestureState) -> Unit,
-) : TouchpadGestureMonitor {
+class BackGestureMonitor(private val gestureDistanceThresholdPx: Int) : TouchpadGestureMonitor {
+
private val distanceTracker = DistanceTracker()
+ private var gestureStateChangedCallback: (GestureState) -> Unit = {}
+
+ override fun addGestureStateCallback(callback: (GestureState) -> Unit) {
+ gestureStateChangedCallback = callback
+ }
- override fun processTouchpadEvent(event: MotionEvent) {
+ override fun accept(event: MotionEvent) {
if (!isThreeFingerTouchpadSwipe(event)) return
- val distanceState = distanceTracker.processEvent(event)
- updateGestureStateBasedOnDistance(
+ val gestureState = distanceTracker.processEvent(event)
+ updateGestureState(
gestureStateChangedCallback,
- distanceState,
+ gestureState,
isFinished = { abs(it.deltaX) >= gestureDistanceThresholdPx },
progress = { 0f },
)
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/DistanceTracker.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/DistanceTracker.kt
index 70d93668c6b3..d48235892d69 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/DistanceTracker.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/DistanceTracker.kt
@@ -38,10 +38,10 @@ class DistanceTracker(var startX: Float = 0f, var startY: Float = 0f) {
}
}
-sealed interface DistanceGestureState
+sealed class DistanceGestureState(val deltaX: Float, val deltaY: Float)
-class Started(val deltaX: Float, val deltaY: Float) : DistanceGestureState
+class Started(deltaX: Float, deltaY: Float) : DistanceGestureState(deltaX, deltaY)
-class Moving(val deltaX: Float, val deltaY: Float) : DistanceGestureState
+class Moving(deltaX: Float, deltaY: Float) : DistanceGestureState(deltaX, deltaY)
-class Finished(val deltaX: Float, val deltaY: Float) : DistanceGestureState
+class Finished(deltaX: Float, deltaY: Float) : DistanceGestureState(deltaX, deltaY)
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/GestureStateUpdates.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/GestureStateUpdates.kt
index c1caeb3cbf9e..f19467726def 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/GestureStateUpdates.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/GestureStateUpdates.kt
@@ -16,11 +16,8 @@
package com.android.systemui.touchpad.tutorial.ui.gesture
-/**
- * Helper function for gesture recognizers to have common state triggering logic based on distance
- * only.
- */
-inline fun updateGestureStateBasedOnDistance(
+/** Helper function for gesture recognizers to have common state triggering logic */
+inline fun updateGestureState(
gestureStateChangedCallback: (GestureState) -> Unit,
gestureState: DistanceGestureState?,
isFinished: (Finished) -> Boolean,
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/HomeGestureMonitor.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/HomeGestureMonitor.kt
index fdcf9deec923..83d4f566257b 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/HomeGestureMonitor.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/HomeGestureMonitor.kt
@@ -19,18 +19,21 @@ package com.android.systemui.touchpad.tutorial.ui.gesture
import android.view.MotionEvent
/** Monitors for touchpad home gesture, that is three fingers swiping up */
-class HomeGestureMonitor(
- private val gestureDistanceThresholdPx: Int,
- override val gestureStateChangedCallback: (GestureState) -> Unit,
-) : TouchpadGestureMonitor {
+class HomeGestureMonitor(private val gestureDistanceThresholdPx: Int) : TouchpadGestureMonitor {
+
private val distanceTracker = DistanceTracker()
+ private var gestureStateChangedCallback: (GestureState) -> Unit = {}
+
+ override fun addGestureStateCallback(callback: (GestureState) -> Unit) {
+ gestureStateChangedCallback = callback
+ }
- override fun processTouchpadEvent(event: MotionEvent) {
+ override fun accept(event: MotionEvent) {
if (!isThreeFingerTouchpadSwipe(event)) return
- val distanceState = distanceTracker.processEvent(event)
- updateGestureStateBasedOnDistance(
+ val gestureState = distanceTracker.processEvent(event)
+ updateGestureState(
gestureStateChangedCallback,
- distanceState,
+ gestureState,
isFinished = { -it.deltaY >= gestureDistanceThresholdPx },
progress = { 0f },
)
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/RecentAppsGestureMonitor.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/RecentAppsGestureMonitor.kt
index dd31ce309cce..1731bb85fba4 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/RecentAppsGestureMonitor.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/RecentAppsGestureMonitor.kt
@@ -17,7 +17,6 @@
package com.android.systemui.touchpad.tutorial.ui.gesture
import android.view.MotionEvent
-import androidx.compose.ui.input.pointer.util.VelocityTracker1D
import kotlin.math.abs
/**
@@ -27,45 +26,30 @@ import kotlin.math.abs
*/
class RecentAppsGestureMonitor(
private val gestureDistanceThresholdPx: Int,
- override val gestureStateChangedCallback: (GestureState) -> Unit,
private val velocityThresholdPxPerMs: Float,
- private val velocityTracker: VelocityTracker1D = VelocityTracker1D(isDataDifferential = false),
+ private val distanceTracker: DistanceTracker = DistanceTracker(),
+ private val velocityTracker: VerticalVelocityTracker = VerticalVelocityTracker(),
) : TouchpadGestureMonitor {
- private var xStart = 0f
- private var yStart = 0f
+ private var gestureStateChangedCallback: (GestureState) -> Unit = {}
- override fun processTouchpadEvent(event: MotionEvent) {
- val action = event.actionMasked
- velocityTracker.addDataPoint(event.eventTime, event.y)
- when (action) {
- MotionEvent.ACTION_DOWN -> {
- if (isThreeFingerTouchpadSwipe(event)) {
- xStart = event.x
- yStart = event.y
- gestureStateChangedCallback(GestureState.InProgress())
- }
- }
- MotionEvent.ACTION_UP -> {
- if (isThreeFingerTouchpadSwipe(event) && isRecentAppsGesture(event)) {
- gestureStateChangedCallback(GestureState.Finished)
- } else {
- gestureStateChangedCallback(GestureState.NotStarted)
- }
- velocityTracker.resetTracking()
- }
- MotionEvent.ACTION_CANCEL -> {
- velocityTracker.resetTracking()
- }
- }
+ override fun addGestureStateCallback(callback: (GestureState) -> Unit) {
+ gestureStateChangedCallback = callback
}
- private fun isRecentAppsGesture(event: MotionEvent): Boolean {
- // below is trying to mirror behavior of TriggerSwipeUpTouchTracker#onGestureEnd.
- // We're diving velocity by 1000, to have the same unit of measure: pixels/ms.
- val swipeDistance = yStart - event.y
- val velocity = velocityTracker.calculateVelocity() / 1000
- return swipeDistance >= gestureDistanceThresholdPx &&
- abs(velocity) <= velocityThresholdPxPerMs
+ override fun accept(event: MotionEvent) {
+ if (!isThreeFingerTouchpadSwipe(event)) return
+ val gestureState = distanceTracker.processEvent(event)
+ velocityTracker.accept(event)
+
+ updateGestureState(
+ gestureStateChangedCallback,
+ gestureState,
+ isFinished = { state ->
+ -state.deltaY >= gestureDistanceThresholdPx &&
+ abs(velocityTracker.calculateVelocity().value) <= velocityThresholdPxPerMs
+ },
+ progress = { 0f },
+ )
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureHandler.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureHandler.kt
index 88671d41f1cd..4b82ba1c0dda 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureHandler.kt
@@ -18,13 +18,14 @@ package com.android.systemui.touchpad.tutorial.ui.gesture
import android.view.InputDevice
import android.view.MotionEvent
+import java.util.function.Consumer
/**
* Allows listening to touchpadGesture and calling onDone when gesture was triggered. Can have all
* motion events passed to [onMotionEvent] and will filter touchpad events accordingly
*/
class TouchpadGestureHandler(
- private val gestureMonitor: TouchpadGestureMonitor,
+ private val gestureMonitor: Consumer<MotionEvent>,
private val easterEggGestureMonitor: EasterEggGestureMonitor,
) {
@@ -40,7 +41,7 @@ class TouchpadGestureHandler(
if (isTwoFingerSwipe(event)) {
easterEggGestureMonitor.processTouchpadEvent(event)
} else {
- gestureMonitor.processTouchpadEvent(event)
+ gestureMonitor.accept(event)
}
true
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureMonitor.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureMonitor.kt
index 4655c98b65ac..9216821272ff 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureMonitor.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureMonitor.kt
@@ -17,15 +17,11 @@
package com.android.systemui.touchpad.tutorial.ui.gesture
import android.view.MotionEvent
+import java.util.function.Consumer
-/**
- * Monitor for touchpad gestures that calls [gestureStateChangedCallback] when [GestureState]
- * changes. All tracked motion events should be passed to [processTouchpadEvent]
- */
-interface TouchpadGestureMonitor {
- val gestureStateChangedCallback: (GestureState) -> Unit
-
- fun processTouchpadEvent(event: MotionEvent)
+/** Monitor for touchpad gestures that can notify callback when [GestureState] changes. */
+interface TouchpadGestureMonitor : Consumer<MotionEvent> {
+ fun addGestureStateCallback(callback: (GestureState) -> Unit)
}
fun isThreeFingerTouchpadSwipe(event: MotionEvent) = isNFingerTouchpadSwipe(event, fingerCount = 3)
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/VelocityTracker.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/VelocityTracker.kt
new file mode 100644
index 000000000000..9b38eca89f15
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/VelocityTracker.kt
@@ -0,0 +1,51 @@
+/*
+ * 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.touchpad.tutorial.ui.gesture
+
+import android.view.MotionEvent
+import androidx.compose.ui.input.pointer.util.VelocityTracker1D
+import java.util.function.Consumer
+
+/** Velocity in pixels/ms. */
+@JvmInline value class Velocity(val value: Float)
+
+/**
+ * Tracks velocity for processed MotionEvents. Useful for recognizing gestures based on velocity.
+ */
+interface VelocityTracker : Consumer<MotionEvent> {
+
+ fun calculateVelocity(): Velocity
+}
+
+class VerticalVelocityTracker(
+ private val velocityTracker: VelocityTracker1D = VelocityTracker1D(isDataDifferential = false)
+) : VelocityTracker {
+
+ override fun accept(event: MotionEvent) {
+ val action = event.actionMasked
+ if (action == MotionEvent.ACTION_DOWN) {
+ velocityTracker.resetTracking()
+ }
+ velocityTracker.addDataPoint(event.eventTime, event.y)
+ }
+
+ /**
+ * Calculates velocity on demand - this calculation can be expensive so shouldn't be called
+ * after every event.
+ */
+ override fun calculateVelocity() = Velocity(velocityTracker.calculateVelocity() / 1000)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
index 1f92bc1df9c8..bbd8f3dc7e82 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
@@ -59,7 +59,6 @@ import android.view.accessibility.CaptioningManager;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import androidx.annotation.VisibleForTesting;
import androidx.lifecycle.Observer;
import com.android.internal.annotations.GuardedBy;
@@ -110,8 +109,8 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa
// It is safe to use 99 as the broadcast stream now. There are only 10+ default audio
// streams defined in AudioSystem for now and audio team is in the middle of restructure,
// no new default stream is preferred.
- @VisibleForTesting static final int DYNAMIC_STREAM_BROADCAST = 99;
- private static final int DYNAMIC_STREAM_REMOTE_START_INDEX = 100;
+ public static final int DYNAMIC_STREAM_BROADCAST = 99;
+ public static final int DYNAMIC_STREAM_REMOTE_START_INDEX = 100;
private static final AudioAttributes SONIFICIATION_VIBRATION_ATTRIBUTES =
new AudioAttributes.Builder()
.setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index 7166428d863f..7c5116dbf72c 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -537,7 +537,7 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable,
mWindow.setAttributes(lp);
mWindow.setLayout(WRAP_CONTENT, WRAP_CONTENT);
- mDialog.setContentView(R.layout.volume_dialog);
+ mDialog.setContentView(R.layout.volume_dialog_legacy);
mDialogView = mDialog.findViewById(R.id.volume_dialog);
mDialogView.setAlpha(0);
mDialogTimeoutMillis = mSecureSettings.get().getInt(
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/model/VolumeDialogStateModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/model/VolumeDialogStateModel.kt
index f1443e36d019..500cc0bf748c 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/model/VolumeDialogStateModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/model/VolumeDialogStateModel.kt
@@ -23,7 +23,7 @@ import com.android.systemui.plugins.VolumeDialogController
/** Models a state of the Volume Dialog. */
data class VolumeDialogStateModel(
- val states: Map<Int, VolumeDialogStreamStateModel>,
+ val states: Map<Int, VolumeDialogStreamModel>,
val ringerModeInternal: Int = 0,
val ringerModeExternal: Int = 0,
val zenMode: Int = 0,
@@ -39,7 +39,7 @@ data class VolumeDialogStateModel(
constructor(
legacyState: VolumeDialogController.State
) : this(
- states = legacyState.states.mapToMap { VolumeDialogStreamStateModel(it) },
+ states = legacyState.states.mapToMap { VolumeDialogStreamModel(it) },
ringerModeInternal = legacyState.ringerModeInternal,
ringerModeExternal = legacyState.ringerModeExternal,
zenMode = legacyState.zenMode,
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/model/VolumeDialogStreamStateModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/model/VolumeDialogStreamModel.kt
index a9d367da41e4..26c96eabc65f 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/model/VolumeDialogStreamStateModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/model/VolumeDialogStreamModel.kt
@@ -16,18 +16,18 @@
package com.android.systemui.volume.dialog.domain.model
-import android.annotation.IntegerRes
+import androidx.annotation.StringRes
import com.android.systemui.plugins.VolumeDialogController
/** Models a state of an audio stream of the Volume Dialog. */
-data class VolumeDialogStreamStateModel(
+data class VolumeDialogStreamModel(
val isDynamic: Boolean = false,
val level: Int = 0,
val levelMin: Int = 0,
val levelMax: Int = 0,
val muted: Boolean = false,
val muteSupported: Boolean = false,
- @IntegerRes val name: Int = 0,
+ @StringRes val name: Int = 0,
val remoteLabel: String? = null,
val routedToBluetooth: Boolean = false,
) {
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/settings/ui/binder/VolumeDialogSettingsButtonViewBinder.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/settings/ui/binder/VolumeDialogSettingsButtonViewBinder.kt
index ba08876609ae..b2f6cb332e74 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/settings/ui/binder/VolumeDialogSettingsButtonViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/settings/ui/binder/VolumeDialogSettingsButtonViewBinder.kt
@@ -34,7 +34,7 @@ constructor(private val viewModelFactory: VolumeDialogSettingsButtonViewModel.Fa
fun bind(view: View) {
with(view) {
- val button = requireViewById<View>(R.id.settings)
+ val button = requireViewById<View>(R.id.volume_dialog_settings)
repeatWhenAttached {
viewModel(
traceName = "VolumeDialogViewBinder",
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSliderInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSliderInteractor.kt
new file mode 100644
index 000000000000..81507ba7dc60
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSliderInteractor.kt
@@ -0,0 +1,54 @@
+/*
+ * 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.volume.dialog.sliders.domain.interactor
+
+import com.android.systemui.plugins.VolumeDialogController
+import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogScope
+import com.android.systemui.volume.dialog.domain.interactor.VolumeDialogStateInteractor
+import com.android.systemui.volume.dialog.domain.model.VolumeDialogStreamModel
+import com.android.systemui.volume.dialog.sliders.domain.model.VolumeDialogSliderType
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.mapNotNull
+
+/** Operates a state of particular slider of the Volume Dialog. */
+class VolumeDialogSliderInteractor
+@AssistedInject
+constructor(
+ @Assisted private val sliderType: VolumeDialogSliderType,
+ volumeDialogStateInteractor: VolumeDialogStateInteractor,
+ private val volumeDialogController: VolumeDialogController,
+) {
+
+ val slider: Flow<VolumeDialogStreamModel> =
+ volumeDialogStateInteractor.volumeDialogState.mapNotNull {
+ it.states[sliderType.audioStream]
+ }
+
+ fun setStreamVolume(userLevel: Int) {
+ volumeDialogController.setStreamVolume(sliderType.audioStream, userLevel)
+ }
+
+ @VolumeDialogScope
+ @AssistedFactory
+ interface Factory {
+
+ fun create(sliderType: VolumeDialogSliderType): VolumeDialogSliderInteractor
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSlidersInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSlidersInteractor.kt
new file mode 100644
index 000000000000..325e4c9514cf
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSlidersInteractor.kt
@@ -0,0 +1,60 @@
+/*
+ * 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.volume.dialog.sliders.domain.interactor
+
+import com.android.systemui.volume.VolumeDialogControllerImpl
+import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogScope
+import com.android.systemui.volume.dialog.domain.interactor.VolumeDialogStateInteractor
+import com.android.systemui.volume.dialog.sliders.domain.model.VolumeDialogSliderType
+import com.android.systemui.volume.dialog.sliders.domain.model.VolumeDialogSlidersModel
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
+
+/** Provides a state for the Sliders section of the Volume Dialog. */
+@VolumeDialogScope
+class VolumeDialogSlidersInteractor
+@Inject
+constructor(volumeDialogStateInteractor: VolumeDialogStateInteractor) {
+
+ val sliders: Flow<VolumeDialogSlidersModel> =
+ volumeDialogStateInteractor.volumeDialogState.map {
+ val sliderTypes: List<VolumeDialogSliderType> =
+ it.states.keys.sortedWith(StreamsSorter).map { audioStream ->
+ when {
+ audioStream == VolumeDialogControllerImpl.DYNAMIC_STREAM_BROADCAST ->
+ VolumeDialogSliderType.AudioSharingStream(audioStream)
+ audioStream >=
+ VolumeDialogControllerImpl.DYNAMIC_STREAM_REMOTE_START_INDEX ->
+ VolumeDialogSliderType.RemoteMediaStream(audioStream)
+ else -> VolumeDialogSliderType.Stream(audioStream)
+ }
+ }
+ VolumeDialogSlidersModel(
+ slider = sliderTypes.first(),
+ floatingSliders = sliderTypes.drop(1),
+ )
+ }
+
+ private object StreamsSorter : Comparator<Int> {
+
+ // TODO(b/369992924) order the streams
+ override fun compare(lhs: Int, rhs: Int): Int {
+ return lhs - rhs
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/domain/model/VolumeDialogSliderType.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/domain/model/VolumeDialogSliderType.kt
new file mode 100644
index 000000000000..18a26891e904
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/domain/model/VolumeDialogSliderType.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.dialog.sliders.domain.model
+
+/** Models different possible audio sliders shown in the Volume Dialog. */
+sealed interface VolumeDialogSliderType {
+
+ // VolumeDialogController uses the same model for every slider type. We need to follow the same
+ // logic until we refactor and decouple data and domain layers from the VolumeDialogController
+ // into separated interactors.
+ val audioStream: Int
+
+ class Stream(override val audioStream: Int) : VolumeDialogSliderType
+
+ class RemoteMediaStream(override val audioStream: Int) : VolumeDialogSliderType
+
+ class AudioSharingStream(override val audioStream: Int) : VolumeDialogSliderType
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/domain/model/VolumeDialogSlidersModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/domain/model/VolumeDialogSlidersModel.kt
new file mode 100644
index 000000000000..91a332830b75
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/domain/model/VolumeDialogSlidersModel.kt
@@ -0,0 +1,23 @@
+/*
+ * 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.volume.dialog.sliders.domain.model
+
+/** Models a state of the sliders section of the Volume Dialog. */
+data class VolumeDialogSlidersModel(
+ val slider: VolumeDialogSliderType,
+ val floatingSliders: List<VolumeDialogSliderType>,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt
new file mode 100644
index 000000000000..25a5f287c21f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt
@@ -0,0 +1,60 @@
+/*
+ * 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.volume.dialog.sliders.ui
+
+import android.view.View
+import androidx.lifecycle.viewmodel.compose.viewModel
+import com.android.systemui.lifecycle.WindowLifecycleState
+import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.lifecycle.setSnapshotBinding
+import com.android.systemui.lifecycle.viewModel
+import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogScope
+import com.android.systemui.volume.dialog.sliders.ui.viewmodel.VolumeDialogSliderViewModel
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import kotlinx.coroutines.awaitCancellation
+
+class VolumeDialogSliderViewBinder
+@AssistedInject
+constructor(@Assisted private val viewModelProvider: () -> VolumeDialogSliderViewModel) {
+
+ fun bind(view: View) {
+ with(view) {
+ repeatWhenAttached {
+ viewModel(
+ traceName = "VolumeDialogSliderViewBinder",
+ minWindowLifecycleState = WindowLifecycleState.ATTACHED,
+ factory = { viewModelProvider() },
+ ) { viewModel ->
+ setSnapshotBinding {}
+
+ awaitCancellation()
+ }
+ }
+ }
+ }
+
+ @AssistedFactory
+ @VolumeDialogScope
+ interface Factory {
+
+ fun create(
+ viewModelProvider: () -> VolumeDialogSliderViewModel
+ ): VolumeDialogSliderViewBinder
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSlidersViewBinder.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSlidersViewBinder.kt
new file mode 100644
index 000000000000..0a00f70b54f1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSlidersViewBinder.kt
@@ -0,0 +1,49 @@
+/*
+ * 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.volume.dialog.sliders.ui
+
+import android.view.View
+import com.android.systemui.lifecycle.WindowLifecycleState
+import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.lifecycle.setSnapshotBinding
+import com.android.systemui.lifecycle.viewModel
+import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogScope
+import com.android.systemui.volume.dialog.sliders.ui.viewmodel.VolumeDialogSlidersViewModel
+import javax.inject.Inject
+import kotlinx.coroutines.awaitCancellation
+
+@VolumeDialogScope
+class VolumeDialogSlidersViewBinder
+@Inject
+constructor(private val viewModelFactory: VolumeDialogSlidersViewModel.Factory) {
+
+ fun bind(view: View) {
+ with(view) {
+ repeatWhenAttached {
+ viewModel(
+ traceName = "VolumeDialogSlidersViewBinder",
+ minWindowLifecycleState = WindowLifecycleState.ATTACHED,
+ factory = { viewModelFactory.create() },
+ ) { viewModel ->
+ setSnapshotBinding {}
+
+ awaitCancellation()
+ }
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderViewModel.kt
new file mode 100644
index 000000000000..27b8f2f5adb7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderViewModel.kt
@@ -0,0 +1,41 @@
+/*
+ * 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.volume.dialog.sliders.ui.viewmodel
+
+import com.android.systemui.volume.dialog.domain.model.VolumeDialogStreamModel
+import com.android.systemui.volume.dialog.sliders.domain.interactor.VolumeDialogSliderInteractor
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import kotlinx.coroutines.flow.Flow
+
+class VolumeDialogSliderViewModel
+@AssistedInject
+constructor(@Assisted private val interactor: VolumeDialogSliderInteractor) {
+
+ val model: Flow<VolumeDialogStreamModel> = interactor.slider
+
+ fun setStreamVolume(volume: Int) {
+ interactor.setStreamVolume(volume)
+ }
+
+ @AssistedFactory
+ interface Factory {
+
+ fun create(interactor: VolumeDialogSliderInteractor): VolumeDialogSliderViewModel
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSlidersViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSlidersViewModel.kt
new file mode 100644
index 000000000000..b5b292fa4a66
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSlidersViewModel.kt
@@ -0,0 +1,73 @@
+/*
+ * 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.volume.dialog.sliders.ui.viewmodel
+
+import com.android.systemui.volume.dialog.dagger.scope.VolumeDialog
+import com.android.systemui.volume.dialog.sliders.domain.interactor.VolumeDialogSliderInteractor
+import com.android.systemui.volume.dialog.sliders.domain.interactor.VolumeDialogSlidersInteractor
+import com.android.systemui.volume.dialog.sliders.domain.model.VolumeDialogSliderType
+import com.android.systemui.volume.dialog.sliders.ui.VolumeDialogSliderViewBinder
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
+
+class VolumeDialogSlidersViewModel
+@AssistedInject
+constructor(
+ @VolumeDialog coroutineScope: CoroutineScope,
+ private val slidersInteractor: VolumeDialogSlidersInteractor,
+ private val sliderInteractorFactory: VolumeDialogSliderInteractor.Factory,
+ private val sliderViewModelFactory: VolumeDialogSliderViewModel.Factory,
+ private val sliderViewBinderFactory: VolumeDialogSliderViewBinder.Factory,
+) {
+
+ val sliders: Flow<VolumeDialogSliderUiModel> =
+ slidersInteractor.sliders
+ .distinctUntilChanged()
+ .map { slidersModel ->
+ VolumeDialogSliderUiModel(
+ sliderViewBinder = createSliderViewBinder(slidersModel.slider),
+ floatingSliderViewBinders =
+ slidersModel.floatingSliders.map(::createSliderViewBinder),
+ )
+ }
+ .stateIn(coroutineScope, SharingStarted.Eagerly, null)
+ .filterNotNull()
+
+ private fun createSliderViewBinder(type: VolumeDialogSliderType): VolumeDialogSliderViewBinder =
+ sliderViewBinderFactory.create {
+ sliderViewModelFactory.create(sliderInteractorFactory.create(type))
+ }
+
+ @AssistedFactory
+ interface Factory {
+
+ fun create(): VolumeDialogSlidersViewModel
+ }
+}
+
+/** Models slider ui */
+data class VolumeDialogSliderUiModel(
+ val sliderViewBinder: VolumeDialogSliderViewBinder,
+ val floatingSliderViewBinders: List<VolumeDialogSliderViewBinder>,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogBinder.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogBinder.kt
index 9452d8c0dcd4..77733fe33275 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogBinder.kt
@@ -50,7 +50,7 @@ constructor(
dialog.setContentView(R.layout.volume_dialog)
dialog.setCanceledOnTouchOutside(true)
- settingsButtonViewBinder.bind(dialog.requireViewById(R.id.settings_container))
+ settingsButtonViewBinder.bind(dialog.requireViewById(R.id.volume_dialog_settings))
volumeDialogViewBinder.bind(
dialog,
dialog.requireViewById(R.id.volume_dialog_container),
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
index 7889b3cd6cc3..7889b3cd6cc3 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsViewTest.kt
index 9fbe09619ff1..9fbe09619ff1 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsViewTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardModelTest.kt
index 85e8ab43b2ee..5741d64c4d32 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardModelTest.kt
@@ -122,6 +122,7 @@ class ClipboardModelTest : SysuiTestCase() {
@Test
@Throws(IOException::class)
+ @DisableFlags(FLAG_CLIPBOARD_USE_DESCRIPTION_MIMETYPE)
fun test_imageClipData_loadFailure() {
whenever(mMockContext.contentResolver).thenReturn(mMockContentResolver)
whenever(mMockContext.resources).thenReturn(mContext.resources)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/backup/CommunalBackupUtilsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/communal/data/backup/CommunalBackupUtilsTest.kt
index edc8c837bf78..edc8c837bf78 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/backup/CommunalBackupUtilsTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/communal/data/backup/CommunalBackupUtilsTest.kt
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/db/CommunalWidgetDaoTest.kt b/packages/SystemUI/tests/src/com/android/systemui/communal/data/db/CommunalWidgetDaoTest.kt
index 2312bbd2d7f8..2312bbd2d7f8 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/db/CommunalWidgetDaoTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/communal/data/db/CommunalWidgetDaoTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DisplayWindowPropertiesRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DisplayWindowPropertiesRepositoryImplTest.kt
new file mode 100644
index 000000000000..ff3186abecdc
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DisplayWindowPropertiesRepositoryImplTest.kt
@@ -0,0 +1,158 @@
+/*
+ * 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.display.data.repository
+
+import android.content.testableContext
+import android.platform.test.annotations.EnableFlags
+import android.view.Display
+import android.view.mockWindowManager
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.display.shared.model.DisplayWindowProperties
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.kosmos.unconfinedTestDispatcher
+import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.mock
+
+@EnableFlags(StatusBarConnectedDisplays.FLAG_NAME)
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class DisplayWindowPropertiesRepositoryImplTest : SysuiTestCase() {
+
+ private val kosmos = testKosmos().also { it.testDispatcher = it.unconfinedTestDispatcher }
+ private val fakeDisplayRepository = kosmos.displayRepository
+ private val testScope = kosmos.testScope
+
+ private val applicationContext = kosmos.testableContext
+ private val applicationWindowManager = kosmos.mockWindowManager
+
+ private val repo =
+ DisplayWindowPropertiesRepositoryImpl(
+ kosmos.applicationCoroutineScope,
+ applicationContext,
+ applicationWindowManager,
+ fakeDisplayRepository,
+ )
+
+ @Before
+ fun start() {
+ repo.start()
+ }
+
+ @Before
+ fun addDisplays() = runBlocking {
+ fakeDisplayRepository.addDisplay(createDisplay(DEFAULT_DISPLAY_ID))
+ fakeDisplayRepository.addDisplay(createDisplay(NON_DEFAULT_DISPLAY_ID))
+ }
+
+ @Test
+ fun get_defaultDisplayId_returnsDefaultProperties() =
+ testScope.runTest {
+ val displayContext = repo.get(DEFAULT_DISPLAY_ID, WINDOW_TYPE_FOO)
+
+ assertThat(displayContext)
+ .isEqualTo(
+ DisplayWindowProperties(
+ displayId = DEFAULT_DISPLAY_ID,
+ windowType = WINDOW_TYPE_FOO,
+ context = applicationContext,
+ windowManager = applicationWindowManager,
+ )
+ )
+ }
+
+ @Test
+ fun get_nonDefaultDisplayId_returnsNewStatusBarContext() =
+ testScope.runTest {
+ val displayContext = repo.get(NON_DEFAULT_DISPLAY_ID, WINDOW_TYPE_FOO)
+
+ assertThat(displayContext.context).isNotSameInstanceAs(applicationContext)
+ }
+
+ @Test
+ fun get_nonDefaultDisplayId_returnsNewWindowManager() =
+ testScope.runTest {
+ val displayContext = repo.get(NON_DEFAULT_DISPLAY_ID, WINDOW_TYPE_FOO)
+
+ assertThat(displayContext.windowManager).isNotSameInstanceAs(applicationWindowManager)
+ }
+
+ @Test
+ fun get_multipleCallsForDefaultDisplay_returnsSameInstance() =
+ testScope.runTest {
+ val displayContext = repo.get(DEFAULT_DISPLAY_ID, WINDOW_TYPE_FOO)
+
+ assertThat(repo.get(DEFAULT_DISPLAY_ID, WINDOW_TYPE_FOO))
+ .isSameInstanceAs(displayContext)
+ }
+
+ @Test
+ fun get_multipleCallsForNonDefaultDisplay_returnsSameInstance() =
+ testScope.runTest {
+ val displayContext = repo.get(NON_DEFAULT_DISPLAY_ID, WINDOW_TYPE_FOO)
+
+ assertThat(repo.get(NON_DEFAULT_DISPLAY_ID, WINDOW_TYPE_FOO))
+ .isSameInstanceAs(displayContext)
+ }
+
+ @Test
+ fun get_multipleCalls_differentType_returnsNewInstance() =
+ testScope.runTest {
+ val displayContext = repo.get(NON_DEFAULT_DISPLAY_ID, WINDOW_TYPE_FOO)
+
+ assertThat(repo.get(NON_DEFAULT_DISPLAY_ID, WINDOW_TYPE_BAR))
+ .isNotSameInstanceAs(displayContext)
+ }
+
+ @Test
+ fun get_afterDisplayRemoved_returnsNewInstance() =
+ testScope.runTest {
+ val displayContext = repo.get(NON_DEFAULT_DISPLAY_ID, WINDOW_TYPE_FOO)
+
+ fakeDisplayRepository.removeDisplay(NON_DEFAULT_DISPLAY_ID)
+ fakeDisplayRepository.addDisplay(createDisplay(NON_DEFAULT_DISPLAY_ID))
+
+ assertThat(repo.get(NON_DEFAULT_DISPLAY_ID, WINDOW_TYPE_FOO))
+ .isNotSameInstanceAs(displayContext)
+ }
+
+ @Test(expected = IllegalArgumentException::class)
+ fun get_nonExistingDisplayId_throws() =
+ testScope.runTest { repo.get(NON_EXISTING_DISPLAY_ID, WINDOW_TYPE_FOO) }
+
+ private fun createDisplay(displayId: Int) =
+ mock<Display> { on { getDisplayId() } doReturn displayId }
+
+ companion object {
+ private const val DEFAULT_DISPLAY_ID = Display.DEFAULT_DISPLAY
+ private const val NON_DEFAULT_DISPLAY_ID = DEFAULT_DISPLAY_ID + 1
+ private const val NON_EXISTING_DISPLAY_ID = DEFAULT_DISPLAY_ID + 2
+ private const val WINDOW_TYPE_FOO = 123
+ private const val WINDOW_TYPE_BAR = 321
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/data/repository/KeyboardRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyboard/data/repository/KeyboardRepositoryTest.kt
index 8b1341114c68..8b1341114c68 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/data/repository/KeyboardRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyboard/data/repository/KeyboardRepositoryTest.kt
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
index e1845a17a767..e1845a17a767 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt
index 5b216620ec2b..5b216620ec2b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/lifecycle/ActivatableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/lifecycle/ActivatableTest.kt
index 2ba670ceb76a..2ba670ceb76a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/lifecycle/ActivatableTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/lifecycle/ActivatableTest.kt
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/lifecycle/SysUiViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/lifecycle/SysUiViewModelTest.kt
index 73f724e7daef..73f724e7daef 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/lifecycle/SysUiViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/lifecycle/SysUiViewModelTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaSwitchingControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaSwitchingControllerTest.java
index 07e48b9da153..bf4ef509ac80 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaSwitchingControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaSwitchingControllerTest.java
@@ -125,6 +125,8 @@ public class MediaSwitchingControllerTest extends SysuiTestCase {
private static final boolean VOLUME_FIXED_TRUE = true;
private static final int LATCH_COUNT_DOWN_TIME_IN_SECOND = 5;
private static final int LATCH_TIME_OUT_TIME_IN_SECOND = 10;
+ private static final String PRODUCT_NAME_BUILTIN_MIC = "Built-in Mic";
+ private static final String PRODUCT_NAME_WIRED_HEADSET = "My Wired Headset";
@Mock
private DialogTransitionAnimator mDialogTransitionAnimator;
@@ -568,7 +570,8 @@ public class MediaSwitchingControllerTest extends SysuiTestCase {
AudioDeviceInfo.TYPE_BUILTIN_MIC,
MAX_VOLUME,
CURRENT_VOLUME,
- VOLUME_FIXED_TRUE);
+ VOLUME_FIXED_TRUE,
+ PRODUCT_NAME_BUILTIN_MIC);
final MediaDevice mediaDevice4 =
InputMediaDevice.create(
mContext,
@@ -576,7 +579,8 @@ public class MediaSwitchingControllerTest extends SysuiTestCase {
AudioDeviceInfo.TYPE_WIRED_HEADSET,
MAX_VOLUME,
CURRENT_VOLUME,
- VOLUME_FIXED_TRUE);
+ VOLUME_FIXED_TRUE,
+ PRODUCT_NAME_WIRED_HEADSET);
final List<MediaDevice> inputDevices = new ArrayList<>();
inputDevices.add(mediaDevice3);
inputDevices.add(mediaDevice4);
@@ -1355,7 +1359,8 @@ public class MediaSwitchingControllerTest extends SysuiTestCase {
AudioDeviceInfo.TYPE_BUILTIN_MIC,
MAX_VOLUME,
CURRENT_VOLUME,
- VOLUME_FIXED_TRUE);
+ VOLUME_FIXED_TRUE,
+ PRODUCT_NAME_BUILTIN_MIC);
mMediaSwitchingController.connectDevice(inputMediaDevice);
CountDownLatch latch = new CountDownLatch(LATCH_COUNT_DOWN_TIME_IN_SECOND);
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QSImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSImplTest.java
index fe1b963f6801..fe1b963f6801 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QSImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSImplTest.java
diff --git a/packages/SystemUI/multivalentTests/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 8d060e936cd9..8d060e936cd9 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/DragAndDropTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/DragAndDropTest.kt
diff --git a/packages/SystemUI/multivalentTests/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 ee1c0e99d6ac..ee1c0e99d6ac 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/ResizingTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/ResizingTest.kt
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt
index 963973588236..963973588236 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/ScreenshotDetectionControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotDetectionControllerTest.kt
index c50702868025..c50702868025 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/ScreenshotDetectionControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotDetectionControllerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerWifiTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerWifiTest.java
index 6febb91db992..7a579bacc86d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerWifiTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerWifiTest.java
@@ -58,7 +58,7 @@ public class NetworkControllerWifiTest extends NetworkControllerBaseTest {
private static final int MIN_RSSI = -100;
private static final int MAX_RSSI = -55;
private WifiInfo mWifiInfo = mock(WifiInfo.class);
- private VcnTransportInfo mVcnTransportInfo = mock(VcnTransportInfo.class);
+ private VcnTransportInfo mVcnTransportInfo = new VcnTransportInfo.Builder().build();
@Before
public void setUp() throws Exception {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
index 59fc0d157d54..87cda64ed8e5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
@@ -591,8 +591,8 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase {
ArgumentCaptor<FooterView> captor = ArgumentCaptor.forClass(FooterView.class);
verify(mStackScroller).setFooterView(captor.capture());
- assertNotNull(captor.getValue().findViewById(R.id.manage_text).hasOnClickListeners());
- assertNotNull(captor.getValue().findViewById(R.id.dismiss_text).hasOnClickListeners());
+ assertNotNull(captor.getValue().findViewById(R.id.manage_text));
+ assertNotNull(captor.getValue().findViewById(R.id.dismiss_text));
}
@Test
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 328d31014ccc..c48898aad087 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
@@ -136,6 +136,7 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() {
private val wifiLogBuffer = LogBuffer("wifi", maxSize = 100, logcatEchoTracker = mock())
private val wifiPickerTrackerCallback =
argumentCaptor<WifiPickerTracker.WifiPickerTrackerCallback>()
+ private val vcnTransportInfo = VcnTransportInfo.Builder().build()
private val testDispatcher = StandardTestDispatcher()
private val testScope = TestScope(testDispatcher)
@@ -1003,6 +1004,18 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() {
assertThat(latest).isTrue()
}
+ private fun newWifiNetwork(wifiInfo: WifiInfo): Network {
+ val network = mock<Network>()
+ val capabilities =
+ mock<NetworkCapabilities>().also {
+ whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
+ whenever(it.transportInfo).thenReturn(wifiInfo)
+ }
+ whenever(connectivityManager.getNetworkCapabilities(network)).thenReturn(capabilities)
+
+ return network
+ }
+
/** Regression test for b/272586234. */
@Test
fun hasCarrierMergedConnection_carrierMergedViaWifiWithVcnTransport_isTrue() =
@@ -1012,10 +1025,12 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() {
whenever(this.isCarrierMerged).thenReturn(true)
whenever(this.isPrimary).thenReturn(true)
}
+ val underlyingWifi = newWifiNetwork(carrierMergedInfo)
val caps =
mock<NetworkCapabilities>().also {
whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
- whenever(it.transportInfo).thenReturn(VcnTransportInfo(carrierMergedInfo))
+ whenever(it.transportInfo).thenReturn(vcnTransportInfo)
+ whenever(it.underlyingNetworks).thenReturn(listOf(underlyingWifi))
}
val latest by collectLastValue(underTest.hasCarrierMergedConnection)
@@ -1034,10 +1049,12 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() {
whenever(this.isCarrierMerged).thenReturn(true)
whenever(this.isPrimary).thenReturn(true)
}
+ val underlyingWifi = newWifiNetwork(carrierMergedInfo)
val caps =
mock<NetworkCapabilities>().also {
whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
- whenever(it.transportInfo).thenReturn(VcnTransportInfo(carrierMergedInfo))
+ whenever(it.transportInfo).thenReturn(vcnTransportInfo)
+ whenever(it.underlyingNetworks).thenReturn(listOf(underlyingWifi))
}
val latest by collectLastValue(underTest.hasCarrierMergedConnection)
@@ -1094,10 +1111,15 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() {
whenever(this.isCarrierMerged).thenReturn(true)
whenever(this.isPrimary).thenReturn(true)
}
+
+ // The Wifi network that is under the VCN network
+ val physicalWifiNetwork = newWifiNetwork(carrierMergedInfo)
+
val underlyingCapabilities =
mock<NetworkCapabilities>().also {
whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
- whenever(it.transportInfo).thenReturn(VcnTransportInfo(carrierMergedInfo))
+ whenever(it.transportInfo).thenReturn(vcnTransportInfo)
+ whenever(it.underlyingNetworks).thenReturn(listOf(physicalWifiNetwork))
}
whenever(connectivityManager.getNetworkCapabilities(underlyingCarrierMergedNetwork))
.thenReturn(underlyingCapabilities)
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 0945742fb325..88f262bec123 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
@@ -23,6 +23,7 @@ import android.net.NetworkCapabilities.TRANSPORT_CELLULAR
import android.net.NetworkCapabilities.TRANSPORT_ETHERNET
import android.net.NetworkCapabilities.TRANSPORT_VPN
import android.net.NetworkCapabilities.TRANSPORT_WIFI
+import android.net.TelephonyNetworkSpecifier
import android.net.VpnTransportInfo
import android.net.vcn.VcnTransportInfo
import android.net.wifi.WifiInfo
@@ -74,6 +75,8 @@ class ConnectivityRepositoryImplTest : SysuiTestCase() {
private val testScope = kosmos.testScope
private val tunerService = mock<TunerService>()
+ private val vcnTransportInfo = VcnTransportInfo.Builder().build()
+
@Before
fun setUp() {
createAndSetRepo()
@@ -343,6 +346,30 @@ class ConnectivityRepositoryImplTest : SysuiTestCase() {
assertThat(latest!!.wifi.isDefault).isTrue()
}
+ private fun newWifiNetwork(wifiInfo: WifiInfo): Network {
+ val network = mock<Network>()
+ val capabilities =
+ mock<NetworkCapabilities>().also {
+ whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
+ whenever(it.transportInfo).thenReturn(wifiInfo)
+ }
+ whenever(connectivityManager.getNetworkCapabilities(network)).thenReturn(capabilities)
+
+ return network
+ }
+
+ private fun newCellNetwork(subId: Int): Network {
+ val network = mock<Network>()
+ val capabilities =
+ mock<NetworkCapabilities>().also {
+ whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
+ whenever(it.networkSpecifier).thenReturn(TelephonyNetworkSpecifier(subId))
+ }
+ whenever(connectivityManager.getNetworkCapabilities(network)).thenReturn(capabilities)
+
+ return network
+ }
+
@Test
fun defaultConnections_carrierMergedViaWifiWithVcnTransport_wifiAndCarrierMergedDefault() =
testScope.runTest {
@@ -350,10 +377,12 @@ class ConnectivityRepositoryImplTest : SysuiTestCase() {
val carrierMergedInfo =
mock<WifiInfo>().apply { whenever(this.isCarrierMerged).thenReturn(true) }
+ val underlyingWifi = newWifiNetwork(carrierMergedInfo)
val capabilities =
mock<NetworkCapabilities>().also {
whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
- whenever(it.transportInfo).thenReturn(VcnTransportInfo(carrierMergedInfo))
+ whenever(it.transportInfo).thenReturn(vcnTransportInfo)
+ whenever(it.underlyingNetworks).thenReturn(listOf(underlyingWifi))
whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(false)
whenever(it.hasTransport(TRANSPORT_ETHERNET)).thenReturn(false)
}
@@ -373,10 +402,12 @@ class ConnectivityRepositoryImplTest : SysuiTestCase() {
val carrierMergedInfo =
mock<WifiInfo>().apply { whenever(this.isCarrierMerged).thenReturn(true) }
+ val underlyingWifi = newWifiNetwork(carrierMergedInfo)
val capabilities =
mock<NetworkCapabilities>().also {
whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
- whenever(it.transportInfo).thenReturn(VcnTransportInfo(carrierMergedInfo))
+ whenever(it.transportInfo).thenReturn(vcnTransportInfo)
+ whenever(it.underlyingNetworks).thenReturn(listOf(underlyingWifi))
whenever(it.hasTransport(TRANSPORT_ETHERNET)).thenReturn(false)
whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(false)
}
@@ -561,10 +592,12 @@ class ConnectivityRepositoryImplTest : SysuiTestCase() {
val underlyingCarrierMergedNetwork = mock<Network>()
val carrierMergedInfo =
mock<WifiInfo>().apply { whenever(this.isCarrierMerged).thenReturn(true) }
+ val underlyingWifi = newWifiNetwork(carrierMergedInfo)
val underlyingCapabilities =
mock<NetworkCapabilities>().also {
whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
- whenever(it.transportInfo).thenReturn(VcnTransportInfo(carrierMergedInfo))
+ whenever(it.transportInfo).thenReturn(vcnTransportInfo)
+ whenever(it.underlyingNetworks).thenReturn(listOf(underlyingWifi))
}
whenever(connectivityManager.getNetworkCapabilities(underlyingCarrierMergedNetwork))
.thenReturn(underlyingCapabilities)
@@ -645,14 +678,15 @@ class ConnectivityRepositoryImplTest : SysuiTestCase() {
@Test
fun vcnSubId_tracksVcnTransportInfo() =
testScope.runTest {
- val vcnInfo = VcnTransportInfo(SUB_1_ID)
+ val underlyingCell = newCellNetwork(SUB_1_ID)
val latest by collectLastValue(underTest.vcnSubId)
val capabilities =
mock<NetworkCapabilities>().also {
whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
- whenever(it.transportInfo).thenReturn(vcnInfo)
+ whenever(it.transportInfo).thenReturn(vcnTransportInfo)
+ whenever(it.underlyingNetworks).thenReturn(listOf(underlyingCell))
}
getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities)
@@ -663,14 +697,15 @@ class ConnectivityRepositoryImplTest : SysuiTestCase() {
@Test
fun vcnSubId_filersOutInvalid() =
testScope.runTest {
- val vcnInfo = VcnTransportInfo(INVALID_SUBSCRIPTION_ID)
+ val underlyingCell = newCellNetwork(INVALID_SUBSCRIPTION_ID)
val latest by collectLastValue(underTest.vcnSubId)
val capabilities =
mock<NetworkCapabilities>().also {
whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
- whenever(it.transportInfo).thenReturn(vcnInfo)
+ whenever(it.transportInfo).thenReturn(vcnTransportInfo)
+ whenever(it.underlyingNetworks).thenReturn(listOf(underlyingCell))
}
getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities)
@@ -703,11 +738,12 @@ class ConnectivityRepositoryImplTest : SysuiTestCase() {
val latest by collectLastValue(underTest.vcnSubId)
val wifiInfo = mock<WifiInfo>()
- val vcnInfo = VcnTransportInfo(wifiInfo)
+ val underlyingWifi = newWifiNetwork(wifiInfo)
val capabilities =
mock<NetworkCapabilities>().also {
whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
- whenever(it.transportInfo).thenReturn(vcnInfo)
+ whenever(it.transportInfo).thenReturn(vcnTransportInfo)
+ whenever(it.underlyingNetworks).thenReturn(listOf(underlyingWifi))
}
getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities)
@@ -721,14 +757,15 @@ class ConnectivityRepositoryImplTest : SysuiTestCase() {
val latest by collectLastValue(underTest.vcnSubId)
val wifiInfo = mock<WifiInfo>()
- val wifiVcnInfo = VcnTransportInfo(wifiInfo)
- val sub1VcnInfo = VcnTransportInfo(SUB_1_ID)
- val sub2VcnInfo = VcnTransportInfo(SUB_2_ID)
+ val underlyingWifi = newWifiNetwork(wifiInfo)
+ val underlyingCell1 = newCellNetwork(SUB_1_ID)
+ val underlyingCell2 = newCellNetwork(SUB_2_ID)
val capabilities =
mock<NetworkCapabilities>().also {
whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
- whenever(it.transportInfo).thenReturn(wifiVcnInfo)
+ whenever(it.transportInfo).thenReturn(vcnTransportInfo)
+ whenever(it.underlyingNetworks).thenReturn(listOf(underlyingWifi))
}
// WIFI VCN info
@@ -738,14 +775,16 @@ class ConnectivityRepositoryImplTest : SysuiTestCase() {
// Cellular VCN info with subId 1
whenever(capabilities.hasTransport(eq(TRANSPORT_CELLULAR))).thenReturn(true)
- whenever(capabilities.transportInfo).thenReturn(sub1VcnInfo)
+ whenever(capabilities.transportInfo).thenReturn(vcnTransportInfo)
+ whenever(capabilities.underlyingNetworks).thenReturn(listOf(underlyingCell1))
getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities)
assertThat(latest).isEqualTo(SUB_1_ID)
// Cellular VCN info with subId 2
- whenever(capabilities.transportInfo).thenReturn(sub2VcnInfo)
+ whenever(capabilities.transportInfo).thenReturn(vcnTransportInfo)
+ whenever(capabilities.underlyingNetworks).thenReturn(listOf(underlyingCell2))
getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities)
@@ -776,11 +815,12 @@ class ConnectivityRepositoryImplTest : SysuiTestCase() {
@Test
fun getMainOrUnderlyingWifiInfo_vcnWithWifi_hasInfo() {
val wifiInfo = mock<WifiInfo>()
- val vcnInfo = VcnTransportInfo(wifiInfo)
+ val underlyingWifi = newWifiNetwork(wifiInfo)
val capabilities =
mock<NetworkCapabilities>().also {
whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
- whenever(it.transportInfo).thenReturn(vcnInfo)
+ whenever(it.transportInfo).thenReturn(vcnTransportInfo)
+ whenever(it.underlyingNetworks).thenReturn(listOf(underlyingWifi))
}
val result = capabilities.getMainOrUnderlyingWifiInfo(connectivityManager)
@@ -860,11 +900,15 @@ class ConnectivityRepositoryImplTest : SysuiTestCase() {
fun getMainOrUnderlyingWifiInfo_cellular_underlyingVcnWithWifi_hasInfo() {
val wifiInfo = mock<WifiInfo>()
val underlyingNetwork = mock<Network>()
- val underlyingVcnInfo = VcnTransportInfo(wifiInfo)
+
+ // The Wifi network that is under the VCN network
+ val physicalWifiNetwork = newWifiNetwork(wifiInfo)
+
val underlyingWifiCapabilities =
mock<NetworkCapabilities>().also {
whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
- whenever(it.transportInfo).thenReturn(underlyingVcnInfo)
+ whenever(it.transportInfo).thenReturn(vcnTransportInfo)
+ whenever(it.underlyingNetworks).thenReturn(listOf(physicalWifiNetwork))
}
whenever(connectivityManager.getNetworkCapabilities(underlyingNetwork))
.thenReturn(underlyingWifiCapabilities)
@@ -887,11 +931,15 @@ class ConnectivityRepositoryImplTest : SysuiTestCase() {
@DisableFlags(FLAG_STATUS_BAR_ALWAYS_CHECK_UNDERLYING_NETWORKS)
fun getMainOrUnderlyingWifiInfo_notCellular_underlyingVcnWithWifi_noInfo() {
val underlyingNetwork = mock<Network>()
- val underlyingVcnInfo = VcnTransportInfo(mock<WifiInfo>())
+
+ // The Wifi network that is under the VCN network
+ val physicalWifiNetwork = newWifiNetwork(mock<WifiInfo>())
+
val underlyingWifiCapabilities =
mock<NetworkCapabilities>().also {
whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
- whenever(it.transportInfo).thenReturn(underlyingVcnInfo)
+ whenever(it.transportInfo).thenReturn(vcnTransportInfo)
+ whenever(it.underlyingNetworks).thenReturn(listOf(physicalWifiNetwork))
}
whenever(connectivityManager.getNetworkCapabilities(underlyingNetwork))
.thenReturn(underlyingWifiCapabilities)
@@ -917,10 +965,15 @@ class ConnectivityRepositoryImplTest : SysuiTestCase() {
val underlyingCarrierMergedNetwork = mock<Network>()
val carrierMergedInfo =
mock<WifiInfo>().apply { whenever(this.isCarrierMerged).thenReturn(true) }
+
+ // The Wifi network that is under the VCN network
+ val physicalWifiNetwork = newWifiNetwork(carrierMergedInfo)
+
val underlyingCapabilities =
mock<NetworkCapabilities>().also {
whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
- whenever(it.transportInfo).thenReturn(VcnTransportInfo(carrierMergedInfo))
+ whenever(it.transportInfo).thenReturn(vcnTransportInfo)
+ whenever(it.underlyingNetworks).thenReturn(listOf(physicalWifiNetwork))
}
whenever(connectivityManager.getNetworkCapabilities(underlyingCarrierMergedNetwork))
.thenReturn(underlyingCapabilities)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/window/MultiDisplayStatusBarWindowControllerStoreTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/window/MultiDisplayStatusBarWindowControllerStoreTest.kt
new file mode 100644
index 000000000000..faaa4c415d28
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/window/MultiDisplayStatusBarWindowControllerStoreTest.kt
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.window
+
+import android.platform.test.annotations.EnableFlags
+import android.view.Display
+import android.view.WindowManager
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.app.viewcapture.ViewCaptureAwareWindowManager
+import com.android.app.viewcapture.mockViewCaptureAwareWindowManager
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.display.data.repository.displayRepository
+import com.android.systemui.display.data.repository.fakeDisplayWindowPropertiesRepository
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.kosmos.unconfinedTestDispatcher
+import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.mock
+
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+@EnableFlags(StatusBarConnectedDisplays.FLAG_NAME)
+class MultiDisplayStatusBarWindowControllerStoreTest : SysuiTestCase() {
+
+ private val kosmos = testKosmos().also { it.testDispatcher = it.unconfinedTestDispatcher }
+ private val testScope = kosmos.testScope
+ private val fakeDisplayRepository = kosmos.displayRepository
+
+ private val store =
+ MultiDisplayStatusBarWindowControllerStore(
+ backgroundApplicationScope = kosmos.applicationCoroutineScope,
+ controllerFactory = kosmos.fakeStatusBarWindowControllerFactory,
+ displayWindowPropertiesRepository = kosmos.fakeDisplayWindowPropertiesRepository,
+ viewCaptureAwareWindowManagerFactory =
+ object : ViewCaptureAwareWindowManager.Factory {
+ override fun create(
+ windowManager: WindowManager
+ ): ViewCaptureAwareWindowManager {
+ return kosmos.mockViewCaptureAwareWindowManager
+ }
+ },
+ displayRepository = fakeDisplayRepository,
+ )
+
+ @Before
+ fun start() {
+ store.start()
+ }
+
+ @Before
+ fun addDisplays() = runBlocking {
+ fakeDisplayRepository.addDisplay(createDisplay(DEFAULT_DISPLAY_ID))
+ fakeDisplayRepository.addDisplay(createDisplay(NON_DEFAULT_DISPLAY_ID))
+ }
+
+ @Test
+ fun forDisplay_defaultDisplay_multipleCalls_returnsSameInstance() =
+ testScope.runTest {
+ val controller = store.defaultDisplay
+
+ assertThat(store.defaultDisplay).isSameInstanceAs(controller)
+ }
+
+ @Test
+ fun forDisplay_nonDefaultDisplay_multipleCalls_returnsSameInstance() =
+ testScope.runTest {
+ val controller = store.forDisplay(NON_DEFAULT_DISPLAY_ID)
+
+ assertThat(store.forDisplay(NON_DEFAULT_DISPLAY_ID)).isSameInstanceAs(controller)
+ }
+
+ @Test
+ fun forDisplay_nonDefaultDisplay_afterDisplayRemoved_returnsNewInstance() =
+ testScope.runTest {
+ val controller = store.forDisplay(NON_DEFAULT_DISPLAY_ID)
+
+ fakeDisplayRepository.removeDisplay(NON_DEFAULT_DISPLAY_ID)
+ fakeDisplayRepository.addDisplay(createDisplay(NON_DEFAULT_DISPLAY_ID))
+
+ assertThat(store.forDisplay(NON_DEFAULT_DISPLAY_ID)).isNotSameInstanceAs(controller)
+ }
+
+ @Test(expected = IllegalArgumentException::class)
+ fun forDisplay_nonExistingDisplayId_throws() =
+ testScope.runTest { store.forDisplay(NON_EXISTING_DISPLAY_ID) }
+
+ private fun createDisplay(displayId: Int): Display = mock {
+ on { getDisplayId() } doReturn displayId
+ }
+
+ companion object {
+ private const val DEFAULT_DISPLAY_ID = Display.DEFAULT_DISPLAY
+ private const val NON_DEFAULT_DISPLAY_ID = DEFAULT_DISPLAY_ID + 1
+ private const val NON_EXISTING_DISPLAY_ID = DEFAULT_DISPLAY_ID + 2
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/app/viewcapture/ViewCaptureAwareWindowManagerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/app/viewcapture/ViewCaptureAwareWindowManagerKosmos.kt
new file mode 100644
index 000000000000..e1c6699348a9
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/app/viewcapture/ViewCaptureAwareWindowManagerKosmos.kt
@@ -0,0 +1,26 @@
+/*
+ * 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.app.viewcapture
+
+import com.android.systemui.kosmos.Kosmos
+import org.mockito.kotlin.mock
+
+val Kosmos.mockViewCaptureAwareWindowManager by
+ Kosmos.Fixture { mock<ViewCaptureAwareWindowManager>() }
+
+var Kosmos.viewCaptureAwareWindowManager: ViewCaptureAwareWindowManager by
+ Kosmos.Fixture { mockViewCaptureAwareWindowManager }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/DisplayScopeRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/DisplayScopeRepositoryKosmos.kt
new file mode 100644
index 000000000000..ff4ba61b6965
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/DisplayScopeRepositoryKosmos.kt
@@ -0,0 +1,26 @@
+/*
+ * 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.display.data.repository
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testDispatcher
+
+val Kosmos.fakeDisplayScopeRepository by
+ Kosmos.Fixture { FakeDisplayScopeRepository(testDispatcher) }
+
+var Kosmos.displayScopeRepository: DisplayScopeRepository by
+ Kosmos.Fixture { fakeDisplayScopeRepository }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/DisplayWindowPropertiesRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/DisplayWindowPropertiesRepositoryKosmos.kt
new file mode 100644
index 000000000000..65b18c102a16
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/DisplayWindowPropertiesRepositoryKosmos.kt
@@ -0,0 +1,25 @@
+/*
+ * 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.display.data.repository
+
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.fakeDisplayWindowPropertiesRepository by
+ Kosmos.Fixture { FakeDisplayWindowPropertiesRepository() }
+
+var Kosmos.displayWindowPropertiesRepository: DisplayWindowPropertiesRepository by
+ Kosmos.Fixture { fakeDisplayWindowPropertiesRepository }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayScopeRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayScopeRepository.kt
new file mode 100644
index 000000000000..3c2592471694
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayScopeRepository.kt
@@ -0,0 +1,30 @@
+/*
+ * 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.display.data.repository
+
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+
+class FakeDisplayScopeRepository(private val dispatcher: CoroutineDispatcher) :
+ DisplayScopeRepository {
+
+ private val perDisplayScopes = mutableMapOf<Int, CoroutineScope>()
+
+ override fun scopeForDisplay(displayId: Int): CoroutineScope {
+ return perDisplayScopes.computeIfAbsent(displayId) { CoroutineScope(dispatcher) }
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayWindowPropertiesRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayWindowPropertiesRepository.kt
new file mode 100644
index 000000000000..9282f275b20b
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayWindowPropertiesRepository.kt
@@ -0,0 +1,37 @@
+/*
+ * 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.display.data.repository
+
+import com.android.systemui.display.shared.model.DisplayWindowProperties
+import com.google.common.collect.HashBasedTable
+import org.mockito.kotlin.mock
+
+class FakeDisplayWindowPropertiesRepository : DisplayWindowPropertiesRepository {
+
+ private val properties = HashBasedTable.create<Int, Int, DisplayWindowProperties>()
+
+ override fun get(displayId: Int, windowType: Int): DisplayWindowProperties {
+ return properties.get(displayId, windowType)
+ ?: DisplayWindowProperties(
+ displayId = displayId,
+ windowType = windowType,
+ context = mock(),
+ windowManager = mock(),
+ )
+ .also { properties.put(displayId, windowType, it) }
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
index f97f30383398..522c387a0b08 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
@@ -23,6 +23,7 @@ import android.os.fakeExecutorHandler
import com.android.systemui.SysuiTestCase
import com.android.systemui.bouncer.data.repository.bouncerRepository
import com.android.systemui.bouncer.data.repository.fakeKeyguardBouncerRepository
+import com.android.systemui.bouncer.domain.interactor.alternateBouncerInteractor
import com.android.systemui.bouncer.domain.interactor.simBouncerInteractor
import com.android.systemui.classifier.falsingCollector
import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
@@ -152,6 +153,7 @@ class KosmosJavaAdapter() {
val wifiInteractor by lazy { kosmos.wifiInteractor }
val fakeWifiRepository by lazy { kosmos.fakeWifiRepository }
val volumeDialogInteractor by lazy { kosmos.volumeDialogInteractor }
+ val alternateBouncerInteractor by lazy { kosmos.alternateBouncerInteractor }
val ongoingActivityChipsViewModel by lazy { kosmos.ongoingActivityChipsViewModel }
val scrimController by lazy { kosmos.scrimController }
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 cfc31c7f301c..10b073e8f331 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
@@ -17,6 +17,7 @@
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
@@ -45,5 +46,6 @@ var Kosmos.statusBarStateController: SysuiStatusBarStateController by
{ sceneContainerOcclusionInteractor },
{ keyguardClockInteractor },
{ sceneBackInteractor },
+ { alternateBouncerInteractor },
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/FakeStatusBarWindowControllerFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/FakeStatusBarWindowControllerFactory.kt
new file mode 100644
index 000000000000..10f328be12d2
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/FakeStatusBarWindowControllerFactory.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.window
+
+import android.content.Context
+import com.android.app.viewcapture.ViewCaptureAwareWindowManager
+
+class FakeStatusBarWindowControllerFactory : StatusBarWindowController.Factory {
+ override fun create(
+ context: Context,
+ viewCaptureAwareWindowManager: ViewCaptureAwareWindowManager,
+ ) = FakeStatusBarWindowController()
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/FakeStatusBarWindowControllerStore.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/FakeStatusBarWindowControllerStore.kt
new file mode 100644
index 000000000000..d19e3227027c
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/FakeStatusBarWindowControllerStore.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.window
+
+import android.view.Display
+
+class FakeStatusBarWindowControllerStore : StatusBarWindowControllerStore {
+
+ private val perDisplayControllers = mutableMapOf<Int, FakeStatusBarWindowController>()
+
+ override val defaultDisplay
+ get() = forDisplay(Display.DEFAULT_DISPLAY)
+
+ override fun forDisplay(displayId: Int): StatusBarWindowController {
+ return perDisplayControllers.computeIfAbsent(displayId) { FakeStatusBarWindowController() }
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/StatusBarWindowControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/StatusBarWindowControllerKosmos.kt
index c198b35be289..6c6f243f3953 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/StatusBarWindowControllerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/StatusBarWindowControllerKosmos.kt
@@ -21,3 +21,15 @@ import com.android.systemui.kosmos.Kosmos
val Kosmos.fakeStatusBarWindowController by Kosmos.Fixture { FakeStatusBarWindowController() }
var Kosmos.statusBarWindowController by Kosmos.Fixture { fakeStatusBarWindowController }
+
+val Kosmos.fakeStatusBarWindowControllerStore by
+ Kosmos.Fixture { FakeStatusBarWindowControllerStore() }
+
+var Kosmos.statusBarWindowControllerStore: StatusBarWindowControllerStore by
+ Kosmos.Fixture { fakeStatusBarWindowControllerStore }
+
+val Kosmos.fakeStatusBarWindowControllerFactory by
+ Kosmos.Fixture { FakeStatusBarWindowControllerFactory() }
+
+var Kosmos.statusBarWindowControllerFactory: StatusBarWindowController.Factory by
+ Kosmos.Fixture { fakeStatusBarWindowControllerFactory }
diff --git a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionDumpHelper.java b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionDumpHelper.java
index 9fc413f6224b..a832545475bf 100644
--- a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionDumpHelper.java
+++ b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionDumpHelper.java
@@ -84,15 +84,17 @@ public final class AppFunctionDumpHelper {
new FutureGlobalSearchSession(appSearchManager, Runnable::run)) {
pw.println();
- FutureSearchResults futureSearchResults =
- searchSession.search("", buildAppFunctionMetadataSearchSpec()).get();
- List<SearchResult> searchResultsList;
- do {
- searchResultsList = futureSearchResults.getNextPage().get();
- for (SearchResult searchResult : searchResultsList) {
- dumpAppFunctionMetadata(pw, searchResult);
- }
- } while (!searchResultsList.isEmpty());
+ try (FutureSearchResults futureSearchResults =
+ searchSession.search("", buildAppFunctionMetadataSearchSpec()).get(); ) {
+ List<SearchResult> searchResultsList;
+ do {
+ searchResultsList = futureSearchResults.getNextPage().get();
+ for (SearchResult searchResult : searchResultsList) {
+ dumpAppFunctionMetadata(pw, searchResult);
+ }
+ } while (!searchResultsList.isEmpty());
+ }
+
} catch (Exception e) {
pw.println("Failed to dump AppFunction state: " + e);
}
diff --git a/services/appfunctions/java/com/android/server/appfunctions/FutureSearchResults.java b/services/appfunctions/java/com/android/server/appfunctions/FutureSearchResults.java
index 45cbdb4021e5..c38ff143178f 100644
--- a/services/appfunctions/java/com/android/server/appfunctions/FutureSearchResults.java
+++ b/services/appfunctions/java/com/android/server/appfunctions/FutureSearchResults.java
@@ -24,11 +24,12 @@ import android.app.appsearch.SearchResults;
import com.android.internal.infra.AndroidFuture;
+import java.io.Closeable;
import java.io.IOException;
import java.util.List;
/** A future API wrapper of {@link android.app.appsearch.SearchResults}. */
-public interface FutureSearchResults {
+public interface FutureSearchResults extends Closeable {
/** Converts a failed app search result codes into an exception. */
@NonNull
@@ -52,4 +53,7 @@ public interface FutureSearchResults {
* there are no more results.
*/
AndroidFuture<List<SearchResult>> getNextPage();
+
+ @Override
+ void close();
}
diff --git a/services/appfunctions/java/com/android/server/appfunctions/FutureSearchResultsImpl.java b/services/appfunctions/java/com/android/server/appfunctions/FutureSearchResultsImpl.java
index c3be342043ba..c8bc538f7226 100644
--- a/services/appfunctions/java/com/android/server/appfunctions/FutureSearchResultsImpl.java
+++ b/services/appfunctions/java/com/android/server/appfunctions/FutureSearchResultsImpl.java
@@ -54,4 +54,9 @@ public class FutureSearchResultsImpl implements FutureSearchResults {
}
});
}
+
+ @Override
+ public void close() {
+ mSearchResults.close();
+ }
}
diff --git a/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncAdapter.java b/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncAdapter.java
index bbf6c0beb163..96be76975e9d 100644
--- a/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncAdapter.java
+++ b/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncAdapter.java
@@ -420,26 +420,29 @@ public class MetadataSyncAdapter {
Objects.requireNonNull(propertyPackageName);
ArrayMap<String, ArraySet<String>> packageToFunctionIds = new ArrayMap<>();
- FutureSearchResults futureSearchResults =
+ try (FutureSearchResults futureSearchResults =
searchSession
.search(
"",
buildMetadataSearchSpec(
schemaType, propertyFunctionId, propertyPackageName))
- .get();
- List<SearchResult> searchResultsList = futureSearchResults.getNextPage().get();
- // TODO(b/357551503): This could be expensive if we have more functions
- while (!searchResultsList.isEmpty()) {
- for (SearchResult searchResult : searchResultsList) {
- String packageName =
- searchResult.getGenericDocument().getPropertyString(propertyPackageName);
- String functionId =
- searchResult.getGenericDocument().getPropertyString(propertyFunctionId);
- packageToFunctionIds
- .computeIfAbsent(packageName, k -> new ArraySet<>())
- .add(functionId);
+ .get(); ) {
+ List<SearchResult> searchResultsList = futureSearchResults.getNextPage().get();
+ // TODO(b/357551503): This could be expensive if we have more functions
+ while (!searchResultsList.isEmpty()) {
+ for (SearchResult searchResult : searchResultsList) {
+ String packageName =
+ searchResult
+ .getGenericDocument()
+ .getPropertyString(propertyPackageName);
+ String functionId =
+ searchResult.getGenericDocument().getPropertyString(propertyFunctionId);
+ packageToFunctionIds
+ .computeIfAbsent(packageName, k -> new ArraySet<>())
+ .add(functionId);
+ }
+ searchResultsList = futureSearchResults.getNextPage().get();
}
- searchResultsList = futureSearchResults.getNextPage().get();
}
return packageToFunctionIds;
}
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
index 3bcca1c22c89..2968ff3d0df6 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -16,10 +16,13 @@
package com.android.server.companion.virtual;
+import static android.Manifest.permission.ADD_ALWAYS_UNLOCKED_DISPLAY;
+import static android.Manifest.permission.ADD_TRUSTED_DISPLAY;
import static android.app.admin.DevicePolicyManager.NEARBY_STREAMING_ENABLED;
import static android.app.admin.DevicePolicyManager.NEARBY_STREAMING_NOT_CONTROLLED_BY_POLICY;
import static android.app.admin.DevicePolicyManager.NEARBY_STREAMING_SAME_MANAGED_ACCOUNT_ONLY;
import static android.companion.virtual.VirtualDeviceParams.ACTIVITY_POLICY_DEFAULT_ALLOWED;
+import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_CUSTOM;
import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_DEFAULT;
import static android.companion.virtual.VirtualDeviceParams.NAVIGATION_POLICY_DEFAULT_ALLOWED;
import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_ACTIVITY;
@@ -425,6 +428,27 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
mDisplayManager = displayManager;
mDisplayManagerInternal = LocalServices.getService(DisplayManagerInternal.class);
mPowerManager = context.getSystemService(PowerManager.class);
+
+ if (mDevicePolicies.get(POLICY_TYPE_CLIPBOARD, DEVICE_POLICY_DEFAULT)
+ != DEVICE_POLICY_DEFAULT) {
+ if (mContext.checkCallingOrSelfPermission(ADD_TRUSTED_DISPLAY)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Requires ADD_TRUSTED_DISPLAY permission to "
+ + "set a custom clipboard policy.");
+ }
+ }
+
+ int flags = DEFAULT_VIRTUAL_DISPLAY_FLAGS;
+ if (mParams.getLockState() == VirtualDeviceParams.LOCK_STATE_ALWAYS_UNLOCKED) {
+ if (mContext.checkCallingOrSelfPermission(ADD_ALWAYS_UNLOCKED_DISPLAY)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Requires ADD_ALWAYS_UNLOCKED_DISPLAY permission to "
+ + "create an always unlocked virtual device.");
+ }
+ flags |= DisplayManager.VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED;
+ }
+ mBaseVirtualDisplayFlags = flags;
+
if (inputController == null) {
mInputController = new InputController(
context.getMainThreadHandler(),
@@ -467,12 +491,6 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
: mParams.getAllowedActivities();
}
- int flags = DEFAULT_VIRTUAL_DISPLAY_FLAGS;
- if (mParams.getLockState() == VirtualDeviceParams.LOCK_STATE_ALWAYS_UNLOCKED) {
- flags |= DisplayManager.VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED;
- }
- mBaseVirtualDisplayFlags = flags;
-
if (Flags.vdmCustomIme() && mParams.getInputMethodComponent() != null) {
final String imeId = mParams.getInputMethodComponent().flattenToShortString();
Slog.d(TAG, "Setting custom input method " + imeId + " as default for virtual device "
@@ -884,8 +902,12 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
synchronized (mVirtualDeviceLock) {
mDevicePolicies.put(policyType, devicePolicy);
for (int i = 0; i < mVirtualDisplays.size(); i++) {
- mVirtualDisplays.valueAt(i).getWindowPolicyController()
- .setShowInHostDeviceRecents(devicePolicy == DEVICE_POLICY_DEFAULT);
+ VirtualDisplayWrapper wrapper = mVirtualDisplays.valueAt(i);
+ if (wrapper.isTrusted()) {
+ wrapper.getWindowPolicyController()
+ .setShowInHostDeviceRecents(
+ devicePolicy == DEVICE_POLICY_DEFAULT);
+ }
}
}
break;
@@ -905,7 +927,20 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
break;
case POLICY_TYPE_CLIPBOARD:
if (Flags.crossDeviceClipboard()) {
+ if (policyType == DEVICE_POLICY_CUSTOM
+ && mContext.checkCallingOrSelfPermission(ADD_TRUSTED_DISPLAY)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Requires ADD_TRUSTED_DISPLAY permission to "
+ + "set a custom clipboard policy.");
+ }
synchronized (mVirtualDeviceLock) {
+ for (int i = 0; i < mVirtualDisplays.size(); i++) {
+ VirtualDisplayWrapper wrapper = mVirtualDisplays.valueAt(i);
+ if (!wrapper.isTrusted() && !wrapper.isMirror()) {
+ throw new SecurityException("All displays must be trusted for "
+ + "devices with custom clipboard policy.");
+ }
+ }
mDevicePolicies.put(policyType, devicePolicy);
}
}
@@ -936,8 +971,11 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
checkDisplayOwnedByVirtualDeviceLocked(displayId);
switch (policyType) {
case POLICY_TYPE_RECENTS:
- mVirtualDisplays.get(displayId).getWindowPolicyController()
- .setShowInHostDeviceRecents(devicePolicy == DEVICE_POLICY_DEFAULT);
+ VirtualDisplayWrapper wrapper = mVirtualDisplays.get(displayId);
+ if (wrapper.isTrusted()) {
+ wrapper.getWindowPolicyController()
+ .setShowInHostDeviceRecents(devicePolicy == DEVICE_POLICY_DEFAULT);
+ }
break;
case POLICY_TYPE_ACTIVITY:
mVirtualDisplays.get(displayId).getWindowPolicyController()
@@ -1247,10 +1285,13 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
try {
synchronized (mVirtualDeviceLock) {
mDefaultShowPointerIcon = showPointerIcon;
- }
- final int[] displayIds = getDisplayIds();
- for (int i = 0; i < displayIds.length; ++i) {
- mInputController.setShowPointerIcon(showPointerIcon, displayIds[i]);
+ for (int i = 0; i < mVirtualDisplays.size(); i++) {
+ VirtualDisplayWrapper wrapper = mVirtualDisplays.valueAt(i);
+ if (wrapper.isTrusted() || wrapper.isMirror()) {
+ mInputController.setShowPointerIcon(
+ mDefaultShowPointerIcon, mVirtualDisplays.keyAt(i));
+ }
+ }
}
} finally {
Binder.restoreCallingIdentity(ident);
@@ -1491,6 +1532,12 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
boolean isTrustedDisplay =
(mDisplayManagerInternal.getDisplayInfo(displayId).flags & Display.FLAG_TRUSTED)
== Display.FLAG_TRUSTED;
+ if (!isTrustedDisplay) {
+ if (getDevicePolicy(POLICY_TYPE_CLIPBOARD) != DEVICE_POLICY_DEFAULT) {
+ throw new SecurityException("All displays must be trusted for devices with custom"
+ + "clipboard policy.");
+ }
+ }
boolean showPointer;
synchronized (mVirtualDeviceLock) {
@@ -1500,7 +1547,8 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
"Virtual device already has a virtual display with ID " + displayId);
}
- PowerManager.WakeLock wakeLock = createAndAcquireWakeLockForDisplay(displayId);
+ PowerManager.WakeLock wakeLock =
+ isTrustedDisplay ? createAndAcquireWakeLockForDisplay(displayId) : null;
mVirtualDisplays.put(displayId, new VirtualDisplayWrapper(callback, gwpc, wakeLock,
isTrustedDisplay, isMirrorDisplay));
showPointer = mDefaultShowPointerIcon;
@@ -1508,14 +1556,15 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
final long token = Binder.clearCallingIdentity();
try {
- mInputController.setShowPointerIcon(showPointer, displayId);
mInputController.setMousePointerAccelerationEnabled(false, displayId);
mInputController.setDisplayEligibilityForPointerCapture(/* isEligible= */ false,
displayId);
- // WM throws a SecurityException if the display is untrusted.
if (isTrustedDisplay) {
+ mInputController.setShowPointerIcon(showPointer, displayId);
mInputController.setDisplayImePolicy(displayId,
WindowManager.DISPLAY_IME_POLICY_LOCAL);
+ } else {
+ gwpc.setShowInHostDeviceRecents(true);
}
} finally {
Binder.restoreCallingIdentity(token);
@@ -1616,6 +1665,11 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
!= PackageManager.PERMISSION_GRANTED) {
synchronized (mVirtualDeviceLock) {
checkDisplayOwnedByVirtualDeviceLocked(displayId);
+ VirtualDisplayWrapper wrapper = mVirtualDisplays.get(displayId);
+ if (!wrapper.isTrusted() && !wrapper.isMirror()) {
+ throw new SecurityException(
+ "Cannot create input device associated with an untrusted display");
+ }
}
}
}
@@ -1665,7 +1719,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
* @param virtualDisplayWrapper - VirtualDisplayWrapper to release resources for.
*/
private void releaseOwnedVirtualDisplayResources(VirtualDisplayWrapper virtualDisplayWrapper) {
- virtualDisplayWrapper.getWakeLock().release();
+ virtualDisplayWrapper.releaseWakeLock();
virtualDisplayWrapper.getWindowPolicyController().unregisterRunningAppsChangedListener(
this);
}
@@ -1833,10 +1887,10 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
VirtualDisplayWrapper(@NonNull IVirtualDisplayCallback token,
@NonNull GenericWindowPolicyController windowPolicyController,
- @NonNull PowerManager.WakeLock wakeLock, boolean isTrusted, boolean isMirror) {
+ @Nullable PowerManager.WakeLock wakeLock, boolean isTrusted, boolean isMirror) {
mToken = Objects.requireNonNull(token);
mWindowPolicyController = Objects.requireNonNull(windowPolicyController);
- mWakeLock = Objects.requireNonNull(wakeLock);
+ mWakeLock = wakeLock;
mIsTrusted = isTrusted;
mIsMirror = isMirror;
}
@@ -1845,8 +1899,10 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
return mWindowPolicyController;
}
- PowerManager.WakeLock getWakeLock() {
- return mWakeLock;
+ void releaseWakeLock() {
+ if (mWakeLock != null && mWakeLock.isHeld()) {
+ mWakeLock.release();
+ }
}
boolean isTrusted() {
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 2485626a8f42..5236b0399f25 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -3629,42 +3629,68 @@ public final class ProcessList {
}
@GuardedBy({"mService", "mProcLock"})
- private int updateLruProcessInternalLSP(ProcessRecord app, long now, int index,
- int lruSeq, String what, Object obj, ProcessRecord srcApp) {
+ private int offerLruProcessInternalLSP(ProcessRecord app, long now, String what, Object obj,
+ ProcessRecord srcApp) {
app.setLastActivityTime(now);
if (app.hasActivitiesOrRecentTasks()) {
// Don't want to touch dependent processes that are hosting activities.
- return index;
+ return -1;
}
- int lrui = mLruProcesses.lastIndexOf(app);
+ final int lrui = mLruProcesses.lastIndexOf(app);
if (lrui < 0) {
Slog.wtf(TAG, "Adding dependent process " + app + " not on LRU list: "
+ what + " " + obj + " from " + srcApp);
- return index;
}
+ return lrui;
+ }
- if (lrui >= index) {
- // Don't want to cause this to move dependent processes *back* in the
- // list as if they were less frequently used.
- return index;
- }
+ /**
+ * This method is called after the indices array is populated by the indices offered by
+ * {@link #offerLruProcessInternalLSP} to actually move the processes to the desired locations
+ * in the LRU list. Since the indices array is a SparseBooleanArray, the indices are sorted
+ * and this allows us to preserve the previous order of the processes relative to each other.
+ * Key of the indices array holds the current index of the process in the LRU list and the value
+ * is a boolean indicating whether the process is an activity process or not. Activity processes
+ * are moved to the nextActivityIndex and non-activity processes are moved to the nextIndex
+ * positions, which are provided by the caller.
+ *
+ * @param indices The indices of the processes to move.
+ * @param nextActivityIndex The next index to insert an activity process.
+ * @param nextIndex The next index to insert a non-activity process.
+ */
+ @GuardedBy({"mService", "mProcLock"})
+ private void completeLruProcessInternalLSP(SparseBooleanArray indices, int nextActivityIndex,
+ int nextIndex) {
+ for (int i = indices.size() - 1; i >= 0; i--) {
+ final int lrui = indices.keyAt(i);
+ if (lrui < 0) {
+ // Rest of the indices are invalid, we can return early.
+ return;
+ }
+ final boolean isActivity = indices.valueAt(i);
+ int index = isActivity ? nextActivityIndex : nextIndex;
- if (lrui >= mLruProcessActivityStart && index < mLruProcessActivityStart) {
- // Don't want to touch dependent processes that are hosting activities.
- return index;
- }
+ if (lrui >= index) {
+ // Don't want to cause this to move dependent processes *back* in the
+ // list as if they were less frequently used.
+ continue;
+ }
- mLruProcesses.remove(lrui);
- if (index > 0) {
+ final ProcessRecord app = mLruProcesses.remove(lrui);
index--;
+ if (DEBUG_LRU) Slog.d(TAG_LRU, "Moving dep from " + lrui + " to " + index
+ + " in LRU list: " + app);
+ mLruProcesses.add(index, app);
+ app.setLruSeq(mLruSeq);
+
+ if (isActivity) {
+ nextActivityIndex = index;
+ } else {
+ nextIndex = index;
+ }
}
- if (DEBUG_LRU) Slog.d(TAG_LRU, "Moving dep from " + lrui + " to " + index
- + " in LRU list: " + app);
- mLruProcesses.add(index, app);
- app.setLruSeq(lruSeq);
- return index;
}
/**
@@ -4058,6 +4084,15 @@ public final class ProcessList {
app.setLruSeq(mLruSeq);
+ // Key of the indices array holds the current index of the process in the LRU list and the
+ // value is a boolean indicating whether the process is an activity process or not.
+ // Activity processes will be moved to the nextActivityIndex and non-activity processes will
+ // be moved to the nextIndex positions when completeLruProcessInternalLSP is called.
+ // Since SparseBooleanArray's keys are sorted, we'll be able to keep the existing order of
+ // the processes relative to each other after the move.
+ final SparseBooleanArray indices = new SparseBooleanArray(psr.numberOfConnections()
+ + app.mProviders.numberOfProviderConnections());
+
// If the app is currently using a content provider or service,
// bump those processes as well.
for (int j = psr.numberOfConnections() - 1; j >= 0; j--) {
@@ -4069,16 +4104,12 @@ public final class ProcessList {
&& !cr.binding.service.app.isPersistent()) {
if (cr.binding.service.app.mServices.hasClientActivities()) {
if (nextActivityIndex >= 0) {
- nextActivityIndex = updateLruProcessInternalLSP(cr.binding.service.app,
- now,
- nextActivityIndex, mLruSeq,
- "service connection", cr, app);
+ indices.append(offerLruProcessInternalLSP(cr.binding.service.app, now,
+ "service connection", cr, app), true);
}
} else {
- nextIndex = updateLruProcessInternalLSP(cr.binding.service.app,
- now,
- nextIndex, mLruSeq,
- "service connection", cr, app);
+ indices.append(offerLruProcessInternalLSP(cr.binding.service.app, now,
+ "service connection", cr, app), false);
}
}
}
@@ -4086,10 +4117,11 @@ public final class ProcessList {
for (int j = ppr.numberOfProviderConnections() - 1; j >= 0; j--) {
ContentProviderRecord cpr = ppr.getProviderConnectionAt(j).provider;
if (cpr.proc != null && cpr.proc.getLruSeq() != mLruSeq && !cpr.proc.isPersistent()) {
- nextIndex = updateLruProcessInternalLSP(cpr.proc, now, nextIndex, mLruSeq,
- "provider reference", cpr, app);
+ indices.append(offerLruProcessInternalLSP(cpr.proc, now,
+ "provider reference", cpr, app), false);
}
}
+ completeLruProcessInternalLSP(indices, nextActivityIndex, nextIndex);
}
@GuardedBy(anyOf = {"mService", "mProcLock"})
diff --git a/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java b/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java
index 03c81560be89..ed41f2e881f8 100644
--- a/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java
+++ b/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java
@@ -36,6 +36,7 @@ import static android.app.AppOpsManager.UID_STATE_FOREGROUND_SERVICE;
import static android.app.AppOpsManager.UID_STATE_MAX_LAST_NON_RESTRICTED;
import static android.app.AppOpsManager.UID_STATE_NONEXISTENT;
import static android.app.AppOpsManager.UID_STATE_TOP;
+import static android.permission.flags.Flags.delayUidStateChangesFromCapabilityUpdates;
import static android.permission.flags.Flags.finishRunningOpsForKilledPackages;
import static com.android.server.appop.AppOpsUidStateTracker.processStateToUidState;
@@ -236,20 +237,26 @@ class AppOpsUidStateTrackerImpl implements AppOpsUidStateTracker {
mPendingUidStates.put(uid, uidState);
mPendingCapability.put(uid, capability);
+ boolean hasLostCapability = (prevCapability & ~capability) != 0;
+
if (procState == PROCESS_STATE_NONEXISTENT) {
mPendingGone.put(uid, true);
commitUidPendingState(uid);
- } else if (uidState < prevUidState
- || (uidState <= UID_STATE_MAX_LAST_NON_RESTRICTED
- && prevUidState > UID_STATE_MAX_LAST_NON_RESTRICTED)) {
+ } else if (uidState < prevUidState) {
// We are moving to a more important state, or the new state may be in the
// foreground and the old state is in the background, then always do it
// immediately.
commitUidPendingState(uid);
- } else if (uidState == prevUidState && capability != prevCapability) {
+ } else if (delayUidStateChangesFromCapabilityUpdates()
+ && uidState == prevUidState && !hasLostCapability) {
+ // No change on process state, but process capability hasn't decreased.
+ commitUidPendingState(uid);
+ } else if (!delayUidStateChangesFromCapabilityUpdates()
+ && uidState == prevUidState && capability != prevCapability) {
// No change on process state, but process capability has changed.
commitUidPendingState(uid);
- } else if (uidState <= UID_STATE_MAX_LAST_NON_RESTRICTED) {
+ } else if (uidState <= UID_STATE_MAX_LAST_NON_RESTRICTED
+ && (!delayUidStateChangesFromCapabilityUpdates() || !hasLostCapability)) {
// We are moving to a less important state, but it doesn't cross the restriction
// threshold.
commitUidPendingState(uid);
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index d206b20f5b25..fdf7dec31cad 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -286,7 +286,6 @@ import java.util.Objects;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.CancellationException;
-import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
@@ -8049,7 +8048,14 @@ public class AudioService extends IAudioService.Stub
}
synchronized (mAbsoluteVolumeDeviceInfoMapLock) {
if (mAbsoluteVolumeDeviceInfoMap.containsKey(audioSystemDeviceOut)) {
- return mAbsoluteVolumeDeviceInfoMap.get(audioSystemDeviceOut).mDeviceVolumeBehavior;
+ final AbsoluteVolumeDeviceInfo deviceInfo = mAbsoluteVolumeDeviceInfoMap.get(
+ audioSystemDeviceOut);
+ if (deviceInfo != null) {
+ return deviceInfo.mDeviceVolumeBehavior;
+ }
+
+ Log.e(TAG,
+ "Null absolute volume device info stored for key " + audioSystemDeviceOut);
}
}
@@ -15043,6 +15049,11 @@ public class AudioService extends IAudioService.Stub
private void addAudioSystemDeviceOutToAbsVolumeDevices(int audioSystemDeviceOut,
AbsoluteVolumeDeviceInfo info) {
+ if (info == null) {
+ Log.e(TAG, "Cannot add null absolute volume info for audioSystemDeviceOut "
+ + audioSystemDeviceOut);
+ return;
+ }
if (DEBUG_VOL) {
Log.d(TAG, "Adding DeviceType: 0x" + Integer.toHexString(audioSystemDeviceOut)
+ " to mAbsoluteVolumeDeviceInfoMap with behavior "
diff --git a/services/core/java/com/android/server/compat/PlatformCompat.java b/services/core/java/com/android/server/compat/PlatformCompat.java
index a9fe8cb01b3a..8d64383b32b9 100644
--- a/services/core/java/com/android/server/compat/PlatformCompat.java
+++ b/services/core/java/com/android/server/compat/PlatformCompat.java
@@ -242,7 +242,8 @@ public class PlatformCompat extends IPlatformCompat.Stub {
boolean enabled = true;
final int userId = UserHandle.getUserId(uid);
for (String packageName : packages) {
- final var appInfo = getApplicationInfo(packageName, userId);
+ final var appInfo =
+ fixTargetSdk(getApplicationInfo(packageName, userId), uid);
enabled &= isChangeEnabledInternal(changeId, appInfo);
}
return enabled;
@@ -261,7 +262,8 @@ public class PlatformCompat extends IPlatformCompat.Stub {
boolean enabled = true;
final int userId = UserHandle.getUserId(uid);
for (String packageName : packages) {
- final var appInfo = getApplicationInfo(packageName, userId);
+ final var appInfo =
+ fixTargetSdk(getApplicationInfo(packageName, userId), uid);
enabled &= isChangeEnabledInternalNoLogging(changeId, appInfo);
}
return enabled;
@@ -504,6 +506,15 @@ public class PlatformCompat extends IPlatformCompat.Stub {
packageName, 0, Process.myUid(), userId);
}
+ private ApplicationInfo fixTargetSdk(ApplicationInfo appInfo, int uid) {
+ // b/282922910 - we don't want apps sharing system uid and targeting
+ // older target sdk to impact all system uid apps
+ if (Flags.systemUidTargetSystemSdk() && uid == Process.SYSTEM_UID) {
+ appInfo.targetSdkVersion = Build.VERSION.SDK_INT;
+ }
+ return appInfo;
+ }
+
private void killPackage(String packageName) {
int uid = LocalServices.getService(PackageManagerInternal.class).getPackageUid(packageName,
0, UserHandle.myUserId());
diff --git a/services/core/java/com/android/server/compat/platform_compat_flags.aconfig b/services/core/java/com/android/server/compat/platform_compat_flags.aconfig
new file mode 100644
index 000000000000..fb323238c38e
--- /dev/null
+++ b/services/core/java/com/android/server/compat/platform_compat_flags.aconfig
@@ -0,0 +1,10 @@
+package: "com.android.server.compat"
+container: "system"
+
+flag {
+ name: "system_uid_target_system_sdk"
+ namespace: "app_compat"
+ description: "Compat framework feature flag for forcing all system uid apps to target system sdk"
+ bug: "29702703"
+ is_fixed_read_only: true
+}
diff --git a/services/core/java/com/android/server/crashrecovery/CrashRecoveryUtils.java b/services/core/java/com/android/server/crashrecovery/CrashRecoveryUtils.java
index 3eb3380a57a0..2e2a93776f9d 100644
--- a/services/core/java/com/android/server/crashrecovery/CrashRecoveryUtils.java
+++ b/services/core/java/com/android/server/crashrecovery/CrashRecoveryUtils.java
@@ -18,7 +18,7 @@ package com.android.server.crashrecovery;
import android.os.Environment;
import android.util.IndentingPrintWriter;
-import android.util.Slog;
+import android.util.Log;
import java.io.BufferedReader;
import java.io.File;
@@ -41,7 +41,7 @@ public class CrashRecoveryUtils {
/** Persist recovery related events in crashrecovery events file.**/
public static void logCrashRecoveryEvent(int priority, String msg) {
- Slog.println(priority, TAG, msg);
+ Log.println(priority, TAG, msg);
try {
File fname = getCrashRecoveryEventsFile();
synchronized (sFileLock) {
@@ -52,7 +52,7 @@ public class CrashRecoveryUtils {
pw.close();
}
} catch (IOException e) {
- Slog.e(TAG, "Unable to log CrashRecoveryEvents " + e.getMessage());
+ Log.e(TAG, "Unable to log CrashRecoveryEvents " + e.getMessage());
}
}
@@ -72,7 +72,7 @@ public class CrashRecoveryUtils {
pw.println(line);
}
} catch (IOException e) {
- Slog.e(TAG, "Unable to dump CrashRecoveryEvents " + e.getMessage());
+ Log.e(TAG, "Unable to dump CrashRecoveryEvents " + e.getMessage());
}
}
pw.decreaseIndent();
diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
index 8d96ba93530b..c4e10360a7cb 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
@@ -150,7 +150,7 @@ import javax.xml.datatype.DatatypeConfigurationException;
* <screenBrightnessDefault>0.65</screenBrightnessDefault>
* <powerThrottlingConfig>
* <brightnessLowestCapAllowed>0.1</brightnessLowestCapAllowed>
- * <customAnimationRateSec>0.004</customAnimationRateSec>
+ * <customAnimationRate>0.004</customAnimationRate>
* <pollingWindowMaxMillis>30000</pollingWindowMaxMillis>
* <pollingWindowMinMillis>10000</pollingWindowMinMillis>
* <powerThrottlingMap>
@@ -2193,11 +2193,11 @@ public class DisplayDeviceConfig {
return;
}
float lowestBrightnessCap = powerThrottlingCfg.getBrightnessLowestCapAllowed().floatValue();
- float customAnimationRateSec = powerThrottlingCfg.getCustomAnimationRateSec().floatValue();
+ float customAnimationRate = powerThrottlingCfg.getCustomAnimationRate().floatValue();
int pollingWindowMaxMillis = powerThrottlingCfg.getPollingWindowMaxMillis().intValue();
int pollingWindowMinMillis = powerThrottlingCfg.getPollingWindowMinMillis().intValue();
mPowerThrottlingConfigData = new PowerThrottlingConfigData(lowestBrightnessCap,
- customAnimationRateSec,
+ customAnimationRate,
pollingWindowMaxMillis,
pollingWindowMinMillis);
}
@@ -3012,16 +3012,16 @@ public class DisplayDeviceConfig {
/** Lowest brightness cap allowed for this device. */
public final float brightnessLowestCapAllowed;
/** Time take to animate brightness in seconds. */
- public final float customAnimationRateSec;
+ public final float customAnimationRate;
/** Time window for maximum polling power in milliseconds. */
public final int pollingWindowMaxMillis;
/** Time window for minimum polling power in milliseconds. */
public final int pollingWindowMinMillis;
public PowerThrottlingConfigData(float brightnessLowestCapAllowed,
- float customAnimationRateSec, int pollingWindowMaxMillis,
+ float customAnimationRate, int pollingWindowMaxMillis,
int pollingWindowMinMillis) {
this.brightnessLowestCapAllowed = brightnessLowestCapAllowed;
- this.customAnimationRateSec = customAnimationRateSec;
+ this.customAnimationRate = customAnimationRate;
this.pollingWindowMaxMillis = pollingWindowMaxMillis;
this.pollingWindowMinMillis = pollingWindowMinMillis;
}
@@ -3031,7 +3031,7 @@ public class DisplayDeviceConfig {
return "PowerThrottlingConfigData{"
+ "brightnessLowestCapAllowed: "
+ brightnessLowestCapAllowed
- + ", customAnimationRateSec: " + customAnimationRateSec
+ + ", customAnimationRate: " + customAnimationRate
+ ", pollingWindowMaxMillis: " + pollingWindowMaxMillis
+ ", pollingWindowMinMillis: " + pollingWindowMinMillis
+ "} ";
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 179ec63458dd..c7a70fafbf26 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -1769,10 +1769,11 @@ public final class DisplayManagerService extends SystemService {
flags &= ~VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP;
}
// Put the display in the virtual device's display group only if it's not a mirror display,
- // and if it doesn't need its own display group. So effectively, mirror displays go into the
- // default display group.
+ // it is a trusted display, and it doesn't need its own display group. So effectively,
+ // mirror and untrusted displays go into the default display group.
if ((flags & VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP) == 0
&& (flags & VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR) == 0
+ && (flags & VIRTUAL_DISPLAY_FLAG_TRUSTED) == VIRTUAL_DISPLAY_FLAG_TRUSTED
&& virtualDevice != null) {
flags |= VIRTUAL_DISPLAY_FLAG_DEVICE_DISPLAY_GROUP;
}
@@ -1848,9 +1849,7 @@ public final class DisplayManagerService extends SystemService {
if (callingUid != Process.SYSTEM_UID
&& (flags & VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP) != 0) {
- // The virtualDevice instance has been validated above using isValidVirtualDevice
- if (virtualDevice == null
- && !checkCallingPermission(ADD_TRUSTED_DISPLAY, "createVirtualDisplay()")) {
+ if (!checkCallingPermission(ADD_TRUSTED_DISPLAY, "createVirtualDisplay()")) {
throw new SecurityException("Requires ADD_TRUSTED_DISPLAY permission to "
+ "create a virtual display which is not in the default DisplayGroup.");
}
@@ -5667,6 +5666,11 @@ public final class DisplayManagerService extends SystemService {
displayPowerController.stylusGestureStarted(eventTime);
}
}
+
+ @Override
+ public boolean isDisplayReadyForMirroring(int displayId) {
+ return mExternalDisplayPolicy.isDisplayReadyForMirroring(displayId);
+ }
}
class DesiredDisplayModeSpecsObserver
diff --git a/services/core/java/com/android/server/display/ExternalDisplayPolicy.java b/services/core/java/com/android/server/display/ExternalDisplayPolicy.java
index 28a0b28a0167..f34d2cc6e684 100644
--- a/services/core/java/com/android/server/display/ExternalDisplayPolicy.java
+++ b/services/core/java/com/android/server/display/ExternalDisplayPolicy.java
@@ -375,6 +375,54 @@ class ExternalDisplayPolicy {
}
}
+ boolean isDisplayReadyForMirroring(int displayId) {
+ if (!mFlags.isWaitingConfirmationBeforeMirroringEnabled()) {
+ if (DEBUG) {
+ Slog.d(TAG, "isDisplayReadyForMirroring: mirroring CONFIRMED - "
+ + " flag 'waiting for confirmation before mirroring' is disabled");
+ }
+ return true;
+ }
+
+ synchronized (mSyncRoot) {
+ if (!mIsBootCompleted) {
+ if (DEBUG) {
+ Slog.d(TAG, "isDisplayReadyForMirroring: mirroring is not confirmed - "
+ + "boot is in progress");
+ }
+ return false;
+ }
+
+ var logicalDisplay = mLogicalDisplayMapper.getDisplayLocked(displayId);
+ if (logicalDisplay == null) {
+ if (DEBUG) {
+ Slog.d(TAG, "isDisplayReadyForMirroring: mirroring is not confirmed - "
+ + "logicalDisplay is null");
+ }
+ return false;
+ }
+
+ if (!isExternalDisplayLocked(logicalDisplay)) {
+ if (DEBUG) {
+ Slog.d(TAG, "isDisplayReadyForMirroring: mirroring is not confirmed - "
+ + "logicalDisplay" + logicalDisplay.getDisplayIdLocked()
+ + " type is " + logicalDisplay.getDisplayInfoLocked().type);
+ }
+ return false;
+ }
+
+ if (!logicalDisplay.isEnabledLocked()) {
+ if (DEBUG) {
+ Slog.d(TAG, "isDisplayReadyForMirroring: mirroring is not confirmed - "
+ + "logicalDisplay is disabled");
+ }
+ return false;
+ }
+ }
+
+ return true;
+ }
+
private final class SkinThermalStatusObserver extends IThermalEventListener.Stub {
@Override
public void notifyThrottling(@NonNull final Temperature temp) {
diff --git a/services/core/java/com/android/server/display/LogicalDisplayMapper.java b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
index 06a910396d6c..09fa4e6aa628 100644
--- a/services/core/java/com/android/server/display/LogicalDisplayMapper.java
+++ b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
@@ -16,6 +16,7 @@
package com.android.server.display;
+import static android.hardware.devicestate.DeviceState.PROPERTY_EMULATED_ONLY;
import static android.hardware.devicestate.DeviceState.PROPERTY_POWER_CONFIGURATION_TRIGGER_SLEEP;
import static android.hardware.devicestate.DeviceState.PROPERTY_POWER_CONFIGURATION_TRIGGER_WAKE;
import static android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STATE;
@@ -594,6 +595,13 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
boolean shouldDeviceBeWoken(DeviceState pendingState, DeviceState currentState,
boolean isInteractive, boolean isBootCompleted) {
if (mDeviceStateManagerFlags.deviceStatePropertyMigration()) {
+ if (currentState.hasProperties(PROPERTY_EMULATED_ONLY)
+ && !pendingState.hasProperties(PROPERTY_EMULATED_ONLY)) {
+ // Do not wake the device, since this transition may occur due to the user pressing
+ // the power button to exit an emulated state.
+ return false;
+ }
+
return pendingState.hasProperty(PROPERTY_POWER_CONFIGURATION_TRIGGER_WAKE)
&& !currentState.equals(INVALID_DEVICE_STATE)
&& !currentState.hasProperty(PROPERTY_POWER_CONFIGURATION_TRIGGER_WAKE)
diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessPowerClamper.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessPowerClamper.java
index 85e81f989845..1a18b004a3e2 100644
--- a/services/core/java/com/android/server/display/brightness/clamper/BrightnessPowerClamper.java
+++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessPowerClamper.java
@@ -85,7 +85,7 @@ class BrightnessPowerClamper extends
private String mDataId = null;
private float mCurrentBrightness = PowerManager.BRIGHTNESS_INVALID;
private float mCustomAnimationRateSec = DisplayBrightnessState.CUSTOM_ANIMATION_RATE_NOT_SET;
- private float mCustomAnimationRateSecDeviceConfig =
+ private float mCustomAnimationRateDeviceConfig =
DisplayBrightnessState.CUSTOM_ANIMATION_RATE_NOT_SET;
private final BiFunction<String, String, ThrottlingLevel> mDataPointMapper = (key, value) -> {
try {
@@ -117,7 +117,7 @@ class BrightnessPowerClamper extends
};
mPowerThrottlingConfigData = powerData.getPowerThrottlingConfigData();
if (mPowerThrottlingConfigData != null) {
- mCustomAnimationRateSecDeviceConfig = mPowerThrottlingConfigData.customAnimationRateSec;
+ mCustomAnimationRateDeviceConfig = mPowerThrottlingConfigData.customAnimationRate;
}
mThermalLevelListener = new ThermalLevelListener(handler);
mPmicMonitor =
@@ -228,10 +228,6 @@ class BrightnessPowerClamper extends
}
mPowerThrottlingConfigData = data.getPowerThrottlingConfigData();
- if (mPowerThrottlingConfigData == null) {
- Slog.d(TAG,
- "Power throttling data is missing for configuration data.");
- }
}
private void recalculateBrightnessCap() {
@@ -282,13 +278,13 @@ class BrightnessPowerClamper extends
mIsActive = isActive;
Slog.i(TAG, "Power clamper changing current brightness cap mBrightnessCap: "
+ mBrightnessCap + " to target brightness cap:" + targetBrightnessCap
- + " for current screen brightness: " + mCurrentBrightness);
- mBrightnessCap = targetBrightnessCap;
- Slog.i(TAG, "Power clamper changed state: thermalStatus:" + mCurrentThermalLevel
+ + " for current screen brightness: " + mCurrentBrightness + "\n"
+ + "Power clamper changed state: thermalStatus:" + mCurrentThermalLevel
+ " mCurrentThermalLevelChanged:" + mCurrentThermalLevelChanged
+ " mCurrentAvgPowerConsumed:" + mCurrentAvgPowerConsumed
- + " mCustomAnimationRateSec:" + mCustomAnimationRateSecDeviceConfig);
- mCustomAnimationRateSec = mCustomAnimationRateSecDeviceConfig;
+ + " mCustomAnimationRateSec:" + mCustomAnimationRateDeviceConfig);
+ mBrightnessCap = targetBrightnessCap;
+ mCustomAnimationRateSec = mCustomAnimationRateDeviceConfig;
mChangeListener.onChanged();
} else {
mCustomAnimationRateSec = DisplayBrightnessState.CUSTOM_ANIMATION_RATE_NOT_SET;
@@ -344,7 +340,7 @@ class BrightnessPowerClamper extends
+ mPowerThrottlingConfigData.pollingWindowMinMillis + " msec.");
return;
}
- mCustomAnimationRateSecDeviceConfig = mPowerThrottlingConfigData.customAnimationRateSec;
+ mCustomAnimationRateDeviceConfig = mPowerThrottlingConfigData.customAnimationRate;
mThermalLevelListener.start();
}
diff --git a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
index 99ced7f8bb00..b2e98bc05e75 100644
--- a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
+++ b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
@@ -217,6 +217,11 @@ public class DisplayManagerFlags {
Flags::enableUserRefreshRateForExternalDisplay
);
+ private final FlagState mEnableWaitingConfirmationBeforeMirroring = new FlagState(
+ Flags.FLAG_ENABLE_WAITING_CONFIRMATION_BEFORE_MIRRORING,
+ Flags::enableWaitingConfirmationBeforeMirroring
+ );
+
private final FlagState mEnableBatteryStatsForAllDisplays = new FlagState(
Flags.FLAG_ENABLE_BATTERY_STATS_FOR_ALL_DISPLAYS,
Flags::enableBatteryStatsForAllDisplays
@@ -445,6 +450,14 @@ public class DisplayManagerFlags {
}
/**
+ * @return {@code true} if mirroring won't be enabled until boot completes and the user enables
+ * the display.
+ */
+ public boolean isWaitingConfirmationBeforeMirroringEnabled() {
+ return mEnableWaitingConfirmationBeforeMirroring.isEnabled();
+ }
+
+ /**
* @return {@code true} if battery stats is enabled for all displays, not just the primary
* display.
*/
@@ -511,6 +524,7 @@ public class DisplayManagerFlags {
pw.println(" " + mVirtualDisplayLimit);
pw.println(" " + mNormalBrightnessForDozeParameter);
pw.println(" " + mIdleScreenConfigInSubscribingLightSensor);
+ pw.println(" " + mEnableWaitingConfirmationBeforeMirroring);
pw.println(" " + mEnableBatteryStatsForAllDisplays);
pw.println(" " + mBlockAutobrightnessChangesOnStylusUsage);
pw.println(" " + mIsUserRefreshRateForExternalDisplayEnabled);
diff --git a/services/core/java/com/android/server/display/feature/display_flags.aconfig b/services/core/java/com/android/server/display/feature/display_flags.aconfig
index 2f04d9e5fdbb..df626385c5cc 100644
--- a/services/core/java/com/android/server/display/feature/display_flags.aconfig
+++ b/services/core/java/com/android/server/display/feature/display_flags.aconfig
@@ -367,6 +367,17 @@ flag {
}
flag {
+ name: "enable_waiting_confirmation_before_mirroring"
+ namespace: "display_manager"
+ description: "Allow ContentRecorder checking whether user confirmed mirroring after boot"
+ bug: "361698995"
+ is_fixed_read_only: true
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "enable_battery_stats_for_all_displays"
namespace: "display_manager"
description: "Flag to enable battery stats for all displays."
diff --git a/services/core/java/com/android/server/input/InputSettingsObserver.java b/services/core/java/com/android/server/input/InputSettingsObserver.java
index d70bd8b17ddf..d1a6d3b9bb00 100644
--- a/services/core/java/com/android/server/input/InputSettingsObserver.java
+++ b/services/core/java/com/android/server/input/InputSettingsObserver.java
@@ -63,6 +63,12 @@ class InputSettingsObserver extends ContentObserver {
mObservers = Map.ofEntries(
Map.entry(Settings.System.getUriFor(Settings.System.POINTER_SPEED),
(reason) -> updateMousePointerSpeed()),
+ Map.entry(Settings.System.getUriFor(
+ Settings.System.MOUSE_REVERSE_VERTICAL_SCROLLING),
+ (reason) -> updateMouseReverseVerticalScrolling()),
+ Map.entry(Settings.System.getUriFor(
+ Settings.System.MOUSE_SWAP_PRIMARY_BUTTON),
+ (reason) -> updateMouseSwapPrimaryButton()),
Map.entry(Settings.System.getUriFor(Settings.System.TOUCHPAD_POINTER_SPEED),
(reason) -> updateTouchpadPointerSpeed()),
Map.entry(Settings.System.getUriFor(Settings.System.TOUCHPAD_NATURAL_SCROLLING),
@@ -163,6 +169,16 @@ class InputSettingsObserver extends ContentObserver {
mNative.setPointerSpeed(constrainPointerSpeedValue(speed));
}
+ private void updateMouseReverseVerticalScrolling() {
+ mNative.setMouseReverseVerticalScrollingEnabled(
+ InputSettings.isMouseReverseVerticalScrollingEnabled(mContext));
+ }
+
+ private void updateMouseSwapPrimaryButton() {
+ mNative.setMouseSwapPrimaryButtonEnabled(
+ InputSettings.isMouseSwapPrimaryButtonEnabled(mContext));
+ }
+
private void updateTouchpadPointerSpeed() {
mNative.setTouchpadPointerSpeed(
constrainPointerSpeedValue(InputSettings.getTouchpadPointerSpeed(mContext)));
diff --git a/services/core/java/com/android/server/input/NativeInputManagerService.java b/services/core/java/com/android/server/input/NativeInputManagerService.java
index 4404d63e02fc..21e8bccd2883 100644
--- a/services/core/java/com/android/server/input/NativeInputManagerService.java
+++ b/services/core/java/com/android/server/input/NativeInputManagerService.java
@@ -127,6 +127,10 @@ interface NativeInputManagerService {
void setMousePointerAccelerationEnabled(int displayId, boolean enabled);
+ void setMouseReverseVerticalScrollingEnabled(boolean enabled);
+
+ void setMouseSwapPrimaryButtonEnabled(boolean enabled);
+
void setTouchpadPointerSpeed(int speed);
void setTouchpadNaturalScrollingEnabled(boolean enabled);
@@ -388,6 +392,12 @@ interface NativeInputManagerService {
public native void setMousePointerAccelerationEnabled(int displayId, boolean enabled);
@Override
+ public native void setMouseReverseVerticalScrollingEnabled(boolean enabled);
+
+ @Override
+ public native void setMouseSwapPrimaryButtonEnabled(boolean enabled);
+
+ @Override
public native void setTouchpadPointerSpeed(int speed);
@Override
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index f0fb33eaaee7..35b517118aab 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -84,6 +84,7 @@ import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.content.pm.UserInfo;
import android.content.res.Resources;
+import android.hardware.display.DisplayManagerInternal;
import android.hardware.input.InputManager;
import android.inputmethodservice.InputMethodService;
import android.inputmethodservice.InputMethodService.BackDispositionMode;
@@ -119,6 +120,7 @@ import android.util.Printer;
import android.util.Slog;
import android.util.SparseArray;
import android.util.proto.ProtoOutputStream;
+import android.view.Display;
import android.view.InputChannel;
import android.view.InputDevice;
import android.view.MotionEvent;
@@ -448,6 +450,8 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl.
private AudioManagerInternal mAudioManagerInternal = null;
@Nullable
private VirtualDeviceManagerInternal mVdmInternal = null;
+ @Nullable
+ private DisplayManagerInternal mDisplayManagerInternal = null;
// Mapping from deviceId to the device-specific imeId for that device.
@GuardedBy("ImfLock.class")
@@ -2165,7 +2169,18 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl.
final var bindingController = getInputMethodBindingController(userId);
final int oldDeviceId = bindingController.getDeviceIdToShowIme();
final int displayIdToShowIme = bindingController.getDisplayIdToShowIme();
- final int newDeviceId = mVdmInternal.getDeviceIdForDisplayId(displayIdToShowIme);
+ int newDeviceId = mVdmInternal.getDeviceIdForDisplayId(displayIdToShowIme);
+ if (newDeviceId != DEVICE_ID_DEFAULT) {
+ // Only show custom IME on trusted displays.
+ if (mDisplayManagerInternal == null) {
+ mDisplayManagerInternal = LocalServices.getService(DisplayManagerInternal.class);
+ }
+ int displayFlags = mDisplayManagerInternal.getDisplayInfo(displayIdToShowIme).flags;
+ if ((displayFlags & Display.FLAG_TRUSTED) != Display.FLAG_TRUSTED) {
+ // If the display is not trusted, fallback to the default device IME.
+ newDeviceId = DEVICE_ID_DEFAULT;
+ }
+ }
bindingController.setDeviceIdToShowIme(newDeviceId);
if (newDeviceId == DEVICE_ID_DEFAULT) {
if (oldDeviceId == DEVICE_ID_DEFAULT) {
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverySnapshotStorage.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverySnapshotStorage.java
index c02b103f1d33..404c8411f750 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverySnapshotStorage.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverySnapshotStorage.java
@@ -19,7 +19,6 @@ package com.android.server.locksettings.recoverablekeystore.storage;
import android.annotation.Nullable;
import android.os.Environment;
import android.security.keystore.recovery.KeyChainSnapshot;
-import android.util.Log;
import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
@@ -29,9 +28,11 @@ import com.android.server.locksettings.recoverablekeystore.serialization
import com.android.server.locksettings.recoverablekeystore.serialization
.KeyChainSnapshotParserException;
import com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSerializer;
+import com.android.server.utils.Slogf;
import java.io.File;
import java.io.FileInputStream;
+import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.security.cert.CertificateEncodingException;
@@ -81,12 +82,14 @@ public class RecoverySnapshotStorage {
public synchronized void put(int uid, KeyChainSnapshot snapshot) {
mSnapshotByUid.put(uid, snapshot);
- try {
- writeToDisk(uid, snapshot);
+ File snapshotFile = getSnapshotFile(uid);
+ try (FileOutputStream fileOutputStream = new FileOutputStream(snapshotFile)) {
+ KeyChainSnapshotSerializer.serialize(snapshot, fileOutputStream);
} catch (IOException | CertificateEncodingException e) {
- Log.e(TAG,
- String.format(Locale.US, "Error persisting snapshot for %d to disk", uid),
- e);
+ // If we fail to write the latest snapshot, we should delete any older snapshot that
+ // happens to be around. Otherwise snapshot syncs might end up going 'back in time'.
+ snapshotFile.delete();
+ Slogf.e(TAG, e, "Error persisting snapshot for %d to disk", uid);
}
}
@@ -100,10 +103,17 @@ public class RecoverySnapshotStorage {
return snapshot;
}
- try {
- return readFromDisk(uid);
+ File snapshotFile = getSnapshotFile(uid);
+ try (FileInputStream fileInputStream = new FileInputStream(snapshotFile)) {
+ return KeyChainSnapshotDeserializer.deserialize(fileInputStream);
+ } catch (FileNotFoundException e) {
+ Slogf.i(TAG, "Snapshot for uid %d not found", uid);
+ return null;
} catch (IOException | KeyChainSnapshotParserException e) {
- Log.e(TAG, String.format(Locale.US, "Error reading snapshot for %d from disk", uid), e);
+ // If we fail to read the latest snapshot, we should delete it in case it is in some way
+ // corrupted. We can regenerate snapshots anyway.
+ snapshotFile.delete();
+ Slogf.e(TAG, e, "Error reading snapshot for %d from disk", uid);
return null;
}
}
@@ -116,50 +126,6 @@ public class RecoverySnapshotStorage {
getSnapshotFile(uid).delete();
}
- /**
- * Writes the snapshot for recovery agent {@code uid} to disk.
- *
- * @throws IOException if an IO error occurs writing to disk.
- */
- private void writeToDisk(int uid, KeyChainSnapshot snapshot)
- throws IOException, CertificateEncodingException {
- File snapshotFile = getSnapshotFile(uid);
-
- try (
- FileOutputStream fileOutputStream = new FileOutputStream(snapshotFile)
- ) {
- KeyChainSnapshotSerializer.serialize(snapshot, fileOutputStream);
- } catch (IOException | CertificateEncodingException e) {
- // If we fail to write the latest snapshot, we should delete any older snapshot that
- // happens to be around. Otherwise snapshot syncs might end up going 'back in time'.
- snapshotFile.delete();
- throw e;
- }
- }
-
- /**
- * Reads the last snapshot for recovery agent {@code uid} from disk.
- *
- * @return The snapshot, or null if none existed.
- * @throws IOException if an IO error occurs reading from disk.
- */
- @Nullable
- private KeyChainSnapshot readFromDisk(int uid)
- throws IOException, KeyChainSnapshotParserException {
- File snapshotFile = getSnapshotFile(uid);
-
- try (
- FileInputStream fileInputStream = new FileInputStream(snapshotFile)
- ) {
- return KeyChainSnapshotDeserializer.deserialize(fileInputStream);
- } catch (IOException | KeyChainSnapshotParserException e) {
- // If we fail to read the latest snapshot, we should delete it in case it is in some way
- // corrupted. We can regenerate snapshots anyway.
- snapshotFile.delete();
- throw e;
- }
- }
-
private File getSnapshotFile(int uid) {
File folder = getStorageFolder();
String fileName = getSnapshotFileName(uid);
diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java
index 4665a72b0b06..b228bb9a89c3 100644
--- a/services/core/java/com/android/server/pm/ComputerEngine.java
+++ b/services/core/java/com/android/server/pm/ComputerEngine.java
@@ -5144,10 +5144,28 @@ public class ComputerEngine implements Computer {
}
updateOwnerPackageName = installSource.mUpdateOwnerPackageName;
+
+ if (DEBUG_INSTALL) {
+ Log.d(TAG, "ComputerEngine getInstallSourceInfo updateOwnerPackageName = "
+ + updateOwnerPackageName + ", callingUid = " + callingUid + ", packageName = "
+ + packageName + ", userId = " + userId);
+ }
+
if (updateOwnerPackageName != null) {
final PackageStateInternal ps = mSettings.getPackage(updateOwnerPackageName);
final boolean isCallerSystemOrUpdateOwner = callingUid == Process.SYSTEM_UID
|| isCallerSameApp(updateOwnerPackageName, callingUid);
+
+ if (DEBUG_INSTALL) {
+ Log.d(TAG, "ComputerEngine getInstallSourceInfo ps = "
+ + ps + ", isCallerSystemOrUpdateOwner =" + isCallerSystemOrUpdateOwner
+ + ", isCallerSameApp = "
+ + isCallerSameApp(updateOwnerPackageName, callingUid) + ", filter = "
+ + shouldFilterApplicationIncludingUninstalled(ps, callingUid, userId)
+ + ", FromManagedUserOrProfile = "
+ + isCallerFromManagedUserOrProfile(userId));
+ }
+
// Except for package visibility filtering, we also hide update owner if the installer
// is in the managed user or profile. As we don't enforce the update ownership for the
// managed user and profile, knowing there's an update owner is meaningless in that
@@ -5159,6 +5177,11 @@ public class ComputerEngine implements Computer {
}
}
+ if (DEBUG_INSTALL) {
+ Log.d(TAG, "ComputerEngine getInstallSourceInfo updateOwnerPackageName = "
+ + updateOwnerPackageName);
+ }
+
if (installSource.mIsInitiatingPackageUninstalled) {
// We can't check visibility in the usual way, since the initiating package is no
// longer present. So we apply simpler rules to whether to expose the info:
diff --git a/services/core/java/com/android/server/pm/InstallRequest.java b/services/core/java/com/android/server/pm/InstallRequest.java
index 1f79ac0afdac..089bbb7b0022 100644
--- a/services/core/java/com/android/server/pm/InstallRequest.java
+++ b/services/core/java/com/android/server/pm/InstallRequest.java
@@ -16,7 +16,6 @@
package com.android.server.pm;
-import static android.content.pm.Flags.improveInstallFreeze;
import static android.content.pm.PackageInstaller.SessionParams.USER_ACTION_UNSPECIFIED;
import static android.content.pm.PackageManager.INSTALL_REASON_UNKNOWN;
import static android.content.pm.PackageManager.INSTALL_SCENARIO_DEFAULT;
@@ -1050,13 +1049,13 @@ final class InstallRequest {
}
public void onFreezeStarted() {
- if (mPackageMetrics != null && improveInstallFreeze()) {
+ if (mPackageMetrics != null) {
mPackageMetrics.onStepStarted(PackageMetrics.STEP_FREEZE_INSTALL);
}
}
public void onFreezeCompleted() {
- if (mPackageMetrics != null && improveInstallFreeze()) {
+ if (mPackageMetrics != null) {
mPackageMetrics.onStepFinished(PackageMetrics.STEP_FREEZE_INSTALL);
}
}
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 8bab9de903ba..708e0679d6d4 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -1101,7 +1101,7 @@ public class UserManagerService extends IUserManager.Stub {
if (android.multiuser.Flags.cachesNotInvalidatedAtStartReadOnly()) {
UserManager.invalidateIsUserUnlockedCache();
UserManager.invalidateQuietModeEnabledCache();
- UserManager.invalidateUserSerialNumberCache();
+ UserManager.invalidateCacheOnUserListChange();
}
}
@@ -4448,7 +4448,7 @@ public class UserManagerService extends IUserManager.Stub {
if (userData != null) {
synchronized (mUsersLock) {
- mUsers.put(userData.info.id, userData);
+ addUserDataLU(userData);
if (mNextSerialNumber < 0
|| mNextSerialNumber <= userData.info.id) {
mNextSerialNumber = userData.info.id + 1;
@@ -5724,7 +5724,7 @@ public class UserManagerService extends IUserManager.Stub {
userData.info = userInfo;
userData.userProperties = new UserProperties(
userTypeDetails.getDefaultUserPropertiesReference());
- mUsers.put(userId, userData);
+ addUserDataLU(userData);
}
writeUserLP(userData);
writeUserListLP();
@@ -6138,7 +6138,7 @@ public class UserManagerService extends IUserManager.Stub {
final UserData userData = new UserData();
userData.info = userInfo;
synchronized (mUsersLock) {
- mUsers.put(userInfo.id, userData);
+ addUserDataLU(userData);
}
updateUserIds();
return userData;
@@ -6148,8 +6148,7 @@ public class UserManagerService extends IUserManager.Stub {
@VisibleForTesting
void removeUserInfo(@UserIdInt int userId) {
synchronized (mUsersLock) {
- UserManager.invalidateUserSerialNumberCache();
- mUsers.remove(userId);
+ removeUserDataLU(userId);
}
}
@@ -6579,8 +6578,7 @@ public class UserManagerService extends IUserManager.Stub {
// Remove this user from the list
synchronized (mUsersLock) {
- UserManager.invalidateUserSerialNumberCache();
- mUsers.remove(userId);
+ removeUserDataLU(userId);
mIsUserManaged.delete(userId);
}
synchronized (mUserStates) {
@@ -6969,6 +6967,26 @@ public class UserManagerService extends IUserManager.Stub {
}
/**
+ * Adding user data to mUsers list in one place to invalidate related caches.
+ */
+ @GuardedBy("mUsersLock")
+ private void addUserDataLU(UserData userData) {
+ if (android.multiuser.Flags.invalidateCacheOnUsersChangedReadOnly()) {
+ UserManager.invalidateCacheOnUserListChange();
+ }
+ mUsers.put(userData.info.id, userData);
+ }
+
+ /**
+ * Removing user data to mUsers list in one place to invalidate related caches.
+ */
+ @GuardedBy("mUsersLock")
+ private void removeUserDataLU(@UserIdInt int userId) {
+ UserManager.invalidateCacheOnUserListChange();
+ mUsers.remove(userId);
+ }
+
+ /**
* Caches the list of user ids in an array, adjusting the array size when necessary.
*/
private void updateUserIds() {
diff --git a/services/core/java/com/android/server/wm/ContentRecorder.java b/services/core/java/com/android/server/wm/ContentRecorder.java
index bc33946ef618..0b5872b3e601 100644
--- a/services/core/java/com/android/server/wm/ContentRecorder.java
+++ b/services/core/java/com/android/server/wm/ContentRecorder.java
@@ -285,6 +285,11 @@ final class ContentRecorder implements WindowContainerListener {
}
}
+ private boolean isDisplayReadyForMirroring() {
+ return mDisplayContent.getDisplayInfo().type != Display.TYPE_EXTERNAL
+ || mDisplayContent.mWmService.mDisplayManagerInternal.isDisplayReadyForMirroring(
+ mDisplayContent.getDisplayId());
+ }
/**
* Ensure recording does not fall back to the display stack; ensure the recording is stopped
@@ -335,7 +340,7 @@ final class ContentRecorder implements WindowContainerListener {
return;
}
- if (mContentRecordingSession.isWaitingForConsent()) {
+ if (mContentRecordingSession.isWaitingForConsent() || !isDisplayReadyForMirroring()) {
ProtoLog.v(WM_DEBUG_CONTENT_RECORDING, "Content Recording: waiting to record, so do "
+ "nothing");
return;
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index efca90217e83..248ed1a58b75 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -337,6 +337,8 @@ public:
int32_t getMousePointerSpeed();
void setPointerSpeed(int32_t speed);
void setMousePointerAccelerationEnabled(ui::LogicalDisplayId displayId, bool enabled);
+ void setMouseReverseVerticalScrollingEnabled(bool enabled);
+ void setMouseSwapPrimaryButtonEnabled(bool enabled);
void setTouchpadPointerSpeed(int32_t speed);
void setTouchpadNaturalScrollingEnabled(bool enabled);
void setTouchpadTapToClickEnabled(bool enabled);
@@ -482,6 +484,12 @@ private:
// True if stylus button reporting through motion events is enabled.
bool stylusButtonMotionEventsEnabled{true};
+ // True if mouse vertical scrolling is reversed.
+ bool mouseReverseVerticalScrollingEnabled{false};
+
+ // True if the mouse primary button is swapped (left/right buttons).
+ bool mouseSwapPrimaryButtonEnabled{false};
+
// The touchpad pointer speed, as a number from -7 (slowest) to 7 (fastest).
int32_t touchpadPointerSpeed{0};
@@ -762,6 +770,10 @@ void NativeInputManager::getReaderConfiguration(InputReaderConfiguration* outCon
outConfig->defaultPointerDisplayId = mLocked.pointerDisplayId;
+ outConfig->mouseReverseVerticalScrollingEnabled =
+ mLocked.mouseReverseVerticalScrollingEnabled;
+ outConfig->mouseSwapPrimaryButtonEnabled = mLocked.mouseSwapPrimaryButtonEnabled;
+
outConfig->touchpadPointerSpeed = mLocked.touchpadPointerSpeed;
outConfig->touchpadNaturalScrollingEnabled = mLocked.touchpadNaturalScrollingEnabled;
outConfig->touchpadTapToClickEnabled = mLocked.touchpadTapToClickEnabled;
@@ -1317,6 +1329,36 @@ int32_t NativeInputManager::getMousePointerSpeed() {
return mLocked.pointerSpeed;
}
+void NativeInputManager::setMouseReverseVerticalScrollingEnabled(bool enabled) {
+ { // acquire lock
+ std::scoped_lock _l(mLock);
+
+ if (mLocked.mouseReverseVerticalScrollingEnabled == enabled) {
+ return;
+ }
+
+ mLocked.mouseReverseVerticalScrollingEnabled = enabled;
+ } // release lock
+
+ mInputManager->getReader().requestRefreshConfiguration(
+ InputReaderConfiguration::Change::MOUSE_SETTINGS);
+}
+
+void NativeInputManager::setMouseSwapPrimaryButtonEnabled(bool enabled) {
+ { // acquire lock
+ std::scoped_lock _l(mLock);
+
+ if (mLocked.mouseSwapPrimaryButtonEnabled == enabled) {
+ return;
+ }
+
+ mLocked.mouseSwapPrimaryButtonEnabled = enabled;
+ } // release lock
+
+ mInputManager->getReader().requestRefreshConfiguration(
+ InputReaderConfiguration::Change::MOUSE_SETTINGS);
+}
+
void NativeInputManager::setPointerSpeed(int32_t speed) {
{ // acquire lock
std::scoped_lock _l(mLock);
@@ -3002,6 +3044,18 @@ static jint nativeGetLastUsedInputDeviceId(JNIEnv* env, jobject nativeImplObj) {
return static_cast<jint>(im->getInputManager()->getReader().getLastUsedInputDeviceId());
}
+static void nativeSetMouseReverseVerticalScrollingEnabled(JNIEnv* env, jobject nativeImplObj,
+ bool enabled) {
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
+ im->setMouseReverseVerticalScrollingEnabled(enabled);
+}
+
+static void nativeSetMouseSwapPrimaryButtonEnabled(JNIEnv* env, jobject nativeImplObj,
+ bool enabled) {
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
+ im->setMouseSwapPrimaryButtonEnabled(enabled);
+}
+
// ----------------------------------------------------------------------------
static const JNINativeMethod gInputManagerMethods[] = {
@@ -3048,6 +3102,9 @@ static const JNINativeMethod gInputManagerMethods[] = {
{"setPointerSpeed", "(I)V", (void*)nativeSetPointerSpeed},
{"setMousePointerAccelerationEnabled", "(IZ)V",
(void*)nativeSetMousePointerAccelerationEnabled},
+ {"setMouseReverseVerticalScrollingEnabled", "(Z)V",
+ (void*)nativeSetMouseReverseVerticalScrollingEnabled},
+ {"setMouseSwapPrimaryButtonEnabled", "(Z)V", (void*)nativeSetMouseSwapPrimaryButtonEnabled},
{"setTouchpadPointerSpeed", "(I)V", (void*)nativeSetTouchpadPointerSpeed},
{"setTouchpadNaturalScrollingEnabled", "(Z)V",
(void*)nativeSetTouchpadNaturalScrollingEnabled},
diff --git a/services/core/jni/com_android_server_utils_AnrTimer.cpp b/services/core/jni/com_android_server_utils_AnrTimer.cpp
index 2836d46b0f1a..2add5b09f15b 100644
--- a/services/core/jni/com_android_server_utils_AnrTimer.cpp
+++ b/services/core/jni/com_android_server_utils_AnrTimer.cpp
@@ -349,7 +349,7 @@ class AnrTimerTracer {
return nullptr;
}
- // Return the currently watched pids. The lock must be held.
+ // Return the currently watched pids as a comma-separated list. The lock must be held.
std::string watchedPidsLocked() const {
if (watched_.size() == 0) return "none";
bool first = true;
@@ -357,6 +357,7 @@ class AnrTimerTracer {
for (auto i = watched_.cbegin(); i != watched_.cend(); i++) {
if (first) {
result += StringPrintf("%d", *i);
+ first = false;
} else {
result += StringPrintf(",%d", *i);
}
diff --git a/services/core/xsd/display-device-config/display-device-config.xsd b/services/core/xsd/display-device-config/display-device-config.xsd
index 776de2e52061..20c69ac93f63 100644
--- a/services/core/xsd/display-device-config/display-device-config.xsd
+++ b/services/core/xsd/display-device-config/display-device-config.xsd
@@ -464,7 +464,7 @@
<xs:annotation name="nonnull"/>
<xs:annotation name="final"/>
</xs:element>
- <xs:element name="customAnimationRateSec" type="nonNegativeDecimal" minOccurs="0" maxOccurs="1">
+ <xs:element name="customAnimationRate" type="nonNegativeDecimal" minOccurs="0" maxOccurs="1">
<xs:annotation name="nonnull"/>
<xs:annotation name="final"/>
</xs:element>
diff --git a/services/core/xsd/display-device-config/schema/current.txt b/services/core/xsd/display-device-config/schema/current.txt
index 110a5a20da6a..a8f18b3d2eee 100644
--- a/services/core/xsd/display-device-config/schema/current.txt
+++ b/services/core/xsd/display-device-config/schema/current.txt
@@ -345,12 +345,12 @@ package com.android.server.display.config {
public class PowerThrottlingConfig {
ctor public PowerThrottlingConfig();
method @NonNull public final java.math.BigDecimal getBrightnessLowestCapAllowed();
- method @NonNull public final java.math.BigDecimal getCustomAnimationRateSec();
+ method @NonNull public final java.math.BigDecimal getCustomAnimationRate();
method @NonNull public final java.math.BigInteger getPollingWindowMaxMillis();
method @NonNull public final java.math.BigInteger getPollingWindowMinMillis();
method public final java.util.List<com.android.server.display.config.PowerThrottlingMap> getPowerThrottlingMap();
method public final void setBrightnessLowestCapAllowed(@NonNull java.math.BigDecimal);
- method public final void setCustomAnimationRateSec(@NonNull java.math.BigDecimal);
+ method public final void setCustomAnimationRate(@NonNull java.math.BigDecimal);
method public final void setPollingWindowMaxMillis(@NonNull java.math.BigInteger);
method public final void setPollingWindowMinMillis(@NonNull java.math.BigInteger);
}
diff --git a/services/tests/appfunctions/src/com/android/server/appfunctions/MetadataSyncAdapterTest.kt b/services/tests/appfunctions/src/com/android/server/appfunctions/MetadataSyncAdapterTest.kt
index 5758da858423..96fb4535b992 100644
--- a/services/tests/appfunctions/src/com/android/server/appfunctions/MetadataSyncAdapterTest.kt
+++ b/services/tests/appfunctions/src/com/android/server/appfunctions/MetadataSyncAdapterTest.kt
@@ -391,6 +391,10 @@ class MetadataSyncAdapterTest {
return AndroidFuture.completedFuture(mutableListOf())
}
}
+
+ override fun close() {
+ Log.d("FakeRuntimeMetadataSearchSession", "Closing session")
+ }
}
return AndroidFuture.completedFuture(futureSearchResults)
}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
index 3976ea4fc86e..2220f439f6c8 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
@@ -265,7 +265,7 @@ public final class DisplayDeviceConfigTest {
mDisplayDeviceConfig.getPowerThrottlingConfigData();
assertNotNull(powerThrottlingConfigData);
assertEquals(0.1f, powerThrottlingConfigData.brightnessLowestCapAllowed, SMALL_DELTA);
- assertEquals(15f, powerThrottlingConfigData.customAnimationRateSec, SMALL_DELTA);
+ assertEquals(15f, powerThrottlingConfigData.customAnimationRate, SMALL_DELTA);
assertEquals(20000, powerThrottlingConfigData.pollingWindowMaxMillis);
assertEquals(10000, powerThrottlingConfigData.pollingWindowMinMillis);
}
@@ -1299,7 +1299,7 @@ public final class DisplayDeviceConfigTest {
private String getPowerThrottlingConfig() {
return "<powerThrottlingConfig >\n"
+ "<brightnessLowestCapAllowed>0.1</brightnessLowestCapAllowed>\n"
- + "<customAnimationRateSec>15</customAnimationRateSec>\n"
+ + "<customAnimationRate>15</customAnimationRate>\n"
+ "<pollingWindowMaxMillis>20000</pollingWindowMaxMillis>\n"
+ "<pollingWindowMinMillis>10000</pollingWindowMinMillis>\n"
+ "<powerThrottlingMap>\n"
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
index 1a1c8e50ba0a..94eab9cda968 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
@@ -25,6 +25,7 @@ import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_
import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY;
import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP;
import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION;
+import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_TRUSTED;
import static android.view.ContentRecordingSession.RECORD_CONTENT_DISPLAY;
import static android.view.ContentRecordingSession.RECORD_CONTENT_TASK;
import static android.view.Display.HdrCapabilities.HDR_TYPE_INVALID;
@@ -1068,9 +1069,9 @@ public class DisplayManagerServiceTest {
firstDisplayId);
}
- /** Tests that the virtual device is created in a device display group. */
+ /** Tests that a trusted virtual display is created in a device display group. */
@Test
- public void createVirtualDisplay_addsDisplaysToDeviceDisplayGroups() throws Exception {
+ public void createVirtualDisplay_addsTrustedDisplaysToDeviceDisplayGroups() throws Exception {
DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
DisplayManagerInternal localService = displayManager.new LocalService();
@@ -1081,12 +1082,16 @@ public class DisplayManagerServiceTest {
IVirtualDevice virtualDevice = mock(IVirtualDevice.class);
when(virtualDevice.getDeviceId()).thenReturn(1);
when(mIVirtualDeviceManager.isValidVirtualDeviceId(1)).thenReturn(true);
+
+ when(mContext.checkCallingPermission(ADD_TRUSTED_DISPLAY))
+ .thenReturn(PackageManager.PERMISSION_GRANTED);
+
// Create a first virtual display. A display group should be created for this display on the
// virtual device.
final VirtualDisplayConfig.Builder builder1 =
new VirtualDisplayConfig.Builder(VIRTUAL_DISPLAY_NAME, 600, 800, 320)
- .setUniqueId("uniqueId --- device display group 1");
-
+ .setUniqueId("uniqueId --- device display group")
+ .setFlags(VIRTUAL_DISPLAY_FLAG_TRUSTED);
int displayId1 =
localService.createVirtualDisplay(
builder1.build(),
@@ -1097,12 +1102,14 @@ public class DisplayManagerServiceTest {
verify(mMockProjectionService, never()).setContentRecordingSession(any(),
nullable(IMediaProjection.class));
int displayGroupId1 = localService.getDisplayInfo(displayId1).displayGroupId;
+ assertNotEquals(displayGroupId1, Display.DEFAULT_DISPLAY_GROUP);
// Create a second virtual display. This should be added to the previously created display
// group.
final VirtualDisplayConfig.Builder builder2 =
new VirtualDisplayConfig.Builder(VIRTUAL_DISPLAY_NAME, 600, 800, 320)
- .setUniqueId("uniqueId --- device display group 1");
+ .setUniqueId("uniqueId --- device display group")
+ .setFlags(VIRTUAL_DISPLAY_FLAG_TRUSTED);
int displayId2 =
localService.createVirtualDisplay(
@@ -1121,6 +1128,36 @@ public class DisplayManagerServiceTest {
displayGroupId2);
}
+ /** Tests that an untrusted virtual display is created in the default display group. */
+ @Test
+ public void createVirtualDisplay_addsUntrustedDisplayToDefaultDisplayGroups() throws Exception {
+ DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
+ DisplayManagerInternal localService = displayManager.new LocalService();
+
+ registerDefaultDisplays(displayManager);
+ when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
+
+ IVirtualDevice virtualDevice = mock(IVirtualDevice.class);
+ when(virtualDevice.getDeviceId()).thenReturn(1);
+ when(mIVirtualDeviceManager.isValidVirtualDeviceId(1)).thenReturn(true);
+ // Create the virtual display. It is untrusted, so it should go into the default group.
+ final VirtualDisplayConfig.Builder builder =
+ new VirtualDisplayConfig.Builder(VIRTUAL_DISPLAY_NAME, 600, 800, 320)
+ .setUniqueId("uniqueId --- device display group");
+
+ int displayId =
+ localService.createVirtualDisplay(
+ builder.build(),
+ mMockAppToken /* callback */,
+ virtualDevice /* virtualDeviceToken */,
+ mock(DisplayWindowPolicyController.class),
+ PACKAGE_NAME);
+ verify(mMockProjectionService, never()).setContentRecordingSession(any(),
+ nullable(IMediaProjection.class));
+ int displayGroupId = localService.getDisplayInfo(displayId).displayGroupId;
+ assertEquals(displayGroupId, Display.DEFAULT_DISPLAY_GROUP);
+ }
+
/**
* Tests that the virtual display is not added to the device display group when
* VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP is set.
@@ -1138,11 +1175,15 @@ public class DisplayManagerServiceTest {
when(virtualDevice.getDeviceId()).thenReturn(1);
when(mIVirtualDeviceManager.isValidVirtualDeviceId(1)).thenReturn(true);
+ when(mContext.checkCallingPermission(ADD_TRUSTED_DISPLAY))
+ .thenReturn(PackageManager.PERMISSION_GRANTED);
+
// Create a first virtual display. A display group should be created for this display on the
// virtual device.
final VirtualDisplayConfig.Builder builder1 =
new VirtualDisplayConfig.Builder(VIRTUAL_DISPLAY_NAME, 600, 800, 320)
- .setUniqueId("uniqueId --- device display group");
+ .setUniqueId("uniqueId --- device display group")
+ .setFlags(VIRTUAL_DISPLAY_FLAG_TRUSTED);
int displayId1 =
localService.createVirtualDisplay(
@@ -1154,12 +1195,14 @@ public class DisplayManagerServiceTest {
verify(mMockProjectionService, never()).setContentRecordingSession(any(),
nullable(IMediaProjection.class));
int displayGroupId1 = localService.getDisplayInfo(displayId1).displayGroupId;
+ assertNotEquals(displayGroupId1, Display.DEFAULT_DISPLAY_GROUP);
// Create a second virtual display. With the flag VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP,
// the display should not be added to the previously created display group.
final VirtualDisplayConfig.Builder builder2 =
new VirtualDisplayConfig.Builder(VIRTUAL_DISPLAY_NAME, 600, 800, 320)
- .setFlags(VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP)
+ .setFlags(VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP
+ | VIRTUAL_DISPLAY_FLAG_TRUSTED)
.setUniqueId("uniqueId --- own display group");
when(mIVirtualDeviceManager.isValidVirtualDeviceId(1)).thenReturn(true);
@@ -1174,6 +1217,7 @@ public class DisplayManagerServiceTest {
verify(mMockProjectionService, never()).setContentRecordingSession(any(),
nullable(IMediaProjection.class));
int displayGroupId2 = localService.getDisplayInfo(displayId2).displayGroupId;
+ assertNotEquals(displayGroupId2, Display.DEFAULT_DISPLAY_GROUP);
assertNotEquals(
"Display 1 should be in the device display group and display 2 in its own display"
@@ -1208,7 +1252,8 @@ public class DisplayManagerServiceTest {
final VirtualDisplayConfig deviceDisplayGroupDisplayConfig =
new VirtualDisplayConfig.Builder(VIRTUAL_DISPLAY_NAME, 600, 800, 320)
.setUniqueId("uniqueId --- device display group 1")
- .setFlags(VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED)
+ .setFlags(VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED
+ | VIRTUAL_DISPLAY_FLAG_TRUSTED)
.build();
int deviceDisplayGroupDisplayId =
@@ -1235,6 +1280,7 @@ public class DisplayManagerServiceTest {
.setUniqueId("uniqueId --- own display group 1")
.setFlags(
VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED
+ | VIRTUAL_DISPLAY_FLAG_TRUSTED
| VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP)
.build();
@@ -1852,7 +1898,7 @@ public class DisplayManagerServiceTest {
/**
* Tests that specifying VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP is allowed when the permission
- * ADD_TRUSTED_DISPLAY is granted.
+ * ADD_TRUSTED_DISPLAY is granted and that display is not in the default display group.
*/
@Test
public void testOwnDisplayGroup_allowCreationWithAddTrustedDisplayPermission()
@@ -1881,6 +1927,9 @@ public class DisplayManagerServiceTest {
DisplayDeviceInfo ddi = displayManager.getDisplayDeviceInfoInternal(displayId);
assertNotNull(ddi);
assertNotEquals(0, ddi.flags & DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP);
+
+ int displayGroupId = bs.getDisplayInfo(displayId).displayGroupId;
+ assertNotEquals(displayGroupId, Display.DEFAULT_DISPLAY_GROUP);
}
/**
@@ -1915,11 +1964,11 @@ public class DisplayManagerServiceTest {
}
/**
- * Tests that specifying VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP is allowed when called with
- * a virtual device, even if ADD_TRUSTED_DISPLAY is not granted.
+ * Tests that specifying VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP is not allowed when called with
+ * a virtual device, if ADD_TRUSTED_DISPLAY is not granted.
*/
@Test
- public void testOwnDisplayGroup_allowCreationWithVirtualDevice() throws Exception {
+ public void testOwnDisplayGroup_disallowCreationWithVirtualDevice() throws Exception {
DisplayManagerService displayManager =
new DisplayManagerService(mContext, mBasicInjector);
DisplayManagerInternal localService = displayManager.new LocalService();
@@ -1940,16 +1989,16 @@ public class DisplayManagerServiceTest {
when(virtualDevice.getDeviceId()).thenReturn(1);
when(mIVirtualDeviceManager.isValidVirtualDeviceId(1)).thenReturn(true);
- int displayId = localService.createVirtualDisplay(builder.build(),
- mMockAppToken /* callback */, virtualDevice /* virtualDeviceToken */,
- mock(DisplayWindowPolicyController.class), PACKAGE_NAME);
- verify(mMockProjectionService, never()).setContentRecordingSession(any(),
- nullable(IMediaProjection.class));
- performTraversalInternal(displayManager);
- displayManager.getDisplayHandler().runWithScissors(() -> {}, 0 /* now */);
- DisplayDeviceInfo ddi = displayManager.getDisplayDeviceInfoInternal(displayId);
- assertNotNull(ddi);
- assertNotEquals(0, ddi.flags & DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP);
+ try {
+ localService.createVirtualDisplay(builder.build(),
+ mMockAppToken /* callback */, virtualDevice /* virtualDeviceToken */,
+ mock(DisplayWindowPolicyController.class), PACKAGE_NAME);
+ fail("Creating virtual display with VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP without "
+ + "ADD_TRUSTED_DISPLAY permission should throw SecurityException even if "
+ + "called with a virtual device.");
+ } catch (SecurityException e) {
+ // SecurityException is expected
+ }
}
/**
diff --git a/services/tests/displayservicetests/src/com/android/server/display/ExternalDisplayPolicyTest.java b/services/tests/displayservicetests/src/com/android/server/display/ExternalDisplayPolicyTest.java
index f72816889c3b..782262d3f7c9 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/ExternalDisplayPolicyTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/ExternalDisplayPolicyTest.java
@@ -18,6 +18,7 @@ package com.android.server.display;
import static android.hardware.display.DisplayManagerGlobal.EVENT_DISPLAY_CONNECTED;
import static android.view.Display.TYPE_EXTERNAL;
+import static android.view.Display.TYPE_INTERNAL;
import static com.google.common.truth.Truth.assertThat;
@@ -36,6 +37,7 @@ import android.os.IThermalEventListener;
import android.os.IThermalService;
import android.os.RemoteException;
import android.os.Temperature;
+import android.view.Display;
import android.view.DisplayInfo;
import androidx.test.filters.SmallTest;
@@ -97,6 +99,8 @@ public class ExternalDisplayPolicyTest {
@Mock
private LogicalDisplay mMockedLogicalDisplay;
@Mock
+ private LogicalDisplay mMockedDefaultDisplay;
+ @Mock
private DisplayNotificationManager mMockedDisplayNotificationManager;
@Mock
private ExternalDisplayStatsService mMockedExternalDisplayStatsService;
@@ -141,6 +145,15 @@ public class ExternalDisplayPolicyTest {
when(mMockedLogicalDisplay.getDisplayInfoLocked()).thenReturn(mockedLogicalDisplayInfo);
when(mMockedLogicalDisplayMapper.getDisplayLocked(EXTERNAL_DISPLAY_ID)).thenReturn(
mMockedLogicalDisplay);
+
+ // Initialize default logical display
+ when(mMockedDefaultDisplay.getDisplayIdLocked()).thenReturn(Display.DEFAULT_DISPLAY);
+ when(mMockedDefaultDisplay.isEnabledLocked()).thenReturn(true);
+ final var mockedDefaultDisplayInfo = new DisplayInfo();
+ mockedDefaultDisplayInfo.type = TYPE_INTERNAL;
+ when(mMockedDefaultDisplay.getDisplayInfoLocked()).thenReturn(mockedDefaultDisplayInfo);
+ when(mMockedLogicalDisplayMapper.getDisplayLocked(Display.DEFAULT_DISPLAY)).thenReturn(
+ mMockedDefaultDisplay);
}
@Test
@@ -293,6 +306,52 @@ public class ExternalDisplayPolicyTest {
verify(mMockedLogicalDisplayMapper, never()).forEachLocked(any());
}
+ @Test
+ public void testMirroringAlwaysConfirmedByUser_flagDisabled() {
+ when(mMockedFlags.isWaitingConfirmationBeforeMirroringEnabled()).thenReturn(false);
+ assertThat(mExternalDisplayPolicy.isDisplayReadyForMirroring(EXTERNAL_DISPLAY_ID)).isTrue();
+ }
+
+ @Test
+ public void testMirroringConfirmed_afterBootForEnabledDisplay() {
+ when(mMockedFlags.isWaitingConfirmationBeforeMirroringEnabled()).thenReturn(true);
+ mExternalDisplayPolicy.onBootCompleted();
+ assertThat(mExternalDisplayPolicy.isDisplayReadyForMirroring(EXTERNAL_DISPLAY_ID))
+ .isTrue();
+ }
+
+ @Test
+ public void testMirroringNotConfirmed_afterBootForDisabledDisplay() {
+ when(mMockedFlags.isWaitingConfirmationBeforeMirroringEnabled()).thenReturn(true);
+ mExternalDisplayPolicy.onBootCompleted();
+ when(mMockedLogicalDisplay.isEnabledLocked()).thenReturn(false);
+ assertThat(mExternalDisplayPolicy.isDisplayReadyForMirroring(EXTERNAL_DISPLAY_ID))
+ .isFalse();
+ }
+
+ @Test
+ public void testMirroringNeverConfirmed_forNonExternalDisplays() {
+ when(mMockedFlags.isWaitingConfirmationBeforeMirroringEnabled()).thenReturn(true);
+ mExternalDisplayPolicy.onBootCompleted();
+ assertThat(mExternalDisplayPolicy.isDisplayReadyForMirroring(Display.DEFAULT_DISPLAY))
+ .isFalse();
+ }
+
+ @Test
+ public void testMirroringNeverConfirmed_forNonExistingDisplays() {
+ when(mMockedFlags.isWaitingConfirmationBeforeMirroringEnabled()).thenReturn(true);
+ mExternalDisplayPolicy.onBootCompleted();
+ assertThat(mExternalDisplayPolicy.isDisplayReadyForMirroring(Display.INVALID_DISPLAY))
+ .isFalse();
+ }
+
+ @Test
+ public void testMirroringNeverConfirmed_duringBoot() {
+ when(mMockedFlags.isWaitingConfirmationBeforeMirroringEnabled()).thenReturn(true);
+ assertThat(mExternalDisplayPolicy.isDisplayReadyForMirroring(EXTERNAL_DISPLAY_ID))
+ .isFalse();
+ }
+
private void setTemperature(final IThermalEventListener thermalEventListener,
final List<Temperature> temperature) throws RemoteException {
for (var t : temperature) {
diff --git a/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java b/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java
index 1729ad5ff19f..d831cf8a3643 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java
@@ -121,6 +121,8 @@ public class LogicalDisplayMapperTest {
Set.of(DeviceState.PROPERTY_POWER_CONFIGURATION_TRIGGER_WAKE), Collections.emptySet());
private static final DeviceState DEVICE_STATE_OPEN = createDeviceState(2, "Two",
Set.of(DeviceState.PROPERTY_POWER_CONFIGURATION_TRIGGER_WAKE), Collections.emptySet());
+ private static final DeviceState DEVICE_STATE_EMULATED = createDeviceState(3, "Three",
+ Set.of(DeviceState.PROPERTY_EMULATED_ONLY), Collections.emptySet());
private static final int FLAG_GO_TO_SLEEP_ON_FOLD = 0;
private static final int FLAG_GO_TO_SLEEP_FLAG_SOFT_SLEEP = 2;
private static int sNextNonDefaultDisplayId = DEFAULT_DISPLAY + 1;
@@ -686,6 +688,14 @@ public class LogicalDisplayMapperTest {
}
@Test
+ public void testDeviceShouldNotBeWokenWhenExitingEmulatedState() {
+ assertFalse(mLogicalDisplayMapper.shouldDeviceBeWoken(DEVICE_STATE_OPEN,
+ DEVICE_STATE_EMULATED,
+ /* isInteractive= */false,
+ /* isBootCompleted= */true));
+ }
+
+ @Test
public void testDeviceShouldBePutToSleep() {
assertTrue(mLogicalDisplayMapper.shouldDeviceBePutToSleep(DEVICE_STATE_CLOSED,
DEVICE_STATE_OPEN,
diff --git a/services/tests/displayservicetests/src/com/android/server/display/PersistentDataStoreTest.java b/services/tests/displayservicetests/src/com/android/server/display/PersistentDataStoreTest.java
index 5676a388acff..6d14065b6248 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/PersistentDataStoreTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/PersistentDataStoreTest.java
@@ -459,7 +459,6 @@ public class PersistentDataStoreTest {
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
newInjector.setReadStream(bais);
newDataStore.loadIfNeeded();
- assertNotNull(newDataStore.getUserPreferredRefreshRate(testDisplayDevice));
assertEquals(85.3f, mDataStore.getUserPreferredRefreshRate(testDisplayDevice), 01.f);
assertEquals(85.3f, newDataStore.getUserPreferredRefreshRate(testDisplayDevice), 0.1f);
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUidStateTrackerTest.java b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUidStateTrackerTest.java
index 1731590be3c9..026e72f117b4 100644
--- a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUidStateTrackerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUidStateTrackerTest.java
@@ -31,12 +31,14 @@ import static android.app.AppOpsManager.UID_STATE_FOREGROUND;
import static android.app.AppOpsManager.UID_STATE_FOREGROUND_SERVICE;
import static android.app.AppOpsManager.UID_STATE_MAX_LAST_NON_RESTRICTED;
import static android.app.AppOpsManager.UID_STATE_TOP;
+import static android.permission.flags.Flags.delayUidStateChangesFromCapabilityUpdates;
import static com.android.server.appop.AppOpsUidStateTracker.processStateToUidState;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
@@ -325,6 +327,10 @@ public class AppOpsUidStateTrackerTest {
.backgroundState()
.update();
+ assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_RECORD_AUDIO, MODE_FOREGROUND));
+ assertEquals(MODE_IGNORED,
+ mIntf.evalMode(UID, OP_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO, MODE_FOREGROUND));
+
procStateBuilder(UID)
.backgroundState()
.microphoneCapability()
@@ -342,10 +348,23 @@ public class AppOpsUidStateTrackerTest {
.microphoneCapability()
.update();
+ assertEquals(MODE_ALLOWED, mIntf.evalMode(UID, OP_RECORD_AUDIO, MODE_FOREGROUND));
+ assertEquals(MODE_ALLOWED,
+ mIntf.evalMode(UID, OP_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO, MODE_FOREGROUND));
+
procStateBuilder(UID)
.backgroundState()
.update();
+ if (delayUidStateChangesFromCapabilityUpdates()) {
+ mClock.advanceTime(mConstants.BG_STATE_SETTLE_TIME - 1);
+ assertEquals(MODE_ALLOWED, mIntf.evalMode(UID, OP_RECORD_AUDIO, MODE_FOREGROUND));
+ assertEquals(MODE_ALLOWED,
+ mIntf.evalMode(UID, OP_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO,
+ MODE_FOREGROUND));
+
+ mClock.advanceTime(1);
+ }
assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_RECORD_AUDIO, MODE_FOREGROUND));
assertEquals(MODE_IGNORED,
mIntf.evalMode(UID, OP_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO, MODE_FOREGROUND));
@@ -357,6 +376,8 @@ public class AppOpsUidStateTrackerTest {
.backgroundState()
.update();
+ assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_CAMERA, MODE_FOREGROUND));
+
procStateBuilder(UID)
.backgroundState()
.cameraCapability()
@@ -372,10 +393,18 @@ public class AppOpsUidStateTrackerTest {
.cameraCapability()
.update();
+ assertEquals(MODE_ALLOWED, mIntf.evalMode(UID, OP_CAMERA, MODE_FOREGROUND));
+
procStateBuilder(UID)
.backgroundState()
.update();
+ if (delayUidStateChangesFromCapabilityUpdates()) {
+ mClock.advanceTime(mConstants.BG_STATE_SETTLE_TIME - 1);
+ assertEquals(MODE_ALLOWED, mIntf.evalMode(UID, OP_CAMERA, MODE_FOREGROUND));
+
+ mClock.advanceTime(1);
+ }
assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_CAMERA, MODE_FOREGROUND));
}
@@ -385,6 +414,9 @@ public class AppOpsUidStateTrackerTest {
.backgroundState()
.update();
+ assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_COARSE_LOCATION, MODE_FOREGROUND));
+ assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_FINE_LOCATION, MODE_FOREGROUND));
+
procStateBuilder(UID)
.backgroundState()
.locationCapability()
@@ -401,15 +433,55 @@ public class AppOpsUidStateTrackerTest {
.locationCapability()
.update();
+ assertEquals(MODE_ALLOWED, mIntf.evalMode(UID, OP_COARSE_LOCATION, MODE_FOREGROUND));
+ assertEquals(MODE_ALLOWED, mIntf.evalMode(UID, OP_FINE_LOCATION, MODE_FOREGROUND));
+
procStateBuilder(UID)
.backgroundState()
.update();
+ if (delayUidStateChangesFromCapabilityUpdates()) {
+ mClock.advanceTime(mConstants.BG_STATE_SETTLE_TIME - 1);
+ assertEquals(MODE_ALLOWED, mIntf.evalMode(UID, OP_COARSE_LOCATION, MODE_FOREGROUND));
+ assertEquals(MODE_ALLOWED, mIntf.evalMode(UID, OP_FINE_LOCATION, MODE_FOREGROUND));
+
+ mClock.advanceTime(1);
+ }
assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_COARSE_LOCATION, MODE_FOREGROUND));
assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_FINE_LOCATION, MODE_FOREGROUND));
}
@Test
+ public void testProcStateChangesAndStaysUnrestrictedAndCapabilityRemoved() {
+ assumeTrue(delayUidStateChangesFromCapabilityUpdates());
+
+ procStateBuilder(UID)
+ .topState()
+ .microphoneCapability()
+ .cameraCapability()
+ .locationCapability()
+ .update();
+
+ assertEquals(MODE_ALLOWED, mIntf.evalMode(UID, OP_RECORD_AUDIO, MODE_FOREGROUND));
+ assertEquals(MODE_ALLOWED, mIntf.evalMode(UID, OP_CAMERA, MODE_FOREGROUND));
+ assertEquals(MODE_ALLOWED, mIntf.evalMode(UID, OP_COARSE_LOCATION, MODE_FOREGROUND));
+
+ procStateBuilder(UID)
+ .foregroundState()
+ .update();
+
+ mClock.advanceTime(mConstants.TOP_STATE_SETTLE_TIME - 1);
+ assertEquals(MODE_ALLOWED, mIntf.evalMode(UID, OP_RECORD_AUDIO, MODE_FOREGROUND));
+ assertEquals(MODE_ALLOWED, mIntf.evalMode(UID, OP_CAMERA, MODE_FOREGROUND));
+ assertEquals(MODE_ALLOWED, mIntf.evalMode(UID, OP_COARSE_LOCATION, MODE_FOREGROUND));
+
+ mClock.advanceTime(1);
+ assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_RECORD_AUDIO, MODE_FOREGROUND));
+ assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_CAMERA, MODE_FOREGROUND));
+ assertEquals(MODE_IGNORED, mIntf.evalMode(UID, OP_COARSE_LOCATION, MODE_FOREGROUND));
+ }
+
+ @Test
public void testVisibleAppWidget() {
procStateBuilder(UID)
.backgroundState()
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
index 584fd6270c69..40b9c61a0597 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
@@ -25,6 +25,7 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSess
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
import static com.android.server.job.Flags.FLAG_COUNT_QUOTA_FIX;
+import static com.android.server.job.Flags.FLAG_ENFORCE_QUOTA_POLICY_TO_FGS_JOBS;
import static com.android.server.job.JobSchedulerService.ACTIVE_INDEX;
import static com.android.server.job.JobSchedulerService.EXEMPTED_INDEX;
import static com.android.server.job.JobSchedulerService.FREQUENT_INDEX;
@@ -303,6 +304,12 @@ public class QuotaControllerTest {
}
}
+ private int getProcessStateQuotaFreeThreshold() {
+ synchronized (mQuotaController.mLock) {
+ return mQuotaController.getProcessStateQuotaFreeThreshold();
+ }
+ }
+
private void setProcessState(int procState) {
setProcessState(procState, mSourceUid);
}
@@ -315,7 +322,7 @@ public class QuotaControllerTest {
final boolean contained = foregroundUids.get(uid);
mUidObserver.onUidStateChanged(uid, procState, 0,
ActivityManager.PROCESS_CAPABILITY_NONE);
- if (procState <= ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE) {
+ if (procState <= getProcessStateQuotaFreeThreshold()) {
if (!contained) {
verify(foregroundUids, timeout(2 * SECOND_IN_MILLIS).times(1))
.put(eq(uid), eq(true));
@@ -1371,7 +1378,7 @@ public class QuotaControllerTest {
}
setDischarging();
- setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
+ setProcessState(getProcessStateQuotaFreeThreshold());
synchronized (mQuotaController.mLock) {
assertEquals(timeUntilQuotaConsumedMs,
mQuotaController.getMaxJobExecutionTimeMsLocked((job)));
@@ -1473,7 +1480,7 @@ public class QuotaControllerTest {
}
setDischarging();
- setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
+ setProcessState(getProcessStateQuotaFreeThreshold());
synchronized (mQuotaController.mLock) {
assertEquals(mQcConstants.EJ_LIMIT_WORKING_MS / 2,
mQuotaController.getMaxJobExecutionTimeMsLocked(job));
@@ -1505,7 +1512,7 @@ public class QuotaControllerTest {
createTimingSession(sElapsedRealtimeClock.millis() - mQcConstants.EJ_WINDOW_SIZE_MS,
timeUsedMs, 5), true);
- setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
+ setProcessState(getProcessStateQuotaFreeThreshold());
synchronized (mQuotaController.mLock) {
assertEquals(mQcConstants.EJ_LIMIT_WORKING_MS / 2,
mQuotaController.getMaxJobExecutionTimeMsLocked(job));
@@ -4126,7 +4133,7 @@ public class QuotaControllerTest {
}
advanceElapsedClock(5 * SECOND_IN_MILLIS);
// Change to a state that should still be considered foreground.
- setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
+ setProcessState(getProcessStateQuotaFreeThreshold());
advanceElapsedClock(5 * SECOND_IN_MILLIS);
synchronized (mQuotaController.mLock) {
mQuotaController.maybeStopTrackingJobLocked(jobStatus, null);
@@ -4134,6 +4141,36 @@ public class QuotaControllerTest {
assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
}
+ /** Tests that Timers count FOREGROUND_SERVICE jobs. */
+ @Test
+ @RequiresFlagsEnabled(FLAG_ENFORCE_QUOTA_POLICY_TO_FGS_JOBS)
+ public void testTimerTracking_Fgs() {
+ setDischarging();
+
+ JobStatus jobStatus = createJobStatus("testTimerTracking_Fgs", 1);
+ setProcessState(ActivityManager.PROCESS_STATE_BOUND_TOP);
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
+ }
+
+ assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
+
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.prepareForExecutionLocked(jobStatus);
+ }
+ advanceElapsedClock(5 * SECOND_IN_MILLIS);
+ // Change to FOREGROUND_SERVICE state that should count.
+ setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
+ long start = JobSchedulerService.sElapsedRealtimeClock.millis();
+ advanceElapsedClock(5 * SECOND_IN_MILLIS);
+ synchronized (mQuotaController.mLock) {
+ mQuotaController.maybeStopTrackingJobLocked(jobStatus, null);
+ }
+ List<TimingSession> expected = new ArrayList<>();
+ expected.add(createTimingSession(start, 5 * SECOND_IN_MILLIS, 1));
+ assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
+ }
+
/**
* Tests that Timers properly track sessions when switching between foreground and background
* states.
@@ -4180,7 +4217,7 @@ public class QuotaControllerTest {
}
advanceElapsedClock(10 * SECOND_IN_MILLIS);
expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
- setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
+ setProcessState(getProcessStateQuotaFreeThreshold());
synchronized (mQuotaController.mLock) {
mQuotaController.prepareForExecutionLocked(jobFg3);
}
@@ -4213,7 +4250,7 @@ public class QuotaControllerTest {
}
advanceElapsedClock(10 * SECOND_IN_MILLIS);
expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
- setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
+ setProcessState(getProcessStateQuotaFreeThreshold());
synchronized (mQuotaController.mLock) {
mQuotaController.prepareForExecutionLocked(jobFg3);
}
@@ -4262,7 +4299,7 @@ public class QuotaControllerTest {
}
assertEquals(0, stats.jobCountInRateLimitingWindow);
- setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
+ setProcessState(getProcessStateQuotaFreeThreshold());
synchronized (mQuotaController.mLock) {
mQuotaController.prepareForExecutionLocked(jobFg1);
}
@@ -4412,7 +4449,7 @@ public class QuotaControllerTest {
mQuotaController.maybeStopTrackingJobLocked(jobBg1, jobBg1);
}
advanceElapsedClock(5 * SECOND_IN_MILLIS);
- setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
+ setProcessState(getProcessStateQuotaFreeThreshold());
synchronized (mQuotaController.mLock) {
mQuotaController.prepareForExecutionLocked(jobFg1);
}
@@ -4625,7 +4662,7 @@ public class QuotaControllerTest {
// App still in foreground so everything should be in quota.
advanceElapsedClock(20 * SECOND_IN_MILLIS);
- setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
+ setProcessState(getProcessStateQuotaFreeThreshold());
assertTrue(jobTop.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
assertTrue(jobFg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
assertTrue(jobBg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
@@ -5901,7 +5938,7 @@ public class QuotaControllerTest {
}
advanceElapsedClock(10 * SECOND_IN_MILLIS);
expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
- setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
+ setProcessState(getProcessStateQuotaFreeThreshold());
synchronized (mQuotaController.mLock) {
mQuotaController.prepareForExecutionLocked(jobFg3);
}
@@ -5935,7 +5972,7 @@ public class QuotaControllerTest {
}
advanceElapsedClock(10 * SECOND_IN_MILLIS);
expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
- setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
+ setProcessState(getProcessStateQuotaFreeThreshold());
synchronized (mQuotaController.mLock) {
mQuotaController.prepareForExecutionLocked(jobFg3);
}
@@ -6056,7 +6093,7 @@ public class QuotaControllerTest {
mQuotaController.maybeStopTrackingJobLocked(jobBg1, jobBg1);
}
advanceElapsedClock(5 * SECOND_IN_MILLIS);
- setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
+ setProcessState(getProcessStateQuotaFreeThreshold());
synchronized (mQuotaController.mLock) {
mQuotaController.prepareForExecutionLocked(jobFg1);
}
@@ -6534,7 +6571,7 @@ public class QuotaControllerTest {
// App still in foreground so everything should be in quota.
advanceElapsedClock(20 * SECOND_IN_MILLIS);
- setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
+ setProcessState(getProcessStateQuotaFreeThreshold());
assertTrue(jobTop2.isExpeditedQuotaApproved());
assertTrue(jobFg.isExpeditedQuotaApproved());
assertTrue(jobBg.isExpeditedQuotaApproved());
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
index dad45b3048b9..e09933a55fb9 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
@@ -245,6 +245,8 @@ public class VirtualDeviceManagerServiceTest {
@Mock
private IDisplayManager mIDisplayManager;
@Mock
+ private WindowManager mWindowManager;
+ @Mock
private VirtualDeviceImpl.PendingTrampolineCallback mPendingTrampolineCallback;
@Mock
private DevicePolicyManager mDevicePolicyManagerMock;
@@ -383,8 +385,7 @@ public class VirtualDeviceManagerServiceTest {
// Allow virtual devices to be created on the looper thread for testing.
final InputController.DeviceCreationThreadVerifier threadVerifier = () -> true;
mInputController = new InputController(mNativeWrapperMock,
- new Handler(TestableLooper.get(this).getLooper()),
- mContext.getSystemService(WindowManager.class),
+ new Handler(TestableLooper.get(this).getLooper()), mWindowManager,
AttributionSource.myAttributionSource(), threadVerifier);
mCameraAccessController =
new CameraAccessController(mContext, mLocalService, mCameraAccessBlockedCallback);
@@ -535,7 +536,7 @@ public class VirtualDeviceManagerServiceTest {
.build();
mDeviceImpl.close();
mDeviceImpl = createVirtualDevice(VIRTUAL_DEVICE_ID_1, DEVICE_OWNER_UID_1, params);
- addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
+ addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1, Display.FLAG_TRUSTED);
GenericWindowPolicyController gwpc =
mDeviceImpl.getDisplayWindowPolicyControllerForTest(DISPLAY_ID_1);
@@ -543,6 +544,21 @@ public class VirtualDeviceManagerServiceTest {
}
@Test
+ public void getDevicePolicy_customRecentsPolicy_untrustedDisplaygwpcShowsRecentsOnHostDevice() {
+ VirtualDeviceParams params = new VirtualDeviceParams
+ .Builder()
+ .setDevicePolicy(POLICY_TYPE_RECENTS, DEVICE_POLICY_CUSTOM)
+ .build();
+ mDeviceImpl.close();
+ mDeviceImpl = createVirtualDevice(VIRTUAL_DEVICE_ID_1, DEVICE_OWNER_UID_1, params);
+ addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
+
+ GenericWindowPolicyController gwpc =
+ mDeviceImpl.getDisplayWindowPolicyControllerForTest(DISPLAY_ID_1);
+ assertThat(gwpc.canShowTasksInHostDeviceRecents()).isTrue();
+ }
+
+ @Test
public void getDeviceOwnerUid_oneDevice_returnsCorrectId() {
int ownerUid = mLocalService.getDeviceOwnerUid(mDeviceImpl.getDeviceId());
assertThat(ownerUid).isEqualTo(mDeviceImpl.getOwnerUid());
@@ -692,7 +708,7 @@ public class VirtualDeviceManagerServiceTest {
@Test
public void getPreferredLocaleListForApp_keyboardAttached_returnLocaleHints() {
- addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
+ addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1, Display.FLAG_TRUSTED);
mDeviceImpl.createVirtualKeyboard(KEYBOARD_CONFIG, BINDER);
mVdms.notifyRunningAppsChanged(mDeviceImpl.getDeviceId(), Sets.newArraySet(UID_1));
@@ -732,8 +748,8 @@ public class VirtualDeviceManagerServiceTest {
.setLanguageTag("fr-FR")
.build();
- addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
- addVirtualDisplay(secondDevice, DISPLAY_ID_2);
+ addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1, Display.FLAG_TRUSTED);
+ addVirtualDisplay(secondDevice, DISPLAY_ID_2, Display.FLAG_TRUSTED);
mDeviceImpl.createVirtualKeyboard(firstKeyboardConfig, BINDER);
secondDevice.createVirtualKeyboard(secondKeyboardConfig, secondBinder);
@@ -910,11 +926,24 @@ public class VirtualDeviceManagerServiceTest {
}
@Test
- public void onVirtualDisplayCreatedLocked_wakeLockIsAcquired() throws RemoteException {
+ public void onVirtualDisplayCreatedLocked_notTrustedDisplay_noWakeLockIsAcquired()
+ throws RemoteException {
verify(mIPowerManagerMock, never()).acquireWakeLock(any(Binder.class), anyInt(),
nullable(String.class), nullable(String.class), nullable(WorkSource.class),
nullable(String.class), anyInt(), eq(null));
addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
+ TestableLooper.get(this).processAllMessages();
+ verify(mIPowerManagerMock, never()).acquireWakeLock(any(Binder.class), anyInt(),
+ nullable(String.class), nullable(String.class), nullable(WorkSource.class),
+ nullable(String.class), anyInt(), eq(null));
+ }
+
+ @Test
+ public void onVirtualDisplayCreatedLocked_wakeLockIsAcquired() throws RemoteException {
+ verify(mIPowerManagerMock, never()).acquireWakeLock(any(Binder.class), anyInt(),
+ nullable(String.class), nullable(String.class), nullable(WorkSource.class),
+ nullable(String.class), anyInt(), eq(null));
+ addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1, Display.FLAG_TRUSTED);
verify(mIPowerManagerMock).acquireWakeLock(any(Binder.class), anyInt(),
nullable(String.class), nullable(String.class), nullable(WorkSource.class),
nullable(String.class), eq(DISPLAY_ID_1), eq(null));
@@ -923,7 +952,7 @@ public class VirtualDeviceManagerServiceTest {
@Test
public void onVirtualDisplayCreatedLocked_duplicateCalls_onlyOneWakeLockIsAcquired()
throws RemoteException {
- addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
+ addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1, Display.FLAG_TRUSTED);
assertThrows(IllegalStateException.class,
() -> addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1));
TestableLooper.get(this).processAllMessages();
@@ -934,7 +963,7 @@ public class VirtualDeviceManagerServiceTest {
@Test
public void onVirtualDisplayRemovedLocked_wakeLockIsReleased() throws RemoteException {
- addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
+ addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1, Display.FLAG_TRUSTED);
ArgumentCaptor<IBinder> wakeLockCaptor = ArgumentCaptor.forClass(IBinder.class);
TestableLooper.get(this).processAllMessages();
verify(mIPowerManagerMock).acquireWakeLock(wakeLockCaptor.capture(),
@@ -949,7 +978,7 @@ public class VirtualDeviceManagerServiceTest {
@Test
public void addVirtualDisplay_displayNotReleased_wakeLockIsReleased() throws RemoteException {
- addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
+ addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1, Display.FLAG_TRUSTED);
ArgumentCaptor<IBinder> wakeLockCaptor = ArgumentCaptor.forClass(IBinder.class);
TestableLooper.get(this).processAllMessages();
verify(mIPowerManagerMock).acquireWakeLock(wakeLockCaptor.capture(),
@@ -970,24 +999,52 @@ public class VirtualDeviceManagerServiceTest {
}
@Test
+ public void createVirtualDpad_untrustedDisplay_failsSecurityException() {
+ addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
+ assertThrows(SecurityException.class,
+ () -> mDeviceImpl.createVirtualDpad(DPAD_CONFIG, BINDER));
+ }
+
+ @Test
public void createVirtualKeyboard_noDisplay_failsSecurityException() {
assertThrows(SecurityException.class,
() -> mDeviceImpl.createVirtualKeyboard(KEYBOARD_CONFIG, BINDER));
}
@Test
+ public void createVirtualKeyboard_untrustedDisplay_failsSecurityException() {
+ addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
+ assertThrows(SecurityException.class,
+ () -> mDeviceImpl.createVirtualKeyboard(KEYBOARD_CONFIG, BINDER));
+ }
+
+ @Test
public void createVirtualMouse_noDisplay_failsSecurityException() {
assertThrows(SecurityException.class,
() -> mDeviceImpl.createVirtualMouse(MOUSE_CONFIG, BINDER));
}
@Test
+ public void createVirtualMouse_untrustedDisplay_failsSecurityException() {
+ addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
+ assertThrows(SecurityException.class,
+ () -> mDeviceImpl.createVirtualMouse(MOUSE_CONFIG, BINDER));
+ }
+
+ @Test
public void createVirtualTouchscreen_noDisplay_failsSecurityException() {
assertThrows(SecurityException.class,
() -> mDeviceImpl.createVirtualTouchscreen(TOUCHSCREEN_CONFIG, BINDER));
}
@Test
+ public void createVirtualTouchscreen_untrustedDisplay_failsSecurityException() {
+ addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
+ assertThrows(SecurityException.class,
+ () -> mDeviceImpl.createVirtualTouchscreen(TOUCHSCREEN_CONFIG, BINDER));
+ }
+
+ @Test
public void createVirtualTouchscreen_zeroDisplayDimension_failsIllegalArgumentException() {
assertThrows(IllegalArgumentException.class,
() -> new VirtualTouchscreenConfig.Builder(
@@ -1003,7 +1060,7 @@ public class VirtualDeviceManagerServiceTest {
@Test
public void createVirtualTouchscreen_positiveDisplayDimension_successful() {
- addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
+ addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1, Display.FLAG_TRUSTED);
VirtualTouchscreenConfig positiveConfig =
new VirtualTouchscreenConfig.Builder(
/* touchscrenWidth= */ 600, /* touchscreenHeight= */ 800)
@@ -1026,6 +1083,14 @@ public class VirtualDeviceManagerServiceTest {
}
@Test
+ public void createVirtualNavigationTouchpad_untrustedDisplay_failsSecurityException() {
+ addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
+ assertThrows(SecurityException.class,
+ () -> mDeviceImpl.createVirtualNavigationTouchpad(NAVIGATION_TOUCHPAD_CONFIG,
+ BINDER));
+ }
+
+ @Test
public void createVirtualNavigationTouchpad_zeroDisplayDimension_failsWithException() {
assertThrows(IllegalArgumentException.class,
() -> new VirtualNavigationTouchpadConfig.Builder(
@@ -1041,7 +1106,7 @@ public class VirtualDeviceManagerServiceTest {
@Test
public void createVirtualNavigationTouchpad_positiveDisplayDimension_successful() {
- addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
+ addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1, Display.FLAG_TRUSTED);
VirtualNavigationTouchpadConfig positiveConfig =
new VirtualNavigationTouchpadConfig.Builder(
/* touchpadHeight= */ 50, /* touchpadWidth= */ 50)
@@ -1130,7 +1195,7 @@ public class VirtualDeviceManagerServiceTest {
@Test
public void createVirtualDpad_hasDisplay_obtainFileDescriptor() {
- addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
+ addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1, Display.FLAG_TRUSTED);
mDeviceImpl.createVirtualDpad(DPAD_CONFIG, BINDER);
assertWithMessage("Virtual dpad should register fd when the display matches").that(
mInputController.getInputDeviceDescriptors()).isNotEmpty();
@@ -1140,7 +1205,7 @@ public class VirtualDeviceManagerServiceTest {
@Test
public void createVirtualKeyboard_hasDisplay_obtainFileDescriptor() {
- addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
+ addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1, Display.FLAG_TRUSTED);
mDeviceImpl.createVirtualKeyboard(KEYBOARD_CONFIG, BINDER);
assertWithMessage("Virtual keyboard should register fd when the display matches").that(
mInputController.getInputDeviceDescriptors()).isNotEmpty();
@@ -1150,7 +1215,7 @@ public class VirtualDeviceManagerServiceTest {
@Test
public void createVirtualKeyboard_keyboardCreated_localeUpdated() {
- addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
+ addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1, Display.FLAG_TRUSTED);
mDeviceImpl.createVirtualKeyboard(KEYBOARD_CONFIG, BINDER);
assertWithMessage("Virtual keyboard should register fd when the display matches")
.that(mInputController.getInputDeviceDescriptors())
@@ -1171,7 +1236,7 @@ public class VirtualDeviceManagerServiceTest {
.setAssociatedDisplayId(DISPLAY_ID_1)
.build();
- addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
+ addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1, Display.FLAG_TRUSTED);
mDeviceImpl.createVirtualKeyboard(configWithoutExplicitLayoutInfo, BINDER);
assertWithMessage("Virtual keyboard should register fd when the display matches")
.that(mInputController.getInputDeviceDescriptors())
@@ -1192,7 +1257,7 @@ public class VirtualDeviceManagerServiceTest {
@Test
public void createVirtualMouse_hasDisplay_obtainFileDescriptor() {
- addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
+ addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1, Display.FLAG_TRUSTED);
mDeviceImpl.createVirtualMouse(MOUSE_CONFIG, BINDER);
assertWithMessage("Virtual mouse should register fd when the display matches").that(
mInputController.getInputDeviceDescriptors()).isNotEmpty();
@@ -1202,7 +1267,7 @@ public class VirtualDeviceManagerServiceTest {
@Test
public void createVirtualTouchscreen_hasDisplay_obtainFileDescriptor() {
- addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
+ addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1, Display.FLAG_TRUSTED);
mDeviceImpl.createVirtualTouchscreen(TOUCHSCREEN_CONFIG, BINDER);
assertWithMessage("Virtual touchscreen should register fd when the display matches").that(
mInputController.getInputDeviceDescriptors()).isNotEmpty();
@@ -1212,7 +1277,7 @@ public class VirtualDeviceManagerServiceTest {
@Test
public void createVirtualNavigationTouchpad_hasDisplay_obtainFileDescriptor() {
- addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
+ addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1, Display.FLAG_TRUSTED);
mDeviceImpl.createVirtualNavigationTouchpad(NAVIGATION_TOUCHPAD_CONFIG, BINDER);
assertWithMessage("Virtual navigation touchpad should register fd when the display matches")
.that(
@@ -1472,9 +1537,9 @@ public class VirtualDeviceManagerServiceTest {
@Test
public void setShowPointerIcon_setsValueForAllDisplays() {
- addVirtualDisplay(mDeviceImpl, 1);
- addVirtualDisplay(mDeviceImpl, 2);
- addVirtualDisplay(mDeviceImpl, 3);
+ addVirtualDisplay(mDeviceImpl, 1, Display.FLAG_TRUSTED);
+ addVirtualDisplay(mDeviceImpl, 2, Display.FLAG_TRUSTED);
+ addVirtualDisplay(mDeviceImpl, 3, Display.FLAG_TRUSTED);
VirtualMouseConfig config1 = new VirtualMouseConfig.Builder()
.setAssociatedDisplayId(1)
.setInputDeviceName(DEVICE_NAME_1)
@@ -1507,6 +1572,14 @@ public class VirtualDeviceManagerServiceTest {
}
@Test
+ public void setShowPointerIcon_untrustedDisplay_pointerIconIsAlwaysShown() {
+ addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
+ clearInvocations(mInputManagerInternalMock);
+ mDeviceImpl.setShowPointerIcon(false);
+ verify(mInputManagerInternalMock, times(0)).setPointerIconVisible(eq(false), anyInt());
+ }
+
+ @Test
public void openNonBlockedAppOnVirtualDisplay_doesNotStartBlockedAlertActivity() {
addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
GenericWindowPolicyController gwpc = mDeviceImpl.getDisplayWindowPolicyControllerForTest(
@@ -1968,15 +2041,20 @@ public class VirtualDeviceManagerServiceTest {
}
private void addVirtualDisplay(VirtualDeviceImpl virtualDevice, int displayId) {
+ addVirtualDisplay(virtualDevice, displayId, /* flags= */ 0);
+ }
+
+ private void addVirtualDisplay(VirtualDeviceImpl virtualDevice, int displayId, int flags) {
when(mDisplayManagerInternalMock.createVirtualDisplay(any(), eq(mVirtualDisplayCallback),
eq(virtualDevice), any(), any())).thenReturn(displayId);
- virtualDevice.createVirtualDisplay(VIRTUAL_DISPLAY_CONFIG, mVirtualDisplayCallback);
final String uniqueId = UNIQUE_ID + displayId;
doAnswer(inv -> {
final DisplayInfo displayInfo = new DisplayInfo();
displayInfo.uniqueId = uniqueId;
+ displayInfo.flags = flags;
return displayInfo;
}).when(mDisplayManagerInternalMock).getDisplayInfo(eq(displayId));
+ virtualDevice.createVirtualDisplay(VIRTUAL_DISPLAY_CONFIG, mVirtualDisplayCallback);
mInputManagerMockHelper.addDisplayIdMapping(uniqueId, displayId);
}
diff --git a/services/tests/servicestests/src/com/android/server/compat/PlatformCompatTest.java b/services/tests/servicestests/src/com/android/server/compat/PlatformCompatTest.java
index 9df7a3612a92..1d075401832d 100644
--- a/services/tests/servicestests/src/com/android/server/compat/PlatformCompatTest.java
+++ b/services/tests/servicestests/src/com/android/server/compat/PlatformCompatTest.java
@@ -20,6 +20,7 @@ import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.anyLong;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
@@ -33,6 +34,11 @@ import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.os.Build;
+import android.os.Process;
+
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
import androidx.test.runner.AndroidJUnit4;
@@ -43,6 +49,7 @@ import com.android.internal.compat.CompatibilityChangeInfo;
import com.android.server.LocalServices;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -55,6 +62,8 @@ import java.util.Set;
public class PlatformCompatTest {
private static final String PACKAGE_NAME = "my.package";
+ @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
@Mock
private Context mContext;
@Mock
@@ -441,4 +450,79 @@ public class PlatformCompatTest {
assertThat(mPlatformCompat.isChangeEnabled(3L, systemAppInfo)).isTrue();
verify(mChangeReporter).reportChange(123, 3L, ChangeReporter.STATE_ENABLED, true, false);
}
+
+ @DisableFlags(Flags.FLAG_SYSTEM_UID_TARGET_SYSTEM_SDK)
+ @Test
+ public void testSharedSystemUidFlagOff() throws Exception {
+ testSharedSystemUid(false);
+ }
+
+ @EnableFlags(Flags.FLAG_SYSTEM_UID_TARGET_SYSTEM_SDK)
+ @Test
+ public void testSharedSystemUidFlagOn() throws Exception {
+ testSharedSystemUid(true);
+ }
+
+ private void testSharedSystemUid(Boolean expectSystemUidTargetSystemSdk) throws Exception {
+ final String systemUidPackageNameTargetsR = "systemuid.package1";
+ final String systemUidPackageNameTargetsQ = "systemuid.package2";
+ final String nonSystemUidPackageNameTargetsR = "nonsystemuid.package1";
+ final String nonSystemUidPackageNameTargetsQ = "nonsystemuid.package2";
+ final int nonSystemUid = 123;
+
+ mCompatConfig =
+ CompatConfigBuilder.create(mBuildClassifier, mContext)
+ .addEnableSinceSdkChangeWithId(Build.VERSION_CODES.R, 1L)
+ .build();
+ mCompatConfig.forceNonDebuggableFinalForTest(true);
+ mPlatformCompat =
+ new PlatformCompat(mContext, mCompatConfig, mBuildClassifier, mChangeReporter);
+
+ ApplicationInfo systemUidAppInfo1 = ApplicationInfoBuilder.create()
+ .withPackageName(systemUidPackageNameTargetsR)
+ .withUid(Process.SYSTEM_UID)
+ .withTargetSdk(Build.VERSION_CODES.R)
+ .build();
+ when(mPackageManagerInternal.getApplicationInfo(
+ eq(systemUidPackageNameTargetsR), anyLong(), anyInt(), anyInt()))
+ .thenReturn(systemUidAppInfo1);
+
+ ApplicationInfo systemUidAppInfo2 = ApplicationInfoBuilder.create()
+ .withPackageName(systemUidPackageNameTargetsQ)
+ .withUid(Process.SYSTEM_UID)
+ .withTargetSdk(Build.VERSION_CODES.Q)
+ .build();
+ when(mPackageManagerInternal.getApplicationInfo(
+ eq(systemUidPackageNameTargetsQ), anyLong(), anyInt(), anyInt()))
+ .thenReturn(systemUidAppInfo2);
+
+ ApplicationInfo nonSystemUidAppInfo1 = ApplicationInfoBuilder.create()
+ .withPackageName(nonSystemUidPackageNameTargetsR)
+ .withUid(nonSystemUid)
+ .withTargetSdk(Build.VERSION_CODES.R)
+ .build();
+ when(mPackageManagerInternal.getApplicationInfo(
+ eq(nonSystemUidPackageNameTargetsR), anyLong(), anyInt(), anyInt()))
+ .thenReturn(nonSystemUidAppInfo1);
+
+ ApplicationInfo nonSystemUidAppInfo2 = ApplicationInfoBuilder.create()
+ .withPackageName(nonSystemUidPackageNameTargetsQ)
+ .withUid(nonSystemUid)
+ .withTargetSdk(Build.VERSION_CODES.Q)
+ .build();
+ when(mPackageManagerInternal.getApplicationInfo(
+ eq(nonSystemUidPackageNameTargetsQ), anyLong(), anyInt(), anyInt()))
+ .thenReturn(nonSystemUidAppInfo2);
+
+ when(mPackageManager.getPackagesForUid(eq(Process.SYSTEM_UID)))
+ .thenReturn(new String[] {systemUidPackageNameTargetsR, systemUidPackageNameTargetsQ});
+ when(mPackageManager.getPackagesForUid(eq(nonSystemUid)))
+ .thenReturn(new String[] {
+ nonSystemUidPackageNameTargetsR, nonSystemUidPackageNameTargetsQ
+ });
+
+ assertThat(mPlatformCompat.isChangeEnabledByUid(1L, Process.SYSTEM_UID))
+ .isEqualTo(expectSystemUidTargetSystemSdk);
+ assertThat(mPlatformCompat.isChangeEnabledByUid(1L, nonSystemUid)).isFalse();
+ }
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java b/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java
index e4436966ae03..c51261f40ed5 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java
@@ -52,6 +52,7 @@ import android.graphics.Rect;
import android.os.IBinder;
import android.platform.test.annotations.Presubmit;
import android.view.ContentRecordingSession;
+import android.view.Display;
import android.view.DisplayInfo;
import android.view.Gravity;
import android.view.SurfaceControl;
@@ -93,9 +94,11 @@ public class ContentRecorderTests extends WindowTestsBase {
private boolean mHandleAnisotropicDisplayMirroring = false;
@Before public void setUp() {
+ mDisplayInfo.type = Display.TYPE_VIRTUAL;
MockitoAnnotations.initMocks(this);
doReturn(INVALID_DISPLAY).when(mWm.mDisplayManagerInternal).getDisplayIdToMirror(anyInt());
+ doReturn(false).when(mWm.mDisplayManagerInternal).isDisplayReadyForMirroring(anyInt());
// Skip unnecessary operations of relayout.
spyOn(mWm.mWindowPlacerLocked);
@@ -163,6 +166,25 @@ public class ContentRecorderTests extends WindowTestsBase {
}
@Test
+ public void testUpdateRecording_externalDisplayWithoutUserConfirmation() {
+ mDisplayInfo.type = Display.TYPE_EXTERNAL;
+ defaultInit();
+ mContentRecorder.setContentRecordingSession(mDisplaySession);
+ mContentRecorder.updateRecording();
+ assertThat(mContentRecorder.isCurrentlyRecording()).isFalse();
+ }
+
+ @Test
+ public void testUpdateRecording_externalDisplayWithUserConfirmation() {
+ doReturn(true).when(mWm.mDisplayManagerInternal).isDisplayReadyForMirroring(anyInt());
+ mDisplayInfo.type = Display.TYPE_EXTERNAL;
+ defaultInit();
+ mContentRecorder.setContentRecordingSession(mDisplaySession);
+ mContentRecorder.updateRecording();
+ assertThat(mContentRecorder.isCurrentlyRecording()).isTrue();
+ }
+
+ @Test
public void testUpdateRecording_display_invalidDisplayIdToMirror() {
defaultInit();
ContentRecordingSession session = ContentRecordingSession.createDisplaySession(
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationImmersiveAppCompatPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationImmersiveAppCompatPolicyTests.java
index 5e8f347c0c6e..c8fc4822259e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationImmersiveAppCompatPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationImmersiveAppCompatPolicyTests.java
@@ -26,7 +26,6 @@ import static android.content.res.Configuration.ORIENTATION_UNDEFINED;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
import static org.junit.Assert.assertFalse;
@@ -73,7 +72,6 @@ public class DisplayRotationImmersiveAppCompatPolicyTests extends WindowTestsBas
when(mMockWindowState.getRequestedVisibleTypes()).thenReturn(0);
when(mMockActivityRecord.findMainWindow()).thenReturn(mMockWindowState);
- spy(mDisplayContent);
doReturn(mMockActivityRecord).when(mDisplayContent).topRunningActivity();
when(mDisplayContent.getIgnoreOrientationRequest()).thenReturn(true);
diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java
index 49ca6f34d2d9..44de65a009ff 100644
--- a/telephony/java/android/telephony/satellite/SatelliteManager.java
+++ b/telephony/java/android/telephony/satellite/SatelliteManager.java
@@ -1965,13 +1965,14 @@ public final class SatelliteManager {
}
/**
- * Inform whether the device is aligned with the satellite for demo mode.
+ * Inform whether the device is aligned with the satellite in both real and demo mode.
*
- * Framework can send datagram to modem only when device is aligned with the satellite.
- * This method helps framework to simulate the experience of sending datagram over satellite.
+ * In demo mode, framework will send datagram to modem only when device is aligned with
+ * the satellite. This method helps framework to simulate the experience of sending datagram
+ * over satellite.
*
- * @param isAligned {@true} Device is aligned with the satellite for demo mode
- * {@false} Device is not aligned with the satellite for demo mode
+ * @param isAligned {code @true} Device is aligned with the satellite
+ * {code @false} Device is not aligned with the satellite
*
* @throws SecurityException if the caller doesn't have required permission.
* @throws IllegalStateException if the Telephony process is not currently available.
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 61f01461232f..231c8f551389 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -2977,10 +2977,10 @@ interface ITelephony {
void requestTimeForNextSatelliteVisibility(in ResultReceiver receiver);
/**
- * Inform whether the device is aligned with the satellite within in margin for demo mode.
+ * Inform whether the device is aligned with the satellite in both real and demo mode.
*
- * @param isAligned {@true} Device is aligned with the satellite for demo mode
- * {@false} Device is not aligned with the satellite for demo mode
+ * @param isAligned {@true} Device is aligned with the satellite.
+ * {@false} Device is not aligned with the satellite.
*/
@JavaPassthrough(annotation="@android.annotation.RequiresPermission("
+ "android.Manifest.permission.SATELLITE_COMMUNICATION)")
diff --git a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/LetterboxAppHelper.kt b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/LetterboxAppHelper.kt
index 634b6eedd7e6..8d27c1d1dfd1 100644
--- a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/LetterboxAppHelper.kt
+++ b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/LetterboxAppHelper.kt
@@ -33,9 +33,9 @@ class LetterboxAppHelper
@JvmOverloads
constructor(
instr: Instrumentation,
- launcherName: String = ActivityOptions.NonResizeablePortraitActivity.LABEL,
+ launcherName: String = ActivityOptions.NonResizeableFixedAspectRatioPortraitActivity.LABEL,
component: ComponentNameMatcher =
- ActivityOptions.NonResizeablePortraitActivity.COMPONENT.toFlickerComponent()
+ ActivityOptions.NonResizeableFixedAspectRatioPortraitActivity.COMPONENT.toFlickerComponent()
) : StandardAppHelper(instr, launcherName, component) {
private val gestureHelper: GestureHelper = GestureHelper(instrumentation)
diff --git a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
index f891606f0066..f2e34257ef01 100644
--- a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
+++ b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
@@ -115,6 +115,19 @@
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
+ <activity android:name=".NonResizeableFixedAspectRatioPortraitActivity"
+ android:theme="@style/CutoutNever"
+ android:resizeableActivity="false"
+ android:screenOrientation="portrait"
+ android:minAspectRatio="1.77"
+ android:taskAffinity="com.android.server.wm.flicker.testapp.NonResizeableFixedAspectRatioPortraitActivity"
+ android:label="NonResizeableFixedAspectRatioPortraitActivity"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.LAUNCHER"/>
+ </intent-filter>
+ </activity>
<activity android:name=".StartMediaProjectionActivity"
android:theme="@style/CutoutNever"
android:resizeableActivity="false"
@@ -143,6 +156,7 @@
<activity android:name=".LaunchTransparentActivity"
android:resizeableActivity="false"
android:screenOrientation="portrait"
+ android:minAspectRatio="1.77"
android:theme="@style/OptOutEdgeToEdge"
android:taskAffinity="com.android.server.wm.flicker.testapp.LaunchTransparentActivity"
android:label="LaunchTransparentActivity"
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java
index e4de2c574553..73625da9dfa5 100644
--- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java
@@ -85,6 +85,12 @@ public class ActivityOptions {
FLICKER_APP_PACKAGE + ".NonResizeablePortraitActivity");
}
+ public static class NonResizeableFixedAspectRatioPortraitActivity {
+ public static final String LABEL = "NonResizeableFixedAspectRatioPortraitActivity";
+ public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE,
+ FLICKER_APP_PACKAGE + ".NonResizeableFixedAspectRatioPortraitActivity");
+ }
+
public static class StartMediaProjectionActivity {
public static final String LABEL = "StartMediaProjectionActivity";
public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE,
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/NonResizeableFixedAspectRatioPortraitActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/NonResizeableFixedAspectRatioPortraitActivity.java
new file mode 100644
index 000000000000..be38c259d00d
--- /dev/null
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/NonResizeableFixedAspectRatioPortraitActivity.java
@@ -0,0 +1,28 @@
+/*
+ * 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.wm.flicker.testapp;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+public class NonResizeableFixedAspectRatioPortraitActivity extends Activity {
+ @Override
+ protected void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+ setContentView(R.layout.activity_non_resizeable);
+ }
+}